From 0b7212ac1c3db154ae6e4e493887ed2db8acd3ac Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sun, 20 Sep 2026 22:17:55 +0330 Subject: [PATCH 1/8] feat(payment): implement multi-gateway support and enhance payment processing - Added support for multiple payment gateways, including ZarinPal and TwentyCard, through a new IPaymentGatewayResolver. - Updated PaymentRequest DTO to include payer name and invoice number for better transaction tracking. - Enhanced PackageService to dynamically determine payment amounts based on the selected gateway. - Updated appsettings to include configuration for TwentyCard payment options. - Bumped Protobuf project version to reflect the addition of new features and changes. --- README.md | 4 +- .../CMSMicroservice.Application.csproj | 1 + .../Interfaces/IPaymentGatewayResolver.cs | 9 + .../Interfaces/IPaymentGatewayService.cs | 10 + ...serPackagePurchasePaymentCommandHandler.cs | 34 ++- .../ConfigureServices.cs | 22 +- .../Payment/PaymentGatewayResolver.cs | 35 +++ .../Payment/TwentyCardPaymentService.cs | 264 ++++++++++++++++++ .../CMSMicroservice.Protobuf.csproj | 2 +- .../Protos/package.proto | 2 + .../Services/PackageService.cs | 64 ++++- .../appsettings.Staging.json | 17 ++ src/CMSMicroservice.WebApi/appsettings.json | 17 ++ 13 files changed, 447 insertions(+), 34 deletions(-) create mode 100644 src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayResolver.cs create mode 100644 src/CMSMicroservice.Infrastructure/Services/Payment/PaymentGatewayResolver.cs create mode 100644 src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs diff --git a/README.md b/README.md index b4153ef..70283c2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ # CMS Microservice -Documentation moved to [totalDoc/INDEX.md](../totalDoc/INDEX.md). - \ No newline at end of file +Documentation moved to [totalDoc/INDEX.md](../totalDoc/INDEX.md). + \ No newline at end of file diff --git a/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj b/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj index b364983..5b33f39 100644 --- a/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj +++ b/src/CMSMicroservice.Application/CMSMicroservice.Application.csproj @@ -15,6 +15,7 @@ + diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayResolver.cs b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayResolver.cs new file mode 100644 index 0000000..2c16d14 --- /dev/null +++ b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayResolver.cs @@ -0,0 +1,9 @@ +namespace CMSMicroservice.Application.Common.Interfaces; + +/// +/// Resolve payment gateway by provider key (zarinpal, 20card, …). +/// +public interface IPaymentGatewayResolver +{ + IPaymentGatewayService Get(string? provider); +} diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs index 704749b..e411212 100644 --- a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs +++ b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs @@ -98,6 +98,16 @@ public class PaymentRequest /// URL بازگشت بعد از پرداخت /// public string CallbackUrl { get; set; } = string.Empty; + + /// + /// نام پرداخت‌کننده (برای درگاه‌هایی مثل ۲۰کارت) + /// + public string Name { get; set; } = string.Empty; + + /// + /// شماره فاکتور / سفارش + /// + public string Invoice { get; set; } = string.Empty; } /// diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyUserPackagePurchasePayment/VerifyUserPackagePurchasePaymentCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyUserPackagePurchasePayment/VerifyUserPackagePurchasePaymentCommandHandler.cs index 42c61ec..576cd6a 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyUserPackagePurchasePayment/VerifyUserPackagePurchasePaymentCommandHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyUserPackagePurchasePayment/VerifyUserPackagePurchasePaymentCommandHandler.cs @@ -8,6 +8,7 @@ using CMSMicroservice.Domain.Entities.Payment; using CMSMicroservice.Domain.Enums; using MediatR; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyUserPackagePurchasePayment; @@ -16,20 +17,23 @@ public class VerifyUserPackagePurchasePaymentCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; - private readonly IPaymentGatewayService _paymentGateway; + private readonly IPaymentGatewayResolver _paymentGatewayResolver; + private readonly IConfiguration _configuration; private readonly ISender _sender; private readonly IUserPaymentLock _paymentLock; private readonly ILogger _logger; public VerifyUserPackagePurchasePaymentCommandHandler( IApplicationDbContext context, - IPaymentGatewayService paymentGateway, + IPaymentGatewayResolver paymentGatewayResolver, + IConfiguration configuration, ISender sender, IUserPaymentLock paymentLock, ILogger logger) { _context = context; - _paymentGateway = paymentGateway; + _paymentGatewayResolver = paymentGatewayResolver; + _configuration = configuration; _sender = sender; _paymentLock = paymentLock; _logger = logger; @@ -105,7 +109,8 @@ public class VerifyUserPackagePurchasePaymentCommandHandler } var amountInToman = paymentTx?.Amount ?? purchase.Amount; - var verifyResult = await _paymentGateway.VerifyPaymentAsync( + var gateway = _paymentGatewayResolver.Get(paymentTx?.GatewayProvider); + var verifyResult = await gateway.VerifyPaymentAsync( request.Authority, request.Status, (decimal)amountInToman, ct); if (!verifyResult.IsSuccess) @@ -193,17 +198,30 @@ public class VerifyUserPackagePurchasePaymentCommandHandler await _context.SaveChangesAsync(ct); } - var discountMultiplier = purchase.Package?.DiscountMultiplier + var packagePrice = purchase.Package?.Price ?? throw new InvalidOperationException("پکیج سفارش یافت نشد"); - var discountAmount = (long)(purchase.Amount * (double)discountMultiplier); - wallet.Balance += purchase.Amount; + var discountMultiplier = purchase.Package.DiscountMultiplier; + var discountAmount = (long)(packagePrice * (double)discountMultiplier); + + var isTwentyCard = string.Equals( + paymentTx?.GatewayProvider, "20card", StringComparison.OrdinalIgnoreCase); + long balanceCredit = purchase.Amount; + if (isTwentyCard) + { + balanceCredit = _configuration.GetValue( + $"TwentyCard:Packages:{purchase.PackageId}:WalletCreditToman") ?? 0; + if (balanceCredit <= 0) + throw new InvalidOperationException("WalletCredit ۲۰کارت برای این پکیج پیکربندی نشده است"); + } + + wallet.Balance += balanceCredit; wallet.DiscountBalance += discountAmount; _context.UserWalletHistories.Add(new UserWalletHistory { WalletId = wallet.Id, CurrentBalance = wallet.Balance, - ChangeValue = purchase.Amount, + ChangeValue = balanceCredit, CurrentNetworkBalance = wallet.NetworkBalance, ChangeNerworkValue = 0, CurrentDiscountBalance = wallet.DiscountBalance, diff --git a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs index 8890d70..0f854fb 100644 --- a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs +++ b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs @@ -77,27 +77,33 @@ public static class ConfigureServices }); } - // Payment Gateway Service - Multi-Provider Architecture - // پشتیبانی از درگاه‌های مختلف: ZarinPal, Daya, Mock + // Payment Gateway — default provider + multi-gateway resolver (ZarinPal + TwentyCard coexist) var paymentProvider = configuration.GetValue("PaymentProvider", "Mock")?.ToLowerInvariant(); - + + services.AddHttpClient() + .SetHandlerLifetime(TimeSpan.FromMinutes(5)); + services.AddHttpClient() + .SetHandlerLifetime(TimeSpan.FromMinutes(5)); + switch (paymentProvider) { case "zarinpal": - services.AddHttpClient() - .SetHandlerLifetime(TimeSpan.FromMinutes(5)); + services.AddScoped(sp => sp.GetRequiredService()); break; - + case "daya": - services.AddHttpClient() + services.AddHttpClient() .SetHandlerLifetime(TimeSpan.FromMinutes(5)); + services.AddScoped(sp => sp.GetRequiredService()); break; - + case "mock": default: services.AddScoped(); break; } + + services.AddScoped(); services.AddScoped(p => p.GetRequiredService()); diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/PaymentGatewayResolver.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/PaymentGatewayResolver.cs new file mode 100644 index 0000000..3aed4a3 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/PaymentGatewayResolver.cs @@ -0,0 +1,35 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Infrastructure.Services.Payment; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace CMSMicroservice.Infrastructure.Services.Payment; + +public class PaymentGatewayResolver : IPaymentGatewayResolver +{ + private readonly IServiceProvider _serviceProvider; + private readonly IConfiguration _configuration; + + public PaymentGatewayResolver(IServiceProvider serviceProvider, IConfiguration configuration) + { + _serviceProvider = serviceProvider; + _configuration = configuration; + } + + public IPaymentGatewayService Get(string? provider) + { + var key = Normalize(provider) + ?? Normalize(_configuration["PaymentProvider"]) + ?? "zarinpal"; + + return key switch + { + "20card" or "twentycard" => _serviceProvider.GetRequiredService(), + "zarinpal" => _serviceProvider.GetRequiredService(), + _ => _serviceProvider.GetRequiredService() + }; + } + + private static string? Normalize(string? value) + => string.IsNullOrWhiteSpace(value) ? null : value.Trim().ToLowerInvariant(); +} diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs new file mode 100644 index 0000000..01396a3 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs @@ -0,0 +1,264 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using CMSMicroservice.Application.Common.Interfaces; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Infrastructure.Services.Payment; + +/// +/// درگاه پرداخت بیست‌کارت (20Card IPG Merchant API). +/// مبلغ ورودی به تومان است (مثل بقیهٔ سیستم). +/// +public class TwentyCardPaymentService : IPaymentGatewayService +{ + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + private readonly string _terminalId; + private readonly string _terminalUsername; + private readonly string _terminalPassword; + + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + public TwentyCardPaymentService( + HttpClient httpClient, + IConfiguration configuration, + ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + + var baseUrl = configuration["TwentyCard:BaseUrl"] + ?? throw new InvalidOperationException("TwentyCard:BaseUrl is not configured."); + _terminalId = configuration["TwentyCard:TerminalId"] + ?? throw new InvalidOperationException("TwentyCard:TerminalId is not configured."); + _terminalUsername = configuration["TwentyCard:TerminalUsername"] + ?? throw new InvalidOperationException("TwentyCard:TerminalUsername is not configured."); + _terminalPassword = configuration["TwentyCard:TerminalPassword"] + ?? throw new InvalidOperationException("TwentyCard:TerminalPassword is not configured."); + + _httpClient.BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/"); + _logger.LogInformation("TwentyCard payment service initialized. BaseUrl={BaseUrl}", baseUrl); + } + + public async Task InitiatePaymentAsync( + PaymentRequest request, + CancellationToken cancellationToken = default) + { + try + { + var amount = (long)request.Amount; + var payerName = string.IsNullOrWhiteSpace(request.Name) + ? (string.IsNullOrWhiteSpace(request.Mobile) ? "کاربر" : request.Mobile) + : request.Name.Trim(); + + var body = new Dictionary + { + ["terminal_id"] = _terminalId, + ["terminal_username"] = _terminalUsername, + ["terminal_password"] = _terminalPassword, + ["amount"] = amount, + ["name"] = payerName, + ["mobile"] = request.Mobile ?? string.Empty, + ["invoice"] = string.IsNullOrWhiteSpace(request.Invoice) + ? request.UserId.ToString() + : request.Invoice, + ["callback"] = request.CallbackUrl, + ["description"] = request.Description + }; + + _logger.LogInformation( + "TwentyCard payment request: Amount={Amount} Toman, User={UserId}, Invoice={Invoice}", + amount, request.UserId, body["invoice"]); + + using var content = new StringContent( + JsonSerializer.Serialize(body, JsonOptions), Encoding.UTF8, "application/json"); + + var response = await _httpClient.PostAsync("api/pgw/merchant/request", content, cancellationToken); + var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); + + if (!response.IsSuccessStatusCode) + { + _logger.LogError( + "TwentyCard HTTP error on request: StatusCode={StatusCode}, Body={Body}", + (int)response.StatusCode, responseBody); + + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = $"خطای ارتباط با درگاه ۲۰کارت (HTTP {(int)response.StatusCode})" + }; + } + + var result = JsonSerializer.Deserialize(responseBody, JsonOptions); + var authority = result?.Data?.Authority; + var redirectUrl = result?.Data?.RedirectUrl; + + if (string.IsNullOrWhiteSpace(authority) || string.IsNullOrWhiteSpace(redirectUrl)) + { + _logger.LogError("TwentyCard request failed. Body={Body}", responseBody); + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = result?.Message ?? "ایجاد تراکنش ۲۰کارت ناموفق بود" + }; + } + + return new PaymentInitiateResult + { + IsSuccess = true, + RefId = authority, + GatewayUrl = redirectUrl + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "TwentyCard InitiatePayment failed"); + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = "خطا در ارتباط با درگاه ۲۰کارت" + }; + } + } + + public Task VerifyPaymentAsync( + string refId, + string verificationToken, + CancellationToken cancellationToken = default) + => throw new NotImplementedException("TwentyCard requires VerifyPaymentAsync with amount."); + + public async Task VerifyPaymentAsync( + string refId, + string verificationToken, + decimal amountInToman, + CancellationToken cancellationToken = default) + { + try + { + if (!string.Equals(verificationToken, "OK", StringComparison.OrdinalIgnoreCase)) + { + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = "پرداخت توسط کاربر لغو شد یا ناموفق بود", + VerificationCode = 0 + }; + } + + var body = new Dictionary + { + ["terminal_id"] = _terminalId, + ["terminal_username"] = _terminalUsername, + ["terminal_password"] = _terminalPassword, + ["authority"] = refId, + ["amount"] = (long)amountInToman + }; + + using var content = new StringContent( + JsonSerializer.Serialize(body, JsonOptions), Encoding.UTF8, "application/json"); + + var response = await _httpClient.PostAsync("api/pgw/merchant/verify", content, cancellationToken); + var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); + + _logger.LogInformation( + "TwentyCard verify response: Authority={Authority}, StatusCode={StatusCode}, Body={Body}", + refId, (int)response.StatusCode, responseBody); + + var result = JsonSerializer.Deserialize(responseBody, JsonOptions); + + var paid = result?.Status?.Paid == true; + var verified = result?.Status?.Verified == true; + var noError = result?.Error != true; + + if (response.IsSuccessStatusCode && noError && paid && verified) + { + return new PaymentVerificationResult + { + IsSuccess = true, + RefId = refId, + TrackingCode = refId, + Message = result?.Message ?? "تراکنش با موفقیت تایید شد", + VerificationCode = result?.Code ?? 200 + }; + } + + // Code 103 = already verified — treat as success for idempotency + if (result?.Code == 103) + { + return new PaymentVerificationResult + { + IsSuccess = true, + RefId = refId, + TrackingCode = refId, + Message = result.Message ?? "تراکنش قبلاً تایید شده است", + VerificationCode = 103 + }; + } + + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = result?.Message ?? "تایید تراکنش ۲۰کارت ناموفق بود", + VerificationCode = result?.Code ?? (int)response.StatusCode + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "TwentyCard VerifyPayment failed. Authority={Authority}", refId); + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = "خطا در تایید تراکنش ۲۰کارت" + }; + } + } + + public Task ProcessPayoutAsync( + PayoutRequest request, + CancellationToken cancellationToken = default) + { + return Task.FromResult(new PayoutResult + { + IsSuccess = false, + Message = "واریز از طریق درگاه ۲۰کارت پشتیبانی نمی‌شود" + }); + } + + private sealed class TwentyCardRequestResponse + { + public string? Message { get; set; } + public TwentyCardRequestData? Data { get; set; } + } + + private sealed class TwentyCardRequestData + { + public string? Authority { get; set; } + public string? RedirectUrl { get; set; } + } + + private sealed class TwentyCardVerifyResponse + { + public bool Error { get; set; } + public int Code { get; set; } + public string? Message { get; set; } + public TwentyCardVerifyStatus? Status { get; set; } + } + + private sealed class TwentyCardVerifyStatus + { + public bool Paid { get; set; } + public bool Verified { get; set; } + } +} diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index 79ca2d5..b3fd16c 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.211 + 0.0.212 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/package.proto b/src/CMSMicroservice.Protobuf/Protos/package.proto index 3b88874..3acbd26 100644 --- a/src/CMSMicroservice.Protobuf/Protos/package.proto +++ b/src/CMSMicroservice.Protobuf/Protos/package.proto @@ -249,6 +249,8 @@ message CustomerPurchasePackageRequest int64 package_id = 1; PurchaseMethodEnum purchase_method = 2; string callback_url = 3; + // "zarinpal" (default) | "20card" + string gateway_provider = 4; } message CustomerPurchasePackageResponse diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index 89b333f..2c2410d 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -32,7 +32,7 @@ public class PackageService : PackageContract.PackageContractBase private readonly ISender _sender; private readonly IApplicationDbContext _context; private readonly ICurrentUserService _currentUserService; - private readonly IPaymentGatewayService _paymentGateway; + private readonly IPaymentGatewayResolver _paymentGatewayResolver; private readonly IConfiguration _configuration; private readonly IUserPaymentLock _paymentLock; @@ -41,7 +41,7 @@ public class PackageService : PackageContract.PackageContractBase ISender sender, IApplicationDbContext context, ICurrentUserService currentUserService, - IPaymentGatewayService paymentGateway, + IPaymentGatewayResolver paymentGatewayResolver, IConfiguration configuration, IUserPaymentLock paymentLock) { @@ -49,7 +49,7 @@ public class PackageService : PackageContract.PackageContractBase _sender = sender; _context = context; _currentUserService = currentUserService; - _paymentGateway = paymentGateway; + _paymentGatewayResolver = paymentGatewayResolver; _configuration = configuration; _paymentLock = paymentLock; } @@ -171,19 +171,38 @@ public class PackageService : PackageContract.PackageContractBase if (package == null) throw new RpcException(new Status(StatusCode.NotFound, "پکیج مورد نظر یافت نشد")); - - // Create transaction + + var gatewayProvider = NormalizeGatewayProvider(request.GatewayProvider); + var isTwentyCard = gatewayProvider == "20card"; + + long gatewayAmount = package.Price; + if (isTwentyCard) + { + var packageKey = $"TwentyCard:Packages:{package.Id}"; + gatewayAmount = _configuration.GetValue($"{packageKey}:GatewayAmountToman") ?? 0; + var walletCredit = _configuration.GetValue($"{packageKey}:WalletCreditToman") ?? 0; + if (gatewayAmount <= 0 || walletCredit <= 0) + { + throw new RpcException(new Status( + StatusCode.FailedPrecondition, + "پرداخت ۲۰کارت برای این پکیج پیکربندی نشده است")); + } + } + + // Create transaction — 20card: Amount = gateway charge; zarinpal: Package.Price var transaction = new CMSMicroservice.Domain.Entities.Transaction { - Amount = package.Price, - Description = $"خرید پکیج {package.Title}", + Amount = gatewayAmount, + Description = isTwentyCard + ? $"خرید پکیج {package.Title} (۲۰کارت)" + : $"خرید پکیج {package.Title}", PaymentStatus = Domain.Enums.PaymentStatus.Pending, Type = Domain.Enums.TransactionType.Buy }; _context.Transactions.Add(transaction); await _context.SaveChangesAsync(cancellationToken); - // Create purchase record + // Create purchase record — UPP.Amount always Package.Price (catalog) var purchaseMethod = request.PurchaseMethod == PurchaseMethodEnum.PurchaseMethodGateway ? Domain.Enums.PackagePurchaseMethod.DirectPurchase : Domain.Enums.PackagePurchaseMethod.DayaLoan; @@ -204,18 +223,23 @@ public class PackageService : PackageContract.PackageContractBase var user = await _context.Users .AsNoTracking() .Where(u => u.Id == userId) - .Select(u => new { u.Mobile }) + .Select(u => new { u.Mobile, u.FirstName, u.LastName }) .FirstOrDefaultAsync(cancellationToken); // Callback URL از config — نه از ورودی کاربر (امنیت) var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"; var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback?orderId={purchase.Id}"; + + var paymentGateway = _paymentGatewayResolver.Get(gatewayProvider); + var payerName = $"{user?.FirstName} {user?.LastName}".Trim(); - var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest + var paymentResult = await paymentGateway.InitiatePaymentAsync(new PaymentRequest { - Amount = package.Price, + Amount = gatewayAmount, UserId = userId, Mobile = user?.Mobile ?? string.Empty, + Name = payerName, + Invoice = purchase.Id.ToString(), Description = $"خرید پکیج {package.Title}", CallbackUrl = callbackUrl }, cancellationToken); @@ -235,12 +259,14 @@ public class PackageService : PackageContract.PackageContractBase // Save RefId transaction.RefId = paymentResult.RefId; - // ثبت PaymentTransaction + // ثبت PaymentTransaction — Amount = gateway amount var paymentTx = new PaymentTransaction { - GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal", - MerchantId = _configuration["ZarinPal:MerchantId"] ?? "", - Amount = package.Price, + GatewayProvider = gatewayProvider, + MerchantId = isTwentyCard + ? (_configuration["TwentyCard:TerminalId"] ?? "") + : (_configuration["ZarinPal:MerchantId"] ?? ""), + Amount = gatewayAmount, CallbackUrl = callbackUrl, Description = $"خرید پکیج {package.Title}", Mobile = user?.Mobile, @@ -264,6 +290,14 @@ public class PackageService : PackageContract.PackageContractBase Authority = paymentResult.RefId ?? string.Empty }; } + + private static string NormalizeGatewayProvider(string? provider) + { + if (string.IsNullOrWhiteSpace(provider)) + return "zarinpal"; + var key = provider.Trim().ToLowerInvariant(); + return key is "20card" or "twentycard" ? "20card" : "zarinpal"; + } public override async Task CustomerVerifyPackagePurchase( CustomerVerifyPackagePurchaseRequest request, diff --git a/src/CMSMicroservice.WebApi/appsettings.Staging.json b/src/CMSMicroservice.WebApi/appsettings.Staging.json index ba9c060..209616a 100644 --- a/src/CMSMicroservice.WebApi/appsettings.Staging.json +++ b/src/CMSMicroservice.WebApi/appsettings.Staging.json @@ -4,6 +4,23 @@ "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", "UseSandbox": true }, + "TwentyCard": { + "BaseUrl": "https://sandbox.20card.ir/ipg", + "TerminalId": "", + "TerminalUsername": "", + "TerminalPassword": "", + "CreditPortalUrl": "https://20card.ir", + "Packages": { + "4": { + "GatewayAmountToman": 9700000, + "WalletCreditToman": 13000000 + }, + "1": { + "GatewayAmountToman": 97000000, + "WalletCreditToman": 130000000 + } + } + }, "CmsBaseUrl": "https://cms.se.kbs1.ir", "FrontOfficeBaseUrl": "https://frontoffice.se.kbs1.ir", "FMS": { diff --git a/src/CMSMicroservice.WebApi/appsettings.json b/src/CMSMicroservice.WebApi/appsettings.json index 006a80b..8be1e8b 100644 --- a/src/CMSMicroservice.WebApi/appsettings.json +++ b/src/CMSMicroservice.WebApi/appsettings.json @@ -4,6 +4,23 @@ "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", "UseSandbox": true }, + "TwentyCard": { + "BaseUrl": "https://sandbox.20card.ir/ipg", + "TerminalId": "", + "TerminalUsername": "", + "TerminalPassword": "", + "CreditPortalUrl": "https://20card.ir", + "Packages": { + "4": { + "GatewayAmountToman": 9700000, + "WalletCreditToman": 13000000 + }, + "1": { + "GatewayAmountToman": 97000000, + "WalletCreditToman": 130000000 + } + } + }, "CmsBaseUrl": "https://localhost:32846", "FrontOfficeBaseUrl": "http://localhost:5268", "FMS": { From f31fb5ee3972b4d572abc7705a8cc89a9a595da8 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sun, 20 Sep 2026 22:30:39 +0330 Subject: [PATCH 2/8] chore(config): update TwentyCard payment configuration for production and staging environments - Changed BaseUrl for TwentyCard from sandbox to production gateway. - Added TerminalId, TerminalUsername, and TerminalPassword for TwentyCard payment processing in both appsettings.json and appsettings.Staging.json. --- src/CMSMicroservice.WebApi/appsettings.Staging.json | 8 ++++---- src/CMSMicroservice.WebApi/appsettings.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/CMSMicroservice.WebApi/appsettings.Staging.json b/src/CMSMicroservice.WebApi/appsettings.Staging.json index 209616a..3c7c05c 100644 --- a/src/CMSMicroservice.WebApi/appsettings.Staging.json +++ b/src/CMSMicroservice.WebApi/appsettings.Staging.json @@ -5,10 +5,10 @@ "UseSandbox": true }, "TwentyCard": { - "BaseUrl": "https://sandbox.20card.ir/ipg", - "TerminalId": "", - "TerminalUsername": "", - "TerminalPassword": "", + "BaseUrl": "https://gateway.20card.ir", + "TerminalId": "165674", + "TerminalUsername": "ea5y1biehi", + "TerminalPassword": "ea5y1biehi", "CreditPortalUrl": "https://20card.ir", "Packages": { "4": { diff --git a/src/CMSMicroservice.WebApi/appsettings.json b/src/CMSMicroservice.WebApi/appsettings.json index 8be1e8b..6b3d8e0 100644 --- a/src/CMSMicroservice.WebApi/appsettings.json +++ b/src/CMSMicroservice.WebApi/appsettings.json @@ -5,10 +5,10 @@ "UseSandbox": true }, "TwentyCard": { - "BaseUrl": "https://sandbox.20card.ir/ipg", - "TerminalId": "", - "TerminalUsername": "", - "TerminalPassword": "", + "BaseUrl": "https://gateway.20card.ir", + "TerminalId": "165674", + "TerminalUsername": "ea5y1biehi", + "TerminalPassword": "ea5y1biehi", "CreditPortalUrl": "https://20card.ir", "Packages": { "4": { From 2b958bd21419743da78f53c032fef995a90f1d7e Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sun, 20 Sep 2026 23:33:03 +0330 Subject: [PATCH 3/8] feat(user): add user invitation functionality and enhance OTP verification - Introduced UserInvitation entity and updated IApplicationDbContext to include UserInvitations DbSet. - Enhanced VerifyOtpTokenCommand and its handler to support optional inviter mobile number for user registration. - Added new Protobuf definitions for GetMyInvitees and GetInvitationsByFilter RPCs to manage user invitations. - Updated UserService to implement methods for retrieving invitees and invitations based on filters. - Bumped Protobuf project version to reflect the addition of new features. --- .../Interfaces/IApplicationDbContext.cs | 1 + .../VerifyOtpToken/VerifyOtpTokenCommand.cs | 2 + .../VerifyOtpTokenCommandHandler.cs | 37 + .../GetInvitationsByFilterQuery.cs | 46 + .../GetInvitationsByFilterQueryHandler.cs | 96 + .../GetMyInvitees/GetMyInviteesQuery.cs | 36 + .../GetMyInviteesQueryHandler.cs | 98 + .../Entities/UserInvitation.cs | 18 + .../Persistence/ApplicationDbContext.cs | 1 + .../Configurations/UserConfiguration.cs | 7 +- .../UserInvitationConfiguration.cs | 38 + ...60920194955_AddUserInvitations.Designer.cs | 5269 +++++++++++++++++ .../20260920194955_AddUserInvitations.cs | 103 + .../ApplicationDbContextModelSnapshot.cs | 67 +- .../CMSMicroservice.Protobuf.csproj | 2 +- .../Protos/user.proto | 87 + .../Services/UserService.cs | 114 + 17 files changed, 6019 insertions(+), 3 deletions(-) create mode 100644 src/CMSMicroservice.Application/UserCQ/Queries/GetInvitationsByFilter/GetInvitationsByFilterQuery.cs create mode 100644 src/CMSMicroservice.Application/UserCQ/Queries/GetInvitationsByFilter/GetInvitationsByFilterQueryHandler.cs create mode 100644 src/CMSMicroservice.Application/UserCQ/Queries/GetMyInvitees/GetMyInviteesQuery.cs create mode 100644 src/CMSMicroservice.Application/UserCQ/Queries/GetMyInvitees/GetMyInviteesQueryHandler.cs create mode 100644 src/CMSMicroservice.Domain/Entities/UserInvitation.cs create mode 100644 src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserInvitationConfiguration.cs create mode 100644 src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260920194955_AddUserInvitations.Designer.cs create mode 100644 src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260920194955_AddUserInvitations.cs diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs index 3c6ca1b..6184676 100644 --- a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs +++ b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs @@ -21,6 +21,7 @@ public interface IApplicationDbContext DbSet Products { get; } DbSet ProductImages { get; } DbSet Users { get; } + DbSet UserInvitations { get; } DbSet OtpTokens { get; } DbSet Contracts { get; } DbSet UserContracts { get; } diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommand.cs b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommand.cs index 594d105..9aaf337 100644 --- a/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommand.cs +++ b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommand.cs @@ -9,5 +9,7 @@ public record VerifyOtpTokenCommand : IRequest public string Code { get; init; } //کد معرف والد public string? ParentReferralCode { get; init; } + //شماره موبایل دعوت‌کننده (اختیاری) + public string? InviterMobile { get; init; } } \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs index 5cd143c..41ce802 100644 --- a/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs +++ b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs @@ -118,6 +118,34 @@ public class VerifyOtpTokenCommandHandler : IRequestHandler u.Mobile == inviterMobile, cancellationToken); + + if (inviter == null) + { + return new VerifyOtpTokenResponseDto + { + Success = false, + Message = "دعوت‌کننده با این شماره موبایل یافت نشد." + }; + } + } + // ایجاد کاربر جدید user = new User { @@ -134,6 +162,15 @@ public class VerifyOtpTokenCommandHandler : IRequestHandler +{ + public PaginationState? PaginationState { get; set; } + public string? SortBy { get; set; } + public GetInvitationsByFilterFilterDto? Filter { get; set; } +} + +public class GetInvitationsByFilterFilterDto +{ + public string? InviterMobile { get; set; } + public string? InviterFirstName { get; set; } + public string? InviterLastName { get; set; } + public string? InviteeMobile { get; set; } + public string? InviteeFirstName { get; set; } + public string? InviteeLastName { get; set; } + public bool? InviteeClubActive { get; set; } + public DateTime? CreatedFrom { get; set; } + public DateTime? CreatedTo { get; set; } +} + +public class GetInvitationsByFilterResult +{ + public MetaData MetaData { get; set; } = new(); + public List Models { get; set; } = new(); +} + +public class InvitationReportDto +{ + public long Id { get; set; } + public long InviterUserId { get; set; } + public string? InviterFirstName { get; set; } + public string? InviterLastName { get; set; } + public string InviterMobile { get; set; } = string.Empty; + public long InviteeUserId { get; set; } + public string? InviteeFirstName { get; set; } + public string? InviteeLastName { get; set; } + public string InviteeMobile { get; set; } = string.Empty; + public DateTime InviteeRegisteredAt { get; set; } + public bool InviteeClubActive { get; set; } + public DateTime InvitedAt { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetInvitationsByFilter/GetInvitationsByFilterQueryHandler.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetInvitationsByFilter/GetInvitationsByFilterQueryHandler.cs new file mode 100644 index 0000000..47d0561 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetInvitationsByFilter/GetInvitationsByFilterQueryHandler.cs @@ -0,0 +1,96 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.UserCQ.Queries.GetInvitationsByFilter; + +public class GetInvitationsByFilterQueryHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetInvitationsByFilterQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetInvitationsByFilterQuery request, + CancellationToken cancellationToken) + { + var filter = request.Filter ?? new GetInvitationsByFilterFilterDto(); + + var query = _context.UserInvitations + .AsNoTracking() + .Select(x => new InvitationReportDto + { + Id = x.Id, + InviterUserId = x.InviterUserId, + InviterFirstName = x.Inviter.FirstName, + InviterLastName = x.Inviter.LastName, + InviterMobile = x.Inviter.Mobile, + InviteeUserId = x.InviteeUserId, + InviteeFirstName = x.Invitee.FirstName, + InviteeLastName = x.Invitee.LastName, + InviteeMobile = x.Invitee.Mobile, + InviteeRegisteredAt = x.Invitee.Created, + InviteeClubActive = x.Invitee.ClubMembership != null && x.Invitee.ClubMembership.IsActive, + InvitedAt = x.Created + }); + + if (!string.IsNullOrWhiteSpace(filter.InviterMobile)) + query = query.Where(x => x.InviterMobile.Contains(filter.InviterMobile)); + if (!string.IsNullOrWhiteSpace(filter.InviterFirstName)) + query = query.Where(x => x.InviterFirstName != null && x.InviterFirstName.Contains(filter.InviterFirstName)); + if (!string.IsNullOrWhiteSpace(filter.InviterLastName)) + query = query.Where(x => x.InviterLastName != null && x.InviterLastName.Contains(filter.InviterLastName)); + if (!string.IsNullOrWhiteSpace(filter.InviteeMobile)) + query = query.Where(x => x.InviteeMobile.Contains(filter.InviteeMobile)); + if (!string.IsNullOrWhiteSpace(filter.InviteeFirstName)) + query = query.Where(x => x.InviteeFirstName != null && x.InviteeFirstName.Contains(filter.InviteeFirstName)); + if (!string.IsNullOrWhiteSpace(filter.InviteeLastName)) + query = query.Where(x => x.InviteeLastName != null && x.InviteeLastName.Contains(filter.InviteeLastName)); + if (filter.InviteeClubActive.HasValue) + query = query.Where(x => x.InviteeClubActive == filter.InviteeClubActive.Value); + if (filter.CreatedFrom.HasValue) + query = query.Where(x => x.InvitedAt >= filter.CreatedFrom.Value); + if (filter.CreatedTo.HasValue) + query = query.Where(x => x.InvitedAt <= filter.CreatedTo.Value); + + // پیش‌فرض: جدیدترین + query = (request.SortBy?.Trim().ToLowerInvariant()) switch + { + "oldest" or "created_asc" => query.OrderBy(x => x.InvitedAt), + _ => query.OrderByDescending(x => x.InvitedAt) + }; + + var paginationState = request.PaginationState ?? new PaginationState { PageNumber = 1, PageSize = 20 }; + if (paginationState.PageNumber < 1) paginationState.PageNumber = 1; + if (paginationState.PageSize < 1) paginationState.PageSize = 20; + + var totalCount = await query.CountAsync(cancellationToken); + var models = await query + .Skip((paginationState.PageNumber - 1) * paginationState.PageSize) + .Take(paginationState.PageSize) + .ToListAsync(cancellationToken); + + var totalPage = totalCount == 0 + ? 0 + : (int)Math.Ceiling((double)totalCount / paginationState.PageSize); + + return new GetInvitationsByFilterResult + { + MetaData = new MetaData + { + TotalCount = totalCount, + CurrentPage = paginationState.PageNumber, + PageSize = paginationState.PageSize, + TotalPage = totalPage, + HasPrevious = paginationState.PageNumber > 1, + HasNext = paginationState.PageNumber < totalPage + }, + Models = models + }; + } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetMyInvitees/GetMyInviteesQuery.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetMyInvitees/GetMyInviteesQuery.cs new file mode 100644 index 0000000..9724111 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetMyInvitees/GetMyInviteesQuery.cs @@ -0,0 +1,36 @@ +using CMSMicroservice.Application.Common.Models; +using MediatR; + +namespace CMSMicroservice.Application.UserCQ.Queries.GetMyInvitees; + +public class GetMyInviteesQuery : IRequest +{ + public long UserId { get; set; } + public PaginationState? PaginationState { get; set; } + /// ALL | ACTIVE | INACTIVE — ClubMembership.IsActive + public string? ClubStatusFilter { get; set; } +} + +public class GetMyInviteesResult +{ + public MetaData MetaData { get; set; } = new(); + public List Invitees { get; set; } = new(); + public InviteeStatsDto Stats { get; set; } = new(); +} + +public class InviteeDto +{ + public long UserId { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string Mobile { get; set; } = string.Empty; + public DateTime RegisteredAt { get; set; } + public bool IsClubActive { get; set; } +} + +public class InviteeStatsDto +{ + public int TotalInvitees { get; set; } + public int ClubActiveCount { get; set; } + public int ClubInactiveCount { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Queries/GetMyInvitees/GetMyInviteesQueryHandler.cs b/src/CMSMicroservice.Application/UserCQ/Queries/GetMyInvitees/GetMyInviteesQueryHandler.cs new file mode 100644 index 0000000..a638ff0 --- /dev/null +++ b/src/CMSMicroservice.Application/UserCQ/Queries/GetMyInvitees/GetMyInviteesQueryHandler.cs @@ -0,0 +1,98 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.UserCQ.Queries.GetMyInvitees; + +public class GetMyInviteesQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + + public GetMyInviteesQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser) + { + _context = context; + _currentUser = currentUser; + } + + public async Task Handle(GetMyInviteesQuery request, CancellationToken cancellationToken) + { + var userId = request.UserId == 0 + ? (long.TryParse(_currentUser.UserId, out var id) ? id : 0) + : request.UserId; + + if (userId == 0) + throw new UnauthorizedAccessException("User ID not found in JWT token"); + + var baseQuery = _context.UserInvitations + .AsNoTracking() + .Where(x => x.InviterUserId == userId) + .Select(x => new + { + x.InviteeUserId, + InviteeFirstName = x.Invitee.FirstName, + InviteeLastName = x.Invitee.LastName, + InviteeMobile = x.Invitee.Mobile, + RegisteredAt = x.Invitee.Created, + IsClubActive = x.Invitee.ClubMembership != null && x.Invitee.ClubMembership.IsActive, + InvitedAt = x.Created + }); + + var all = await baseQuery.ToListAsync(cancellationToken); + var total = all.Count; + var clubActive = all.Count(x => x.IsClubActive); + var clubInactive = total - clubActive; + + var filtered = all.AsEnumerable(); + var status = request.ClubStatusFilter?.Trim().ToUpperInvariant(); + if (status == "ACTIVE") + filtered = filtered.Where(x => x.IsClubActive); + else if (status == "INACTIVE") + filtered = filtered.Where(x => !x.IsClubActive); + + var ordered = filtered.OrderByDescending(x => x.InvitedAt).ToList(); + + var paginationState = request.PaginationState ?? new PaginationState { PageNumber = 1, PageSize = 10 }; + if (paginationState.PageNumber < 1) paginationState.PageNumber = 1; + if (paginationState.PageSize < 1) paginationState.PageSize = 10; + + var totalCount = ordered.Count; + var pageItems = ordered + .Skip((paginationState.PageNumber - 1) * paginationState.PageSize) + .Take(paginationState.PageSize) + .ToList(); + + var totalPage = totalCount == 0 + ? 0 + : (int)Math.Ceiling((double)totalCount / paginationState.PageSize); + + return new GetMyInviteesResult + { + MetaData = new MetaData + { + TotalCount = totalCount, + CurrentPage = paginationState.PageNumber, + PageSize = paginationState.PageSize, + TotalPage = totalPage, + HasPrevious = paginationState.PageNumber > 1, + HasNext = paginationState.PageNumber < totalPage + }, + Stats = new InviteeStatsDto + { + TotalInvitees = total, + ClubActiveCount = clubActive, + ClubInactiveCount = clubInactive + }, + Invitees = pageItems.Select(x => new InviteeDto + { + UserId = x.InviteeUserId, + FirstName = x.InviteeFirstName, + LastName = x.InviteeLastName, + Mobile = x.InviteeMobile, + RegisteredAt = x.RegisteredAt, + IsClubActive = x.IsClubActive + }).ToList() + }; + } +} diff --git a/src/CMSMicroservice.Domain/Entities/UserInvitation.cs b/src/CMSMicroservice.Domain/Entities/UserInvitation.cs new file mode 100644 index 0000000..a6875b5 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/UserInvitation.cs @@ -0,0 +1,18 @@ +namespace CMSMicroservice.Domain.Entities; + +/// +/// رابطه دعوت‌کننده ↔ دعوت‌شونده (جدا از معرف شبکه / NetworkParent). +/// کد دعوت = شماره موبایل دعوت‌کننده. +/// +public class UserInvitation : BaseAuditableEntity +{ + /// کاربری که دعوت کرده + public long InviterUserId { get; set; } + + public virtual User Inviter { get; set; } = null!; + + /// کاربری که دعوت شده (حداکثر یک دعوت‌کننده) + public long InviteeUserId { get; set; } + + public virtual User Invitee { get; set; } = null!; +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs index 4216ee8..d206b72 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs @@ -86,6 +86,7 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext public DbSet Products => Set(); public DbSet ProductImages => Set(); public DbSet Users => Set(); + public DbSet UserInvitations => Set(); public DbSet OtpTokens => Set(); public DbSet Contracts => Set(); public DbSet UserContracts => Set(); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserConfiguration.cs index f1710e5..a310ed4 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserConfiguration.cs @@ -13,7 +13,7 @@ public class UserConfiguration : IEntityTypeConfiguration builder.Property(entity => entity.Id).UseIdentityColumn(); builder.Property(entity => entity.FirstName).IsRequired(false); builder.Property(entity => entity.LastName).IsRequired(false); - builder.Property(entity => entity.Mobile).IsRequired(true); + builder.Property(entity => entity.Mobile).IsRequired(true).HasMaxLength(20); builder.Property(entity => entity.NationalCode).IsRequired(false); builder.Property(entity => entity.AvatarPath).IsRequired(false); builder.Property(entity => entity.ReferralCode).IsRequired(true); @@ -48,5 +48,10 @@ public class UserConfiguration : IEntityTypeConfiguration builder.HasIndex(e => e.LegPosition) .HasDatabaseName("IX_User_LegPosition"); + // موبایل یکتا برای کاربران فعال (lookup دعوت‌کننده) + builder.HasIndex(e => e.Mobile) + .IsUnique() + .HasFilter("[IsDeleted] = 0") + .HasDatabaseName("IX_User_Mobile_Active"); } } diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserInvitationConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserInvitationConfiguration.cs new file mode 100644 index 0000000..34a3945 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/UserInvitationConfiguration.cs @@ -0,0 +1,38 @@ +using CMSMicroservice.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations; + +public class UserInvitationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("UserInvitations"); + builder.HasQueryFilter(p => !p.IsDeleted); + builder.Ignore(entity => entity.DomainEvents); + + builder.HasKey(entity => entity.Id); + builder.Property(entity => entity.Id).UseIdentityColumn(); + + builder.Property(entity => entity.InviterUserId).IsRequired(); + builder.Property(entity => entity.InviteeUserId).IsRequired(); + + builder.HasOne(entity => entity.Inviter) + .WithMany() + .HasForeignKey(entity => entity.InviterUserId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(entity => entity.Invitee) + .WithMany() + .HasForeignKey(entity => entity.InviteeUserId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasIndex(e => e.InviteeUserId) + .IsUnique() + .HasDatabaseName("IX_UserInvitations_InviteeUserId"); + + builder.HasIndex(e => new { e.InviterUserId, e.Created }) + .HasDatabaseName("IX_UserInvitations_InviterUserId_Created"); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260920194955_AddUserInvitations.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260920194955_AddUserInvitations.Designer.cs new file mode 100644 index 0000000..07765c0 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260920194955_AddUserInvitations.Designer.cs @@ -0,0 +1,5269 @@ +// +using System; +using CMSMicroservice.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260920194955_AddUserInvitations")] + partial class AddUserInvitations + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("CMS") + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_BlogCategories_IsActive"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("IX_BlogCategories_Slug"); + + b.ToTable("BlogCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AuthorUserId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FeaturedImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("FeaturedImageThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsFeatured") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PublishedAt") + .HasColumnType("datetime2"); + + b.Property("ScheduledPublishAt") + .HasColumnType("datetime2"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Summary") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ViewCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("AuthorUserId") + .HasDatabaseName("IX_BlogPosts_AuthorUserId"); + + b.HasIndex("IsFeatured") + .HasDatabaseName("IX_BlogPosts_IsFeatured"); + + b.HasIndex("PublishedAt") + .HasDatabaseName("IX_BlogPosts_PublishedAt"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("IX_BlogPosts_Slug"); + + b.HasIndex("Status") + .HasDatabaseName("IX_BlogPosts_Status"); + + b.HasIndex("Status", "PublishedAt") + .HasDatabaseName("IX_BlogPosts_Status_PublishedAt"); + + b.ToTable("BlogPosts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlogCategoryId") + .HasColumnType("bigint"); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BlogCategoryId"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AltText") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.Property("Caption") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.Property("TagId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BlogPostId"); + + b.HasIndex("TagId"); + + b.ToTable("BlogPostTags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("Categories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive", "SortOrder") + .HasDatabaseName("IX_ClubFeature_IsActive_SortOrder"); + + b.ToTable("ClubFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActivatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstActivationDate") + .HasColumnType("datetime2"); + + b.Property("FirstPackageId") + .HasColumnType("bigint"); + + b.Property("GiftValue") + .HasColumnType("bigint"); + + b.Property("InitialContribution") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastActivationDate") + .HasColumnType("datetime2"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastPackageId") + .HasColumnType("bigint"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("TotalEarned") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("FirstPackageId"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_ClubMembership_IsActive"); + + b.HasIndex("LastActivationDate") + .HasDatabaseName("IX_ClubMembership_LastActivationDate"); + + b.HasIndex("LastPackageId"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("IX_ClubMembership_UserId"); + + b.ToTable("ClubMemberships", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembershipCycle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CycleNumber") + .HasColumnType("int"); + + b.Property("IsCurrentCycle") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MagicCompletedAt") + .HasColumnType("datetime2"); + + b.Property("MagicStartedAt") + .HasColumnType("datetime2"); + + b.Property("PackageAmount") + .HasColumnType("bigint"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PackagePurchasedAt") + .HasColumnType("datetime2"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClubMembershipId"); + + b.HasIndex("PackageId"); + + b.HasIndex("PackagePurchasedAt") + .HasDatabaseName("IX_ClubMembershipCycle_PackagePurchasedAt"); + + b.HasIndex("UserId", "IsCurrentCycle") + .HasDatabaseName("IX_ClubMembershipCycle_UserId_IsCurrentCycle"); + + b.ToTable("ClubMembershipCycles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.UserClubFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubFeatureId") + .HasColumnType("bigint"); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClubFeatureId"); + + b.HasIndex("ClubMembershipId") + .HasDatabaseName("IX_UserClubFeature_ClubMembershipId"); + + b.HasIndex("UserId", "ClubFeatureId") + .IsUnique() + .HasDatabaseName("IX_UserClubFeature_UserId_ClubFeatureId"); + + b.ToTable("UserClubFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BalancesEarned") + .HasColumnType("int"); + + b.Property("BankReferenceId") + .HasColumnType("nvarchar(max)"); + + b.Property("BankTrackingCode") + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IbanNumber") + .HasMaxLength(26) + .HasColumnType("nvarchar(26)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PaidAt") + .HasColumnType("datetime2"); + + b.Property("PaymentFailureReason") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedAt") + .HasColumnType("datetime2"); + + b.Property("ProcessedBy") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RejectionReason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("ValuePerBalance") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.Property("WeeklyPoolId") + .HasColumnType("bigint"); + + b.Property("WithdrawalMethod") + .HasColumnType("int"); + + b.Property("WithdrawnAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("PackageId"); + + b.HasIndex("Status") + .HasDatabaseName("IX_UserCommissionPayout_Status"); + + b.HasIndex("WeekDefinitionId"); + + b.HasIndex("WeeklyPoolId") + .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); + + b.HasIndex("UserId", "WeekDefinitionId", "PackageId") + .IsUnique() + .HasDatabaseName("IX_UserCommissionPayout_User_WeekDef_Package"); + + b.ToTable("UserCommissionPayouts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CalculatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsCalculated") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("TotalBalances") + .HasColumnType("int"); + + b.Property("TotalPoolAmount") + .HasColumnType("bigint"); + + b.Property("ValuePerBalance") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsCalculated") + .HasDatabaseName("IX_WeeklyCommissionPool_IsCalculated"); + + b.HasIndex("PackageId"); + + b.HasIndex("WeekDefinitionId", "PackageId") + .IsUnique() + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDef_Package"); + + b.ToTable("WeeklyCommissionPools", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WorkerExecutionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Details") + .HasColumnType("nvarchar(max)"); + + b.Property("DurationMs") + .HasColumnType("bigint"); + + b.Property("ErrorCount") + .HasColumnType("int"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ErrorStackTrace") + .HasColumnType("nvarchar(max)"); + + b.Property("ExecutionId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedCount") + .HasColumnType("int"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("StartedAt"); + + b.HasIndex("Status"); + + b.HasIndex("WeekDefinitionId"); + + b.ToTable("WorkerExecutionLogs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Configuration.AppVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AppName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CurrentVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MinRequiredVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("RequiresFullCacheClear") + .HasColumnType("bit"); + + b.Property("UpdateMessage") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("AppVersions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroSubtitle") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HeroTitle") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MetaDescription") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("PageKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("PageKey") + .IsUnique() + .HasDatabaseName("IX_SitePages_PageKey"); + + b.ToTable("SitePages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ImageGroup") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LinkUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("SitePageSettingsId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Subtitle") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("ThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("SitePageSettingsId", "ImageGroup") + .HasDatabaseName("IX_SitePageImages_SettingsId_Group"); + + b.ToTable("SitePageImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExtraData") + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .HasColumnType("nvarchar(max)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("ImageThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SectionKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SitePageId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Subtitle") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("SitePageId", "SectionKey") + .HasDatabaseName("IX_SitePageSections_PageId_SectionKey"); + + b.ToTable("SitePageSections", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroSubtitle") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HeroTitle") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MetaDescription") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("PageKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SettingsJson") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("PageKey") + .IsUnique() + .HasDatabaseName("IX_SitePageSettings_PageKey"); + + b.ToTable("SitePageSettings", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Contracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContractNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsProcessed") + .HasColumnType("bit"); + + b.Property("LastCheckDate") + .HasColumnType("datetime2"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NationalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.ToTable("DayaLoanContracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentCategoryId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ParentCategoryId"); + + b.ToTable("DiscountCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryDescription") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("DeliveryStatus") + .HasColumnType("int"); + + b.Property("DiscountBalanceUsed") + .HasColumnType("bigint"); + + b.Property("GatewayAmountPaid") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("TrackingCode") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserAddressId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("VatAmount") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("DiscountOrders", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrderDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountAmount") + .HasColumnType("bigint"); + + b.Property("DiscountOrderId") + .HasColumnType("bigint"); + + b.Property("DiscountPercentUsed") + .HasColumnType("int"); + + b.Property("FinalPrice") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DiscountOrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("DiscountOrderDetails", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FullInformation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxDiscountPercent") + .HasColumnType("int"); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("Rate") + .HasColumnType("int"); + + b.Property("RemainingCount") + .HasColumnType("int"); + + b.Property("SaleCount") + .HasColumnType("int"); + + b.Property("ShortInfomation") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ViewCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("DiscountProducts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId", "CategoryId") + .IsUnique(); + + b.ToTable("DiscountProductCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AltText") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountProductId") + .HasColumnType("bigint"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("ThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("DiscountProductId"); + + b.HasIndex("DiscountProductId", "SortOrder"); + + b.ToTable("DiscountProductImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountShoppingCart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId", "ProductId") + .IsUnique(); + + b.ToTable("DiscountShoppingCarts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.FactorDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsChangePrice") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UnitDiscount") + .HasColumnType("int"); + + b.Property("UnitDiscountPrice") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("FactorDetails", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.City", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("StateId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("StateId") + .HasDatabaseName("IX_Cities_StateId"); + + b.ToTable("Cities", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Capital") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("CurrencyName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CurrencySymbol") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Emoji") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("EmojiU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Iso2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)"); + + b.Property("Iso3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("NumericCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("PhoneCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Region") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Subregion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Tld") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.HasKey("Id"); + + b.ToTable("Countries", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CountryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("StateCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId") + .HasDatabaseName("IX_States_CountryId"); + + b.ToTable("States", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipCycleHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("ClubMembershipCycleId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CycleNumber") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewIsCurrentCycle") + .HasColumnType("bit"); + + b.Property("NewMagicCompletedAt") + .HasColumnType("datetime2"); + + b.Property("NewMagicStartedAt") + .HasColumnType("datetime2"); + + b.Property("OldIsCurrentCycle") + .HasColumnType("bit"); + + b.Property("OldMagicCompletedAt") + .HasColumnType("datetime2"); + + b.Property("OldMagicStartedAt") + .HasColumnType("datetime2"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_ClubMembershipCycleHistory_Action"); + + b.HasIndex("ClubMembershipCycleId") + .HasDatabaseName("IX_ClubMembershipCycleHistory_CycleId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_ClubMembershipCycleHistory_UserId_Created"); + + b.ToTable("ClubMembershipCycleHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewInitialContribution") + .HasColumnType("bigint"); + + b.Property("NewIsActive") + .HasColumnType("bit"); + + b.Property("OldInitialContribution") + .HasColumnType("bigint"); + + b.Property("OldIsActive") + .HasColumnType("bit"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_ClubMembershipHistory_Action"); + + b.HasIndex("ClubMembershipId") + .HasDatabaseName("IX_ClubMembershipHistory_ClubMembershipId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_ClubMembershipHistory_UserId_Created"); + + b.ToTable("ClubMembershipHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.CommissionPayoutHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("AmountAfter") + .HasColumnType("bigint"); + + b.Property("AmountBefore") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewStatus") + .HasColumnType("int"); + + b.Property("OldStatus") + .HasColumnType("int"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserCommissionPayoutId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_CommissionPayoutHistory_Action"); + + b.HasIndex("UserCommissionPayoutId") + .HasDatabaseName("IX_CommissionPayoutHistory_PayoutId"); + + b.HasIndex("WeekDefinitionId") + .HasDatabaseName("IX_CommissionPayoutHistory_WeekDefinitionId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_CommissionPayoutHistory_UserId_Created"); + + b.ToTable("CommissionPayoutHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.NetworkMembershipHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewLegPosition") + .HasColumnType("int"); + + b.Property("NewParentId") + .HasColumnType("bigint"); + + b.Property("OldLegPosition") + .HasColumnType("int"); + + b.Property("OldParentId") + .HasColumnType("bigint"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_NetworkMembershipHistory_Action"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_NetworkMembershipHistory_UserId_Created"); + + b.ToTable("NetworkMembershipHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.PackageHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewActivationFee") + .HasColumnType("bigint"); + + b.Property("NewIsActive") + .HasColumnType("bit"); + + b.Property("NewMagicMaxDeposit") + .HasColumnType("bigint"); + + b.Property("NewMagicMultiplier") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NewMaxBalancesPerLeg") + .HasColumnType("int"); + + b.Property("NewPrice") + .HasColumnType("bigint"); + + b.Property("OldActivationFee") + .HasColumnType("bigint"); + + b.Property("OldIsActive") + .HasColumnType("bit"); + + b.Property("OldMagicMaxDeposit") + .HasColumnType("bigint"); + + b.Property("OldMagicMultiplier") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("OldMaxBalancesPerLeg") + .HasColumnType("int"); + + b.Property("OldPrice") + .HasColumnType("bigint"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_PackageHistory_Action"); + + b.HasIndex("PackageId", "Created") + .HasDatabaseName("IX_PackageHistory_PackageId_Created"); + + b.ToTable("PackageHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountProductId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastRestockedAt") + .HasColumnType("datetime2"); + + b.Property("LastSoldAt") + .HasColumnType("datetime2"); + + b.Property("LowStockThreshold") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("MaxStockLevel") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1000); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("ProductType") + .HasColumnType("int"); + + b.Property("Quantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ReorderPoint") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(5); + + b.Property("ReservedQuantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(1L); + + b.HasKey("Id"); + + b.HasIndex("DiscountProductId") + .HasDatabaseName("IX_InventoryItems_DiscountProductId"); + + b.HasIndex("ProductId") + .HasDatabaseName("IX_InventoryItems_ProductId"); + + b.HasIndex("WarehouseId") + .HasDatabaseName("IX_InventoryItems_WarehouseId"); + + b.HasIndex("ProductType", "Quantity") + .HasDatabaseName("IX_InventoryItems_ProductType_Quantity"); + + b.ToTable("InventoryItems", "CMS", t => + { + t.HasCheckConstraint("CK_InventoryItem_ProductReference", "(ProductId IS NOT NULL AND DiscountProductId IS NULL) OR (ProductId IS NULL AND DiscountProductId IS NOT NULL)"); + + t.HasCheckConstraint("CK_InventoryItem_ProductType_Match", "(ProductType = 1 AND ProductId IS NOT NULL) OR (ProductType = 2 AND DiscountProductId IS NOT NULL)"); + + t.HasCheckConstraint("CK_InventoryItem_Quantity_NonNegative", "Quantity >= 0"); + + t.HasCheckConstraint("CK_InventoryItem_ReservedQuantity_LessOrEqualQuantity", "ReservedQuantity <= Quantity"); + + t.HasCheckConstraint("CK_InventoryItem_ReservedQuantity_NonNegative", "ReservedQuantity >= 0"); + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CalculatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FlushedPerSide") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsExpired") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LeftLegBalances") + .HasColumnType("int"); + + b.Property("LeftLegCarryover") + .HasColumnType("int"); + + b.Property("LeftLegNewMembers") + .HasColumnType("int"); + + b.Property("LeftLegRemainder") + .HasColumnType("int"); + + b.Property("LeftLegTotal") + .HasColumnType("int"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("RightLegBalances") + .HasColumnType("int"); + + b.Property("RightLegCarryover") + .HasColumnType("int"); + + b.Property("RightLegNewMembers") + .HasColumnType("int"); + + b.Property("RightLegRemainder") + .HasColumnType("int"); + + b.Property("RightLegTotal") + .HasColumnType("int"); + + b.Property("SubordinateBalances") + .HasColumnType("int"); + + b.Property("TotalBalances") + .HasColumnType("int"); + + b.Property("TotalFlushed") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.Property("WeeklyPoolContribution") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsExpired") + .HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired"); + + b.HasIndex("PackageId") + .HasDatabaseName("IX_NetworkWeeklyBalance_PackageId"); + + b.HasIndex("WeekDefinitionId") + .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); + + b.HasIndex("UserId", "WeekDefinitionId", "PackageId") + .IsUnique() + .HasDatabaseName("IX_NetworkWeeklyBalance_User_WeekDef_Package"); + + b.ToTable("NetworkWeeklyBalances", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BaseAmount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsPaid") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PaidAt") + .HasColumnType("datetime2"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("VATAmount") + .HasColumnType("bigint"); + + b.Property("VATRate") + .HasColumnType("decimal(5,4)"); + + b.HasKey("Id"); + + b.HasIndex("Created") + .HasDatabaseName("IX_OrderVATs_Created"); + + b.HasIndex("IsPaid") + .HasDatabaseName("IX_OrderVATs_IsPaid"); + + b.HasIndex("OrderId") + .IsUnique() + .HasDatabaseName("IX_OrderVATs_OrderId"); + + b.ToTable("OrderVATs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.OtpToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Attempts") + .HasColumnType("int"); + + b.Property("CodeHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsUsed") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Mobile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Purpose") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("OtpTokens", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActivationFee") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountMultiplier") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(2.0m); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsBasePackage") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MagicWalletMaxCredit") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(2500000000L); + + b.Property("MagicWalletMaxDeposit") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(1000000000L); + + b.Property("MagicWalletMultiplier") + .ValueGeneratedOnAdd() + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)") + .HasDefaultValue(2.5m); + + b.Property("MaxBalancesPerLeg") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(300); + + b.Property("MaxNetworkLevel") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(15); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("SupportsDayaPurchase") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SupportsDirectPurchase") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_Package_IsActive"); + + b.HasIndex("SortOrder") + .HasDatabaseName("IX_Package_SortOrder"); + + b.ToTable("Packages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PackageFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubFeatureId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsIncluded") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClubFeatureId"); + + b.HasIndex("PackageId", "ClubFeatureId") + .IsUnique() + .HasDatabaseName("IX_PackageFeature_PackageId_ClubFeatureId"); + + b.ToTable("PackageFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApprovedBy") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("ImageDocumentId") + .HasColumnType("bigint"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ReferenceNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("RejectionReason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("RequestedBy") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("Created"); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Status"); + + b.ToTable("ManualPayments", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.PaymentTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Authority") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CallbackUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("CardHash") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CardPan") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("GatewayProvider") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MerchantId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Mobile") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("OrderId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PaymentStatus") + .HasColumnType("bit"); + + b.Property("RefId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RequestStatusCode") + .HasColumnType("int"); + + b.Property("RequestStatusMessage") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("VerificationStatusCode") + .HasColumnType("int"); + + b.Property("VerificationStatusMessage") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("Authority"); + + b.HasIndex("GatewayProvider"); + + b.HasIndex("RefId"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.ToTable("PaymentTransactions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubDiscountPercent") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Discount") + .HasColumnType("int"); + + b.Property("FullInformation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsClubExclusive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("Rate") + .HasColumnType("int"); + + b.Property("RemainingCount") + .HasColumnType("int"); + + b.Property("SaleCount") + .HasColumnType("int"); + + b.Property("ShortInfomation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ViewCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("IsClubExclusive") + .HasDatabaseName("IX_Products_IsClubExclusive"); + + b.ToTable("Products", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId"); + + b.ToTable("ProductCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductGallery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("ProductImageId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("ProductImageId"); + + b.ToTable("ProductGalleries", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ProductImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("TagId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("TagId"); + + b.ToTable("ProductTags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PublicMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ArchivedAt") + .HasColumnType("datetime2"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedByUserId") + .HasColumnType("bigint"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsArchived") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LinkText") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LinkUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("PublishedAt") + .HasColumnType("datetime2"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("ViewCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId") + .HasDatabaseName("IX_PublicMessages_CreatedByUserId"); + + b.HasIndex("ExpiresAt") + .HasDatabaseName("IX_PublicMessages_ExpiresAt"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_PublicMessages_IsActive"); + + b.HasIndex("Priority") + .HasDatabaseName("IX_PublicMessages_Priority"); + + b.HasIndex("StartsAt") + .HasDatabaseName("IX_PublicMessages_StartsAt"); + + b.HasIndex("Type") + .HasDatabaseName("IX_PublicMessages_Type"); + + b.HasIndex("IsActive", "ExpiresAt") + .HasDatabaseName("IX_PublicMessages_IsActive_ExpiresAt"); + + b.ToTable("PublicMessages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Roles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountOrderId") + .HasColumnType("bigint"); + + b.Property("InventoryItemId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MovementType") + .HasColumnType("int"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PerformedByUserId") + .HasColumnType("bigint"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("QuantityAfter") + .HasColumnType("int"); + + b.Property("QuantityBefore") + .HasColumnType("int"); + + b.Property("ReferenceNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Created") + .HasDatabaseName("IX_StockMovements_Created"); + + b.HasIndex("DiscountOrderId") + .HasDatabaseName("IX_StockMovements_DiscountOrderId") + .HasFilter("[DiscountOrderId] IS NOT NULL"); + + b.HasIndex("InventoryItemId") + .HasDatabaseName("IX_StockMovements_InventoryItemId"); + + b.HasIndex("MovementType") + .HasDatabaseName("IX_StockMovements_MovementType"); + + b.HasIndex("OrderId") + .HasDatabaseName("IX_StockMovements_OrderId") + .HasFilter("[OrderId] IS NOT NULL"); + + b.HasIndex("ReferenceNumber") + .HasDatabaseName("IX_StockMovements_ReferenceNumber") + .HasFilter("[ReferenceNumber] IS NOT NULL"); + + b.HasIndex("InventoryItemId", "MovementType", "Created") + .HasDatabaseName("IX_StockMovements_Item_Type_Date"); + + b.ToTable("StockMovements", "CMS", t => + { + t.HasCheckConstraint("CK_StockMovement_QuantityAfter_Calculation", "QuantityAfter = QuantityBefore + Quantity"); + + t.HasCheckConstraint("CK_StockMovement_QuantityAfter_NonNegative", "QuantityAfter >= 0"); + + t.HasCheckConstraint("CK_StockMovement_QuantityBefore_NonNegative", "QuantityBefore >= 0"); + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Tags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("RefId") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Transactions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AvatarPath") + .HasColumnType("nvarchar(max)"); + + b.Property("BirthDate") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DayaCreditReceivedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailNotifications") + .HasColumnType("bit"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("HasReceivedDayaCredit") + .HasColumnType("bit"); + + b.Property("HashPassword") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsMobileVerified") + .HasColumnType("bit"); + + b.Property("IsRulesAccepted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.Property("LegPosition") + .HasColumnType("int"); + + b.Property("Mobile") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("MobileVerifiedAt") + .HasColumnType("datetime2"); + + b.Property("NationalCode") + .HasColumnType("nvarchar(max)"); + + b.Property("NetworkParentId") + .HasColumnType("bigint"); + + b.Property("PackagePurchaseMethod") + .HasColumnType("int"); + + b.Property("PushNotifications") + .HasColumnType("bit"); + + b.Property("ReferralCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RulesAcceptedAt") + .HasColumnType("datetime2"); + + b.Property("SmsNotifications") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("LegPosition") + .HasDatabaseName("IX_User_LegPosition"); + + b.HasIndex("Mobile") + .IsUnique() + .HasDatabaseName("IX_User_Mobile_Active") + .HasFilter("[IsDeleted] = 0"); + + b.HasIndex("NetworkParentId") + .HasDatabaseName("IX_User_NetworkParentId"); + + b.ToTable("Users", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CityId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserAddresses", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserCart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("UserCarts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContractId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SignGuid") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SignedPdfFile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ContractId"); + + b.HasIndex("UserId"); + + b.ToTable("UserContracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserInvitation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("InviteeUserId") + .HasColumnType("bigint"); + + b.Property("InviterUserId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("InviteeUserId") + .IsUnique() + .HasDatabaseName("IX_UserInvitations_InviteeUserId"); + + b.HasIndex("InviterUserId", "Created") + .HasDatabaseName("IX_UserInvitations_InviterUserId_Created"); + + b.ToTable("UserInvitations", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryStatus") + .HasColumnType("int"); + + b.Property("HasVAT") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentMethod") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TrackingCode") + .HasColumnType("nvarchar(max)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserAddressId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("PackageId"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("UserOrders", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserPackagePurchase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("PurchasedAt") + .HasColumnType("datetime2"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("PackageId") + .HasDatabaseName("IX_UserPackagePurchase_PackageId"); + + b.HasIndex("PurchasedAt") + .HasDatabaseName("IX_UserPackagePurchase_PurchasedAt"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId") + .HasDatabaseName("IX_UserPackagePurchase_UserId"); + + b.HasIndex("UserId", "PurchasedAt") + .HasDatabaseName("IX_UserPackagePurchase_UserId_PurchasedAt"); + + b.ToTable("UserPackagePurchases", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.HasIndex("UserId"); + + b.ToTable("UserRoles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountBalance") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MagicActivatedAt") + .HasColumnType("datetime2"); + + b.Property("MagicCompletedAt") + .HasColumnType("datetime2"); + + b.Property("MagicTotalCredited") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L); + + b.Property("MagicTotalDeposited") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L); + + b.Property("NetworkBalance") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WalletMode") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserWallets", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChangeDiscountValue") + .HasColumnType("bigint"); + + b.Property("ChangeNerworkValue") + .HasColumnType("bigint"); + + b.Property("ChangeValue") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CurrentBalance") + .HasColumnType("bigint"); + + b.Property("CurrentDiscountBalance") + .HasColumnType("bigint"); + + b.Property("CurrentNetworkBalance") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsIncrease") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("RefrenceId") + .HasColumnType("bigint"); + + b.Property("WalletId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("PackageId"); + + b.HasIndex("WalletId"); + + b.ToTable("UserWalletHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDefault") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique() + .HasDatabaseName("IX_Warehouses_Code_Unique"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_Warehouses_IsActive"); + + b.HasIndex("IsDefault") + .HasDatabaseName("IX_Warehouses_IsDefault") + .HasFilter("[IsDefault] = 1"); + + b.ToTable("Warehouses", "CMS"); + + b.HasData( + new + { + Id = 1L, + Address = "تهران - انبار مرکزی فروشگاه", + Code = "WH-001", + Created = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + CreatedBy = "System", + IsActive = true, + IsDefault = true, + IsDeleted = false, + Name = "انبار اصلی" + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("GregorianWeekNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("GregorianYear") + .HasColumnType("int"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PersianWeekNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PersianYear") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("WeekOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GregorianWeekNumber") + .IsUnique() + .HasDatabaseName("IX_WeekDefinition_GregorianWeekNumber"); + + b.HasIndex("GregorianYear") + .HasDatabaseName("IX_WeekDefinition_GregorianYear"); + + b.HasIndex("PersianWeekNumber") + .HasDatabaseName("IX_WeekDefinition_PersianWeekNumber"); + + b.HasIndex("PersianYear") + .HasDatabaseName("IX_WeekDefinition_PersianYear"); + + b.HasIndex("StartDate") + .HasDatabaseName("IX_WeekDefinition_StartDate"); + + b.ToTable("WeekDefinitions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogCategory", "BlogCategory") + .WithMany("BlogPostCategories") + .HasForeignKey("BlogCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostCategories") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogCategory"); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostImages") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostTags") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Category", "Parent") + .WithMany("Categories") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "FirstPackage") + .WithMany() + .HasForeignKey("FirstPackageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "LastPackage") + .WithMany() + .HasForeignKey("LastPackageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithOne("ClubMembership") + .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FirstPackage"); + + b.Navigation("LastPackage"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembershipCycle", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("Cycles") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubMembership"); + + b.Navigation("Package"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.UserClubFeature", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubFeature", "ClubFeature") + .WithMany("UserClubFeatures") + .HasForeignKey("ClubFeatureId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("UserClubFeatures") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserClubFeatures") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubFeature"); + + b.Navigation("ClubMembership"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("CommissionPayouts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("UserCommissionPayouts") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", "WeeklyPool") + .WithMany("UserCommissionPayouts") + .HasForeignKey("WeeklyPoolId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("User"); + + b.Navigation("WeekDefinition"); + + b.Navigation("WeeklyPool"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WeeklyCommissionPools") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WorkerExecutionLog", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WorkerExecutionLogs") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Content.SitePageSettings", "SitePageSettings") + .WithMany("Images") + .HasForeignKey("SitePageSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SitePageSettings"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Content.SitePage", "SitePage") + .WithMany("Sections") + .HasForeignKey("SitePageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SitePage"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId"); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DayaLoanContracts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", "ParentCategory") + .WithMany("ChildCategories") + .HasForeignKey("ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.UserAddress", "UserAddress") + .WithMany() + .HasForeignKey("UserAddressId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DiscountOrders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + + b.Navigation("UserAddress"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrderDetail", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", "DiscountOrder") + .WithMany("OrderDetails") + .HasForeignKey("DiscountOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("OrderDetails") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscountOrder"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", "Category") + .WithMany("ProductCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("ProductCategories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "DiscountProduct") + .WithMany("Images") + .HasForeignKey("DiscountProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DiscountProduct"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountShoppingCart", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("ShoppingCarts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DiscountShoppingCarts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.FactorDetails", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithMany("FactorDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("FactorDetails") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.City", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Geography.State", "State") + .WithMany("Cities") + .HasForeignKey("StateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("State"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Geography.Country", "Country") + .WithMany("States") + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Country"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipCycleHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembershipCycle", "ClubMembershipCycle") + .WithMany("CycleHistories") + .HasForeignKey("ClubMembershipCycleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubMembershipCycle"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("ClubMembershipHistories") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubMembership"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.CommissionPayoutHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", "UserCommissionPayout") + .WithMany("CommissionPayoutHistories") + .HasForeignKey("UserCommissionPayoutId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("CommissionPayoutHistories") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("UserCommissionPayout"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.PackageHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("PackageHistories") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Package"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "DiscountProduct") + .WithMany() + .HasForeignKey("DiscountProductId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("CMSMicroservice.Domain.Entities.Warehouse", "Warehouse") + .WithMany("InventoryItems") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscountProduct"); + + b.Navigation("Product"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("NetworkWeeklyBalances") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("NetworkWeeklyBalances") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("User"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithOne("OrderVAT") + .HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PackageFeature", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubFeature", "ClubFeature") + .WithMany() + .HasForeignKey("ClubFeatureId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("PackageFeatures") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClubFeature"); + + b.Navigation("Package"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Category", "Category") + .WithMany("ProductCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductCategories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductGallery", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductGalleries") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.ProductImage", "ProductImage") + .WithMany("ProductGalleries") + .HasForeignKey("ProductImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("ProductImage"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductTag", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductTags") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Tag", "Tag") + .WithMany("ProductTags") + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.StockMovement", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.InventoryItem", "InventoryItem") + .WithMany("StockMovements") + .HasForeignKey("InventoryItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("InventoryItem"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "NetworkParent") + .WithMany("NetworkChildren") + .HasForeignKey("NetworkParentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("NetworkParent"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserAddresses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserCart", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("UserCarts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserCarts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserContract", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Contract", "Contract") + .WithMany("UserContracts") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserContracts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserInvitation", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "Invitee") + .WithMany() + .HasForeignKey("InviteeUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "Inviter") + .WithMany() + .HasForeignKey("InviterUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Invitee"); + + b.Navigation("Inviter"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("UserOrders") + .HasForeignKey("PackageId"); + + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany("UserOrders") + .HasForeignKey("TransactionId"); + + b.HasOne("CMSMicroservice.Domain.Entities.UserAddress", "UserAddress") + .WithMany("UserOrders") + .HasForeignKey("UserAddressId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserOrders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("Transaction"); + + b.Navigation("User"); + + b.Navigation("UserAddress"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserPackagePurchase", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("Purchases") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Package"); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserRole", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Role", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserWallets") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.UserWallet", "Wallet") + .WithMany("UserWalletHistories") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("Wallet"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b => + { + b.Navigation("BlogPostCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b => + { + b.Navigation("BlogPostCategories"); + + b.Navigation("BlogPostImages"); + + b.Navigation("BlogPostTags"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.Navigation("Categories"); + + b.Navigation("ProductCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubFeature", b => + { + b.Navigation("UserClubFeatures"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.Navigation("ClubMembershipHistories"); + + b.Navigation("Cycles"); + + b.Navigation("UserClubFeatures"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembershipCycle", b => + { + b.Navigation("CycleHistories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.Navigation("CommissionPayoutHistories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.Navigation("UserCommissionPayouts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Navigation("Sections"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSettings", b => + { + b.Navigation("Images"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => + { + b.Navigation("UserContracts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.Navigation("ChildCategories"); + + b.Navigation("ProductCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.Navigation("OrderDetails"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b => + { + b.Navigation("Images"); + + b.Navigation("OrderDetails"); + + b.Navigation("ProductCategories"); + + b.Navigation("ShoppingCarts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.Country", b => + { + b.Navigation("States"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.Navigation("Cities"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.Navigation("StockMovements"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b => + { + b.Navigation("PackageFeatures"); + + b.Navigation("PackageHistories"); + + b.Navigation("Purchases"); + + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Product", b => + { + b.Navigation("FactorDetails"); + + b.Navigation("ProductCategories"); + + b.Navigation("ProductGalleries"); + + b.Navigation("ProductTags"); + + b.Navigation("UserCarts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductImage", b => + { + b.Navigation("ProductGalleries"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Role", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Tag", b => + { + b.Navigation("ProductTags"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Transaction", b => + { + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.Navigation("ClubMembership"); + + b.Navigation("CommissionPayouts"); + + b.Navigation("DayaLoanContracts"); + + b.Navigation("DiscountOrders"); + + b.Navigation("DiscountShoppingCarts"); + + b.Navigation("NetworkChildren"); + + b.Navigation("NetworkWeeklyBalances"); + + b.Navigation("UserAddresses"); + + b.Navigation("UserCarts"); + + b.Navigation("UserClubFeatures"); + + b.Navigation("UserContracts"); + + b.Navigation("UserOrders"); + + b.Navigation("UserRoles"); + + b.Navigation("UserWallets"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.Navigation("FactorDetails"); + + b.Navigation("OrderVAT"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.Navigation("UserWalletHistories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => + { + b.Navigation("InventoryItems"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b => + { + b.Navigation("CommissionPayoutHistories"); + + b.Navigation("NetworkWeeklyBalances"); + + b.Navigation("UserCommissionPayouts"); + + b.Navigation("WeeklyCommissionPools"); + + b.Navigation("WorkerExecutionLogs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260920194955_AddUserInvitations.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260920194955_AddUserInvitations.cs new file mode 100644 index 0000000..4ff1365 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260920194955_AddUserInvitations.cs @@ -0,0 +1,103 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddUserInvitations : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Mobile", + schema: "CMS", + table: "Users", + type: "nvarchar(20)", + maxLength: 20, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.CreateTable( + name: "UserInvitations", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + InviterUserId = table.Column(type: "bigint", nullable: false), + InviteeUserId = table.Column(type: "bigint", nullable: false), + Created = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + LastModified = table.Column(type: "datetime2", nullable: true), + LastModifiedBy = table.Column(type: "nvarchar(max)", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserInvitations", x => x.Id); + table.ForeignKey( + name: "FK_UserInvitations_Users_InviteeUserId", + column: x => x.InviteeUserId, + principalSchema: "CMS", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_UserInvitations_Users_InviterUserId", + column: x => x.InviterUserId, + principalSchema: "CMS", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_User_Mobile_Active", + schema: "CMS", + table: "Users", + column: "Mobile", + unique: true, + filter: "[IsDeleted] = 0"); + + migrationBuilder.CreateIndex( + name: "IX_UserInvitations_InviteeUserId", + schema: "CMS", + table: "UserInvitations", + column: "InviteeUserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UserInvitations_InviterUserId_Created", + schema: "CMS", + table: "UserInvitations", + columns: new[] { "InviterUserId", "Created" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserInvitations", + schema: "CMS"); + + migrationBuilder.DropIndex( + name: "IX_User_Mobile_Active", + schema: "CMS", + table: "Users"); + + migrationBuilder.AlterColumn( + name: "Mobile", + schema: "CMS", + table: "Users", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(20)", + oldMaxLength: 20); + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index c734f33..934e3a6 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -3574,7 +3574,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("Mobile") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(450)"); b.Property("MobileVerifiedAt") .HasColumnType("datetime2"); @@ -3606,6 +3606,11 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.HasIndex("LegPosition") .HasDatabaseName("IX_User_LegPosition"); + b.HasIndex("Mobile") + .IsUnique() + .HasDatabaseName("IX_User_Mobile_Active") + .HasFilter("[IsDeleted] = 0"); + b.HasIndex("NetworkParentId") .HasDatabaseName("IX_User_NetworkParentId"); @@ -3750,6 +3755,47 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.ToTable("UserContracts", "CMS"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserInvitation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("InviteeUserId") + .HasColumnType("bigint"); + + b.Property("InviterUserId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("InviteeUserId") + .IsUnique() + .HasDatabaseName("IX_UserInvitations_InviteeUserId"); + + b.HasIndex("InviterUserId", "Created") + .HasDatabaseName("IX_UserInvitations_InviterUserId_Created"); + + b.ToTable("UserInvitations", "CMS"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => { b.Property("Id") @@ -4871,6 +4917,25 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("User"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserInvitation", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "Invitee") + .WithMany() + .HasForeignKey("InviteeUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "Inviter") + .WithMany() + .HasForeignKey("InviterUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Invitee"); + + b.Navigation("Inviter"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => { b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index b3fd16c..ca8475e 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.212 + 0.0.213 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/user.proto b/src/CMSMicroservice.Protobuf/Protos/user.proto index 8465bd4..8e6597c 100644 --- a/src/CMSMicroservice.Protobuf/Protos/user.proto +++ b/src/CMSMicroservice.Protobuf/Protos/user.proto @@ -115,6 +115,16 @@ service UserContract }; }; + rpc GetMyInvitees(GetMyInviteesRequest) returns (GetMyInviteesResponse){ + option (google.api.http) = { + get: "/Customer/GetMyInvitees" + }; + }; + rpc GetInvitationsByFilter(GetInvitationsByFilterRequest) returns (GetInvitationsByFilterResponse){ + option (google.api.http) = { + get: "/GetInvitationsByFilter" + }; + }; rpc UploadCustomerAvatar(UploadCustomerAvatarRequest) returns (UploadCustomerAvatarResponse){ option (google.api.http) = { post: "/Customer/UploadAvatar" @@ -294,6 +304,8 @@ message VerifyOtpTokenRequest string purpose = 2; string code = 3; google.protobuf.StringValue parent_referral_code = 4; + // شماره موبایل دعوت‌کننده (اختیاری؛ فقط برای ثبت‌نام جدید) + google.protobuf.StringValue inviter_mobile = 5; } message VerifyOtpTokenResponse { @@ -466,3 +478,78 @@ message UpdateCustomerSettingsRequest google.protobuf.StringValue time_zone = 6; google.protobuf.BoolValue two_factor_auth_enabled = 7; } + +// ============= Inviter (دعوت‌کننده) — جدا از معرف شبکه ============= + +message GetMyInviteesRequest +{ + city.PaginationState pagination_state = 1; + // ALL | ACTIVE | INACTIVE — بر اساس ClubMembership.IsActive + google.protobuf.StringValue club_status_filter = 2; +} + +message GetMyInviteesResponse +{ + city.MetaData meta_data = 1; + repeated InviteeModel invitees = 2; + InviteeStats stats = 3; +} + +message InviteeModel +{ + int64 user_id = 1; + google.protobuf.StringValue first_name = 2; + google.protobuf.StringValue last_name = 3; + string mobile = 4; + google.protobuf.Timestamp registered_at = 5; + bool is_club_active = 6; +} + +message InviteeStats +{ + int32 total_invitees = 1; + int32 club_active_count = 2; + int32 club_inactive_count = 3; +} + +message GetInvitationsByFilterRequest +{ + city.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetInvitationsByFilterFilter filter = 3; +} + +message GetInvitationsByFilterFilter +{ + google.protobuf.StringValue inviter_mobile = 1; + google.protobuf.StringValue inviter_first_name = 2; + google.protobuf.StringValue inviter_last_name = 3; + google.protobuf.StringValue invitee_mobile = 4; + google.protobuf.StringValue invitee_first_name = 5; + google.protobuf.StringValue invitee_last_name = 6; + google.protobuf.BoolValue invitee_club_active = 7; + google.protobuf.Timestamp created_from = 8; + google.protobuf.Timestamp created_to = 9; +} + +message GetInvitationsByFilterResponse +{ + city.MetaData meta_data = 1; + repeated InvitationReportModel models = 2; +} + +message InvitationReportModel +{ + int64 id = 1; + int64 inviter_user_id = 2; + google.protobuf.StringValue inviter_first_name = 3; + google.protobuf.StringValue inviter_last_name = 4; + string inviter_mobile = 5; + int64 invitee_user_id = 6; + google.protobuf.StringValue invitee_first_name = 7; + google.protobuf.StringValue invitee_last_name = 8; + string invitee_mobile = 9; + google.protobuf.Timestamp invitee_registered_at = 10; + bool invitee_club_active = 11; + google.protobuf.Timestamp invited_at = 12; +} diff --git a/src/CMSMicroservice.WebApi/Services/UserService.cs b/src/CMSMicroservice.WebApi/Services/UserService.cs index d27f2a5..3c3429d 100644 --- a/src/CMSMicroservice.WebApi/Services/UserService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserService.cs @@ -15,8 +15,11 @@ using CMSMicroservice.Application.UserCQ.Commands.VerifyOtpToken; using CMSMicroservice.Application.UserCQ.Commands.AcceptContract; using CMSMicroservice.Application.UserCQ.Queries.GetCustomerProfile; using CMSMicroservice.Application.UserCQ.Queries.GetCustomerReferrals; +using CMSMicroservice.Application.UserCQ.Queries.GetMyInvitees; +using CMSMicroservice.Application.UserCQ.Queries.GetInvitationsByFilter; using CMSMicroservice.Application.UserCQ.Queries.GetCustomerSettings; using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Authorization; using Google.Protobuf.WellKnownTypes; using Microsoft.EntityFrameworkCore; using Grpc.Core; @@ -312,6 +315,117 @@ public class UserService : UserContract.UserContractBase return response; } + public override async Task GetMyInvitees( + GetMyInviteesRequest request, + ServerCallContext context) + { + var result = await _sender.Send(new GetMyInviteesQuery + { + UserId = 0, + PaginationState = request.PaginationState?.Adapt(), + ClubStatusFilter = request.ClubStatusFilter + }, context.CancellationToken); + + var response = new GetMyInviteesResponse + { + MetaData = new MetaData + { + CurrentPage = result.MetaData.CurrentPage, + TotalPage = result.MetaData.TotalPage, + PageSize = result.MetaData.PageSize, + TotalCount = result.MetaData.TotalCount, + HasPrevious = result.MetaData.HasPrevious, + HasNext = result.MetaData.HasNext + }, + Stats = new InviteeStats + { + TotalInvitees = result.Stats.TotalInvitees, + ClubActiveCount = result.Stats.ClubActiveCount, + ClubInactiveCount = result.Stats.ClubInactiveCount + } + }; + + foreach (var item in result.Invitees) + { + response.Invitees.Add(new InviteeModel + { + UserId = item.UserId, + FirstName = item.FirstName, + LastName = item.LastName, + Mobile = item.Mobile, + RegisteredAt = Timestamp.FromDateTime(DateTime.SpecifyKind(item.RegisteredAt, DateTimeKind.Utc)), + IsClubActive = item.IsClubActive + }); + } + + return response; + } + + [RequiresPermission(PermissionNames.UsersView)] + public override async Task GetInvitationsByFilter( + GetInvitationsByFilterRequest request, + ServerCallContext context) + { + DateTime? createdFrom = null; + DateTime? createdTo = null; + if (request.Filter?.CreatedFrom != null) + createdFrom = request.Filter.CreatedFrom.ToDateTime(); + if (request.Filter?.CreatedTo != null) + createdTo = request.Filter.CreatedTo.ToDateTime(); + + var result = await _sender.Send(new GetInvitationsByFilterQuery + { + PaginationState = request.PaginationState?.Adapt(), + SortBy = request.SortBy, + Filter = request.Filter == null ? null : new GetInvitationsByFilterFilterDto + { + InviterMobile = request.Filter.InviterMobile, + InviterFirstName = request.Filter.InviterFirstName, + InviterLastName = request.Filter.InviterLastName, + InviteeMobile = request.Filter.InviteeMobile, + InviteeFirstName = request.Filter.InviteeFirstName, + InviteeLastName = request.Filter.InviteeLastName, + InviteeClubActive = request.Filter.InviteeClubActive, + CreatedFrom = createdFrom, + CreatedTo = createdTo + } + }, context.CancellationToken); + + var response = new GetInvitationsByFilterResponse + { + MetaData = new MetaData + { + CurrentPage = result.MetaData.CurrentPage, + TotalPage = result.MetaData.TotalPage, + PageSize = result.MetaData.PageSize, + TotalCount = result.MetaData.TotalCount, + HasPrevious = result.MetaData.HasPrevious, + HasNext = result.MetaData.HasNext + } + }; + + foreach (var m in result.Models) + { + response.Models.Add(new InvitationReportModel + { + Id = m.Id, + InviterUserId = m.InviterUserId, + InviterFirstName = m.InviterFirstName, + InviterLastName = m.InviterLastName, + InviterMobile = m.InviterMobile, + InviteeUserId = m.InviteeUserId, + InviteeFirstName = m.InviteeFirstName, + InviteeLastName = m.InviteeLastName, + InviteeMobile = m.InviteeMobile, + InviteeRegisteredAt = Timestamp.FromDateTime(DateTime.SpecifyKind(m.InviteeRegisteredAt, DateTimeKind.Utc)), + InviteeClubActive = m.InviteeClubActive, + InvitedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(m.InvitedAt, DateTimeKind.Utc)) + }); + } + + return response; + } + public override async Task UploadCustomerAvatar(UploadCustomerAvatarRequest request, ServerCallContext context) { if (request.FileData == null || request.FileData.Length == 0) From 29426022aaee63e996e476d8ae4500f85bd707ab Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 21 Sep 2026 21:31:17 +0330 Subject: [PATCH 4/8] feat(payment): enhance TwentyCard payment integration and update configuration - Added support for optional Email and NationalId fields in PaymentRequest for TwentyCard transactions. - Updated TwentyCardPaymentService to handle multipart/form-data requests and improved error handling. - Modified appsettings and configuration files for both production and staging environments to include updated BaseUrl and credentials for TwentyCard. - Updated README to reflect the new documentation location. --- README.md | 2 +- k8s/production/cms-config.yaml | 17 ++++++ k8s/staging/cms-config.yaml | 17 ++++++ .../Interfaces/IPaymentGatewayService.cs | 10 ++++ .../Payment/TwentyCardPaymentService.cs | 56 ++++++++++++------- .../Services/PackageService.cs | 6 +- .../appsettings.Staging.json | 6 +- src/CMSMicroservice.WebApi/appsettings.json | 6 +- 8 files changed, 91 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 70283c2..1a3fdd9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ # CMS Microservice Documentation moved to [totalDoc/INDEX.md](../totalDoc/INDEX.md). - \ No newline at end of file + \ No newline at end of file diff --git a/k8s/production/cms-config.yaml b/k8s/production/cms-config.yaml index 6ce28ee..0835075 100644 --- a/k8s/production/cms-config.yaml +++ b/k8s/production/cms-config.yaml @@ -24,6 +24,23 @@ stringData: "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", "UseSandbox": false }, + "TwentyCard": { + "BaseUrl": "https://gateway.20card.ir/ipg", + "TerminalId": "165674", + "TerminalUsername": "ea5y1biehi", + "TerminalPassword": "SaLamAT@1404!", + "CreditPortalUrl": "https://userpanel.20card.ir/", + "Packages": { + "4": { + "GatewayAmountToman": 9700000, + "WalletCreditToman": 13000000 + }, + "1": { + "GatewayAmountToman": 97000000, + "WalletCreditToman": 130000000 + } + } + }, "CmsBaseUrl": "https://cms.kbs2.ir", "FrontOfficeBaseUrl": "https://kbs2.ir", "JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=", diff --git a/k8s/staging/cms-config.yaml b/k8s/staging/cms-config.yaml index c9c5562..17332ba 100644 --- a/k8s/staging/cms-config.yaml +++ b/k8s/staging/cms-config.yaml @@ -24,6 +24,23 @@ stringData: "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", "UseSandbox": true }, + "TwentyCard": { + "BaseUrl": "https://gateway.20card.ir/ipg", + "TerminalId": "165674", + "TerminalUsername": "ea5y1biehi", + "TerminalPassword": "SaLamAT@1404!", + "CreditPortalUrl": "https://userpanel.20card.ir/", + "Packages": { + "4": { + "GatewayAmountToman": 9700000, + "WalletCreditToman": 13000000 + }, + "1": { + "GatewayAmountToman": 97000000, + "WalletCreditToman": 130000000 + } + } + }, "CmsBaseUrl": "https://cms.se.kbs1.ir", "FrontOfficeBaseUrl": "https://frontoffice.se.kbs1.ir", "FMS": { diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs index e411212..63403c6 100644 --- a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs +++ b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs @@ -108,6 +108,16 @@ public class PaymentRequest /// شماره فاکتور / سفارش /// public string Invoice { get; set; } = string.Empty; + + /// + /// ایمیل (اختیاری — برای ۲۰کارت) + /// + public string? Email { get; set; } + + /// + /// کد ملی (اختیاری — برای ۲۰کارت) + /// + public string? NationalId { get; set; } } /// diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs index 01396a3..9fec515 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Net.Http; -using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using CMSMicroservice.Application.Common.Interfaces; @@ -11,7 +10,7 @@ namespace CMSMicroservice.Infrastructure.Services.Payment; /// /// درگاه پرداخت بیست‌کارت (20Card IPG Merchant API). -/// مبلغ ورودی به تومان است (مثل بقیهٔ سیستم). +/// مبلغ ورودی به تومان است. درخواست‌ها به‌صورت multipart/form-data ارسال می‌شوند. /// public class TwentyCardPaymentService : IPaymentGatewayService { @@ -55,12 +54,15 @@ public class TwentyCardPaymentService : IPaymentGatewayService { try { - var amount = (long)request.Amount; + var amount = ((long)request.Amount).ToString(); var payerName = string.IsNullOrWhiteSpace(request.Name) ? (string.IsNullOrWhiteSpace(request.Mobile) ? "کاربر" : request.Mobile) : request.Name.Trim(); + var invoice = string.IsNullOrWhiteSpace(request.Invoice) + ? request.UserId.ToString() + : request.Invoice; - var body = new Dictionary + using var content = BuildForm(new Dictionary { ["terminal_id"] = _terminalId, ["terminal_username"] = _terminalUsername, @@ -68,19 +70,19 @@ public class TwentyCardPaymentService : IPaymentGatewayService ["amount"] = amount, ["name"] = payerName, ["mobile"] = request.Mobile ?? string.Empty, - ["invoice"] = string.IsNullOrWhiteSpace(request.Invoice) - ? request.UserId.ToString() - : request.Invoice, + ["invoice"] = invoice, ["callback"] = request.CallbackUrl, - ["description"] = request.Description - }; + ["description"] = request.Description ?? string.Empty + }); + + if (!string.IsNullOrWhiteSpace(request.Email)) + content.Add(new StringContent(request.Email), "email"); + if (!string.IsNullOrWhiteSpace(request.NationalId)) + content.Add(new StringContent(request.NationalId), "national_id"); _logger.LogInformation( "TwentyCard payment request: Amount={Amount} Toman, User={UserId}, Invoice={Invoice}", - amount, request.UserId, body["invoice"]); - - using var content = new StringContent( - JsonSerializer.Serialize(body, JsonOptions), Encoding.UTF8, "application/json"); + amount, request.UserId, invoice); var response = await _httpClient.PostAsync("api/pgw/merchant/request", content, cancellationToken); var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); @@ -108,7 +110,9 @@ public class TwentyCardPaymentService : IPaymentGatewayService return new PaymentInitiateResult { IsSuccess = false, - ErrorMessage = result?.Message ?? "ایجاد تراکنش ۲۰کارت ناموفق بود" + ErrorMessage = result?.Message + ?? result?.Error?.Message + ?? "ایجاد تراکنش ۲۰کارت ناموفق بود" }; } @@ -155,17 +159,14 @@ public class TwentyCardPaymentService : IPaymentGatewayService }; } - var body = new Dictionary + using var content = BuildForm(new Dictionary { ["terminal_id"] = _terminalId, ["terminal_username"] = _terminalUsername, ["terminal_password"] = _terminalPassword, ["authority"] = refId, - ["amount"] = (long)amountInToman - }; - - using var content = new StringContent( - JsonSerializer.Serialize(body, JsonOptions), Encoding.UTF8, "application/json"); + ["amount"] = ((long)amountInToman).ToString() + }); var response = await _httpClient.PostAsync("api/pgw/merchant/verify", content, cancellationToken); var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); @@ -236,10 +237,25 @@ public class TwentyCardPaymentService : IPaymentGatewayService }); } + private static MultipartFormDataContent BuildForm(Dictionary fields) + { + var form = new MultipartFormDataContent(); + foreach (var (key, value) in fields) + form.Add(new StringContent(value ?? string.Empty), key); + return form; + } + private sealed class TwentyCardRequestResponse { public string? Message { get; set; } public TwentyCardRequestData? Data { get; set; } + public TwentyCardErrorBody? Error { get; set; } + } + + private sealed class TwentyCardErrorBody + { + public string? Code { get; set; } + public string? Message { get; set; } } private sealed class TwentyCardRequestData diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index 2c2410d..f0ebdd2 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -223,7 +223,7 @@ public class PackageService : PackageContract.PackageContractBase var user = await _context.Users .AsNoTracking() .Where(u => u.Id == userId) - .Select(u => new { u.Mobile, u.FirstName, u.LastName }) + .Select(u => new { u.Mobile, u.FirstName, u.LastName, u.Email, u.NationalCode }) .FirstOrDefaultAsync(cancellationToken); // Callback URL از config — نه از ورودی کاربر (امنیت) @@ -241,7 +241,9 @@ public class PackageService : PackageContract.PackageContractBase Name = payerName, Invoice = purchase.Id.ToString(), Description = $"خرید پکیج {package.Title}", - CallbackUrl = callbackUrl + CallbackUrl = callbackUrl, + Email = user?.Email, + NationalId = user?.NationalCode }, cancellationToken); if (!paymentResult.IsSuccess) diff --git a/src/CMSMicroservice.WebApi/appsettings.Staging.json b/src/CMSMicroservice.WebApi/appsettings.Staging.json index 3c7c05c..4009c39 100644 --- a/src/CMSMicroservice.WebApi/appsettings.Staging.json +++ b/src/CMSMicroservice.WebApi/appsettings.Staging.json @@ -5,11 +5,11 @@ "UseSandbox": true }, "TwentyCard": { - "BaseUrl": "https://gateway.20card.ir", + "BaseUrl": "https://gateway.20card.ir/ipg", "TerminalId": "165674", "TerminalUsername": "ea5y1biehi", - "TerminalPassword": "ea5y1biehi", - "CreditPortalUrl": "https://20card.ir", + "TerminalPassword": "SaLamAT@1404!", + "CreditPortalUrl": "https://userpanel.20card.ir/", "Packages": { "4": { "GatewayAmountToman": 9700000, diff --git a/src/CMSMicroservice.WebApi/appsettings.json b/src/CMSMicroservice.WebApi/appsettings.json index 6b3d8e0..fc929bb 100644 --- a/src/CMSMicroservice.WebApi/appsettings.json +++ b/src/CMSMicroservice.WebApi/appsettings.json @@ -5,11 +5,11 @@ "UseSandbox": true }, "TwentyCard": { - "BaseUrl": "https://gateway.20card.ir", + "BaseUrl": "https://gateway.20card.ir/ipg", "TerminalId": "165674", "TerminalUsername": "ea5y1biehi", - "TerminalPassword": "ea5y1biehi", - "CreditPortalUrl": "https://20card.ir", + "TerminalPassword": "SaLamAT@1404!", + "CreditPortalUrl": "https://userpanel.20card.ir/", "Packages": { "4": { "GatewayAmountToman": 9700000, From 9d17f174af225d369de860217ae1eb5e8d23071d Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 21 Sep 2026 21:35:42 +0330 Subject: [PATCH 5/8] feat(config): add CallbackBaseUrl to configuration for payment processing - Introduced CallbackBaseUrl in both cms-config.yaml and appsettings.Staging.json to support dynamic callback URLs for payment processing. - Updated PackageService to utilize the new CallbackBaseUrl for enhanced security and flexibility in payment integration. --- k8s/staging/cms-config.yaml | 1 + src/CMSMicroservice.WebApi/Services/PackageService.cs | 9 +++++++-- src/CMSMicroservice.WebApi/appsettings.Staging.json | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/k8s/staging/cms-config.yaml b/k8s/staging/cms-config.yaml index 17332ba..cd3e94e 100644 --- a/k8s/staging/cms-config.yaml +++ b/k8s/staging/cms-config.yaml @@ -30,6 +30,7 @@ stringData: "TerminalUsername": "ea5y1biehi", "TerminalPassword": "SaLamAT@1404!", "CreditPortalUrl": "https://userpanel.20card.ir/", + "CallbackBaseUrl": "https://kbs2.ir", "Packages": { "4": { "GatewayAmountToman": 9700000, diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index f0ebdd2..58607b6 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -227,8 +227,13 @@ public class PackageService : PackageContract.PackageContractBase .FirstOrDefaultAsync(cancellationToken); // Callback URL از config — نه از ورودی کاربر (امنیت) - var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"; - var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback?orderId={purchase.Id}"; + // ۲۰کارت: CallbackBaseUrl اختیاری (مثلاً استیج → kbs2.ir چون دامنه استیج در ترمینال ثبت نیست) + var frontOfficeBaseUrl = isTwentyCard + ? (_configuration["TwentyCard:CallbackBaseUrl"] + ?? _configuration["FrontOfficeBaseUrl"] + ?? "https://localhost:5268") + : (_configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"); + var callbackUrl = $"{frontOfficeBaseUrl.TrimEnd('/')}/profile/payment-callback?orderId={purchase.Id}"; var paymentGateway = _paymentGatewayResolver.Get(gatewayProvider); var payerName = $"{user?.FirstName} {user?.LastName}".Trim(); diff --git a/src/CMSMicroservice.WebApi/appsettings.Staging.json b/src/CMSMicroservice.WebApi/appsettings.Staging.json index 4009c39..6ce4bc3 100644 --- a/src/CMSMicroservice.WebApi/appsettings.Staging.json +++ b/src/CMSMicroservice.WebApi/appsettings.Staging.json @@ -10,6 +10,7 @@ "TerminalUsername": "ea5y1biehi", "TerminalPassword": "SaLamAT@1404!", "CreditPortalUrl": "https://userpanel.20card.ir/", + "CallbackBaseUrl": "https://kbs2.ir", "Packages": { "4": { "GatewayAmountToman": 9700000, From e970e516ce08491ea0ea55b86e24f15bbe04bd56 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 21 Sep 2026 21:37:45 +0330 Subject: [PATCH 6/8] fix(config): update GatewayAmountToman values in configuration files - Changed GatewayAmountToman values from 9700000 and 97000000 to 1000 in both cms-config.yaml and appsettings.Staging.json for consistency in payment processing configurations. --- k8s/staging/cms-config.yaml | 4 ++-- src/CMSMicroservice.WebApi/appsettings.Staging.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/k8s/staging/cms-config.yaml b/k8s/staging/cms-config.yaml index cd3e94e..6a7e80c 100644 --- a/k8s/staging/cms-config.yaml +++ b/k8s/staging/cms-config.yaml @@ -33,11 +33,11 @@ stringData: "CallbackBaseUrl": "https://kbs2.ir", "Packages": { "4": { - "GatewayAmountToman": 9700000, + "GatewayAmountToman": 1000, "WalletCreditToman": 13000000 }, "1": { - "GatewayAmountToman": 97000000, + "GatewayAmountToman": 1000, "WalletCreditToman": 130000000 } } diff --git a/src/CMSMicroservice.WebApi/appsettings.Staging.json b/src/CMSMicroservice.WebApi/appsettings.Staging.json index 6ce4bc3..140cbfe 100644 --- a/src/CMSMicroservice.WebApi/appsettings.Staging.json +++ b/src/CMSMicroservice.WebApi/appsettings.Staging.json @@ -13,11 +13,11 @@ "CallbackBaseUrl": "https://kbs2.ir", "Packages": { "4": { - "GatewayAmountToman": 9700000, + "GatewayAmountToman": 1000, "WalletCreditToman": 13000000 }, "1": { - "GatewayAmountToman": 97000000, + "GatewayAmountToman": 1000, "WalletCreditToman": 130000000 } } From 6ad1341754f6a2e53c2a1a4b23cc00efc312061c Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Tue, 22 Sep 2026 00:08:09 +0330 Subject: [PATCH 7/8] feat(config): add DefaultEmail to configuration files for payment processing - Introduced DefaultEmail field in cms-config.yaml, appsettings.json, and appsettings.Staging.json to standardize email handling in payment requests. - Updated TwentyCardPaymentService to utilize DefaultEmail when no email is provided in the request, enhancing error handling and user experience. --- k8s/production/cms-config.yaml | 1 + k8s/staging/cms-config.yaml | 1 + .../Payment/TwentyCardPaymentService.cs | 31 ++++++++++++++++--- .../appsettings.Staging.json | 1 + src/CMSMicroservice.WebApi/appsettings.json | 1 + 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/k8s/production/cms-config.yaml b/k8s/production/cms-config.yaml index 0835075..a1a0674 100644 --- a/k8s/production/cms-config.yaml +++ b/k8s/production/cms-config.yaml @@ -30,6 +30,7 @@ stringData: "TerminalUsername": "ea5y1biehi", "TerminalPassword": "SaLamAT@1404!", "CreditPortalUrl": "https://userpanel.20card.ir/", + "DefaultEmail": "info@kbs2.ir", "Packages": { "4": { "GatewayAmountToman": 9700000, diff --git a/k8s/staging/cms-config.yaml b/k8s/staging/cms-config.yaml index 6a7e80c..fa9b9e3 100644 --- a/k8s/staging/cms-config.yaml +++ b/k8s/staging/cms-config.yaml @@ -31,6 +31,7 @@ stringData: "TerminalPassword": "SaLamAT@1404!", "CreditPortalUrl": "https://userpanel.20card.ir/", "CallbackBaseUrl": "https://kbs2.ir", + "DefaultEmail": "info@kbs2.ir", "Packages": { "4": { "GatewayAmountToman": 1000, diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs index 9fec515..f086906 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs @@ -19,6 +19,7 @@ public class TwentyCardPaymentService : IPaymentGatewayService private readonly string _terminalId; private readonly string _terminalUsername; private readonly string _terminalPassword; + private readonly string _defaultEmail; private static readonly JsonSerializerOptions JsonOptions = new() { @@ -43,8 +44,11 @@ public class TwentyCardPaymentService : IPaymentGatewayService ?? throw new InvalidOperationException("TwentyCard:TerminalUsername is not configured."); _terminalPassword = configuration["TwentyCard:TerminalPassword"] ?? throw new InvalidOperationException("TwentyCard:TerminalPassword is not configured."); + _defaultEmail = configuration["TwentyCard:DefaultEmail"] ?? "info@kbs2.ir"; _httpClient.BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/"); + if (!_httpClient.DefaultRequestHeaders.Contains("Accept")) + _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/json"); _logger.LogInformation("TwentyCard payment service initialized. BaseUrl={BaseUrl}", baseUrl); } @@ -72,13 +76,13 @@ public class TwentyCardPaymentService : IPaymentGatewayService ["mobile"] = request.Mobile ?? string.Empty, ["invoice"] = invoice, ["callback"] = request.CallbackUrl, - ["description"] = request.Description ?? string.Empty + ["description"] = request.Description ?? string.Empty, + // بدون email درگاه ۲۰کارت به‌جای validation، HTTP 500 برمی‌گرداند + ["email"] = ResolveEmail(request.Email) }); - if (!string.IsNullOrWhiteSpace(request.Email)) - content.Add(new StringContent(request.Email), "email"); if (!string.IsNullOrWhiteSpace(request.NationalId)) - content.Add(new StringContent(request.NationalId), "national_id"); + AddFormField(content, "national_id", request.NationalId); _logger.LogInformation( "TwentyCard payment request: Amount={Amount} Toman, User={UserId}, Invoice={Invoice}", @@ -241,10 +245,27 @@ public class TwentyCardPaymentService : IPaymentGatewayService { var form = new MultipartFormDataContent(); foreach (var (key, value) in fields) - form.Add(new StringContent(value ?? string.Empty), key); + AddFormField(form, key, value ?? string.Empty); return form; } + /// + /// شبیه curl --form: بدون Content-Type روی هر part ( بعضی بک‌اندهای PHP با charset=utf-8 می‌شکنند). + /// + private static void AddFormField(MultipartFormDataContent form, string name, string value) + { + var part = new StringContent(value ?? string.Empty); + part.Headers.ContentType = null; + form.Add(part, name); + } + + private string ResolveEmail(string? email) + { + if (!string.IsNullOrWhiteSpace(email) && email.Contains('@', StringComparison.Ordinal)) + return email.Trim(); + return _defaultEmail; + } + private sealed class TwentyCardRequestResponse { public string? Message { get; set; } diff --git a/src/CMSMicroservice.WebApi/appsettings.Staging.json b/src/CMSMicroservice.WebApi/appsettings.Staging.json index 140cbfe..557dd00 100644 --- a/src/CMSMicroservice.WebApi/appsettings.Staging.json +++ b/src/CMSMicroservice.WebApi/appsettings.Staging.json @@ -11,6 +11,7 @@ "TerminalPassword": "SaLamAT@1404!", "CreditPortalUrl": "https://userpanel.20card.ir/", "CallbackBaseUrl": "https://kbs2.ir", + "DefaultEmail": "info@kbs2.ir", "Packages": { "4": { "GatewayAmountToman": 1000, diff --git a/src/CMSMicroservice.WebApi/appsettings.json b/src/CMSMicroservice.WebApi/appsettings.json index fc929bb..22c19d1 100644 --- a/src/CMSMicroservice.WebApi/appsettings.json +++ b/src/CMSMicroservice.WebApi/appsettings.json @@ -10,6 +10,7 @@ "TerminalUsername": "ea5y1biehi", "TerminalPassword": "SaLamAT@1404!", "CreditPortalUrl": "https://userpanel.20card.ir/", + "DefaultEmail": "info@kbs2.ir", "Packages": { "4": { "GatewayAmountToman": 9700000, From cbc12255627574d23693180935a6a5f2a75dbacc Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Tue, 22 Sep 2026 00:59:39 +0330 Subject: [PATCH 8/8] fix(payment): update JSON serialization settings and enhance request data structure - Changed PropertyNamingPolicy in TwentyCardPaymentService from SnakeCaseLower to CamelCase for improved consistency in JSON serialization. - Added JsonPropertyName attributes for Authority and RedirectUrl properties in TwentyCardRequestData to ensure correct JSON mapping. --- .../Services/Payment/TwentyCardPaymentService.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs index f086906..f2c45fc 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/TwentyCardPaymentService.cs @@ -23,7 +23,7 @@ public class TwentyCardPaymentService : IPaymentGatewayService private static readonly JsonSerializerOptions JsonOptions = new() { - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; @@ -281,7 +281,10 @@ public class TwentyCardPaymentService : IPaymentGatewayService private sealed class TwentyCardRequestData { + [JsonPropertyName("authority")] public string? Authority { get; set; } + + [JsonPropertyName("redirectUrl")] public string? RedirectUrl { get; set; } }