diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs index caf5896..0c2da24 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/UpdatePackage/UpdatePackageCommand.cs @@ -11,5 +11,17 @@ public record UpdatePackageCommand : IRequest public string ImagePath { get; init; } //قیمت public long Price { get; init; } - + // فیلدهای جدید پکیج + public int SortOrder { get; init; } + public bool IsActive { get; init; } + public bool IsBasePackage { get; init; } + public bool SupportsDayaPurchase { get; init; } + public bool SupportsDirectPurchase { get; init; } + public long ActivationFee { get; init; } + public double DiscountMultiplier { get; init; } + public double MagicWalletMultiplier { get; init; } + public int MaxBalancesPerLeg { get; init; } + public int MaxNetworkLevel { get; init; } + public long MagicWalletMaxDeposit { get; init; } + public long MagicWalletMaxCredit { get; init; } } \ No newline at end of file diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs index 7ce8bf0..08aa7ba 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs @@ -100,7 +100,14 @@ public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler p.Id == order.PackageId.Value, cancellationToken) + : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken); + + var discountMultiplier = package?.DiscountMultiplier ?? 2.0m; + + // 5. شارژ کیف پول (Balance فقط طبق سناریوی پکیج) var wallet = await _context.UserWallets .FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken); @@ -113,9 +120,10 @@ public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler {NewBalance}, DiscountBalance: {OldDiscount} -> {NewDiscount}", @@ -163,7 +171,7 @@ public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler Handle(GetAllPackageByFilterQuery request, CancellationToken cancellationToken) { var query = _context.Packages + .Where(x => !x.IsDeleted) .ApplyOrder(sortBy: request.SortBy) .AsNoTracking() .AsQueryable(); diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQueryHandler.cs index b4122ea..2abf39a 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQueryHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackageDetails/GetCustomerPackageDetailsQueryHandler.cs @@ -18,44 +18,60 @@ public class GetCustomerPackageDetailsQueryHandler : IRequestHandler x.Id == request.PackageId) - .ProjectToType() - .FirstOrDefaultAsync(cancellationToken); + .Include(p => p.PackageFeatures) + .ThenInclude(pf => pf.ClubFeature) + .FirstOrDefaultAsync(x => x.Id == request.PackageId && !x.IsDeleted, cancellationToken); if (package == null) throw new NotFoundException(nameof(Package), request.PackageId); - // Add features based on package (this could be stored in DB in future) - package.Features = new List + var response = new GetCustomerPackageDetailsResponseDto { - new PackageFeatureDto - { - Title = "درآمد کمیسیون", - Description = "دریافت کمیسیون از فروش محصولات", - Icon = "commission", - IsHighlighted = true - }, - new PackageFeatureDto - { - Title = "پشتیبانی 24/7", - Description = "دسترسی به پشتیبانی در تمام ساعات شبانه روز", - Icon = "support", - IsHighlighted = false - }, - new PackageFeatureDto - { - Title = "آموزش‌های تخصصی", - Description = "دسترسی به دوره‌های آموزشی و وبینارها", - Icon = "education", - IsHighlighted = true - } + Id = package.Id, + Title = package.Title, + Description = package.Description, + Price = package.Price, + ImagePath = package.ImagePath, + ActivationFee = package.ActivationFee, + DiscountMultiplier = (double)package.DiscountMultiplier, + MagicWalletMultiplier = (double)package.MagicWalletMultiplier, + MagicWalletMaxDeposit = package.MagicWalletMaxDeposit, + MagicWalletMaxCredit = package.MagicWalletMaxCredit, + IsBasePackage = package.IsBasePackage, + SupportsDayaPurchase = package.SupportsDayaPurchase, + SupportsDirectPurchase = package.SupportsDirectPurchase }; - // Set purchase requirements - package.Requirements = new PurchaseRequirementsDto + // بارگذاری ویژگی‌ها از DB (PackageFeatures → ClubFeature) + if (package.PackageFeatures?.Any() == true) + { + response.Features = package.PackageFeatures + .Where(pf => pf.ClubFeature != null) + .Select(pf => new PackageFeatureDto + { + Title = pf.ClubFeature.Title ?? string.Empty, + Description = pf.ClubFeature.Description ?? string.Empty, + Icon = "feature", + IsHighlighted = pf.ClubFeature.IsActive + }) + .ToList(); + } + else + { + // فال‌بک به ویژگی‌های پیش‌فرض + response.Features = new List + { + new() { Title = "درآمد کمیسیون", Description = "دریافت کمیسیون از فروش محصولات", Icon = "commission", IsHighlighted = true }, + new() { Title = "پشتیبانی 24/7", Description = "دسترسی به پشتیبانی در تمام ساعات شبانه روز", Icon = "support", IsHighlighted = false }, + new() { Title = "آموزش‌های تخصصی", Description = "دسترسی به دوره‌های آموزشی و وبینارها", Icon = "education", IsHighlighted = true } + }; + } + + // شرایط خرید + response.Requirements = new PurchaseRequirementsDto { RequiresMembership = false, - MinimumWalletBalance = package.Price / 10, // 10% minimum + MinimumWalletBalance = package.Price / 10, Restrictions = new List { "باید حداقل 18 سال سن داشته باشید", @@ -63,6 +79,6 @@ public class GetCustomerPackageDetailsQueryHandler : IRequestHandler Features { get; set; } = new(); public PurchaseRequirementsDto Requirements { get; set; } + // New package-based fields + public long ActivationFee { get; set; } + public double DiscountMultiplier { get; set; } + public double MagicWalletMultiplier { get; set; } + public long MagicWalletMaxDeposit { get; set; } + public long MagicWalletMaxCredit { get; set; } + public bool IsBasePackage { get; set; } + public bool SupportsDayaPurchase { get; set; } + public bool SupportsDirectPurchase { get; set; } } public class PackageFeatureDto diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQueryHandler.cs index 6374498..840fc1e 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQueryHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesQueryHandler.cs @@ -17,35 +17,43 @@ public class GetCustomerPackagesQueryHandler : IRequestHandler !p.IsDeleted) .AsQueryable(); - // Filter by PackageType if specified - if (request.PackageTypeFilter.HasValue) + // فیلتر IsActive + if (!request.IncludeInactive) + query = query.Where(p => p.IsActive); + + // مرتب‌سازی بر اساس SortOrder + query = query.OrderBy(p => p.SortOrder); + + var packages = await query.ToListAsync(cancellationToken); + + return packages.Select(p => new GetCustomerPackagesResponseDto { - // Note: Package entity doesn't have PackageType enum, so we filter by convention - // Assuming Title or Description contains the package type indicator - // If Package entity needs PackageType field, it should be added to migration - } - - // Get all packages (assuming all are available unless marked otherwise) - var packages = await query - .ProjectToType() - .ToListAsync(cancellationToken); - - // Map additional fields - foreach (var package in packages) - { - package.Name = package.Title; - package.ImageUrl = package.ImagePath; - package.Currency = "IRR"; - package.IsAvailable = true; - package.ValidityDays = 365; // Default validity - package.IsPopular = false; - package.ShortDescription = package.Description?.Length > 100 - ? package.Description.Substring(0, 100) + "..." - : package.Description; - } - - return packages; + Id = p.Id, + Name = p.Title, + Title = p.Title, + Description = p.Description, + Price = p.Price, + ImageUrl = p.ImagePath, + ImagePath = p.ImagePath, + Currency = "IRR", + IsAvailable = p.IsActive, + ValidityDays = 365, + IsPopular = p.IsBasePackage, + ShortDescription = p.Description?.Length > 100 + ? p.Description.Substring(0, 100) + "..." + : p.Description, + // New fields + ActivationFee = p.ActivationFee, + DiscountMultiplier = (double)p.DiscountMultiplier, + MagicWalletMultiplier = (double)p.MagicWalletMultiplier, + MagicWalletMaxDeposit = p.MagicWalletMaxDeposit, + MagicWalletMaxCredit = p.MagicWalletMaxCredit, + IsBasePackage = p.IsBasePackage, + SupportsDayaPurchase = p.SupportsDayaPurchase, + SupportsDirectPurchase = p.SupportsDirectPurchase + }).ToList(); } } diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesResponseDto.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesResponseDto.cs index dfbf55d..4f4a058 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesResponseDto.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPackages/GetCustomerPackagesResponseDto.cs @@ -15,4 +15,13 @@ public class GetCustomerPackagesResponseDto public int ValidityDays { get; set; } public bool IsPopular { get; set; } public string ShortDescription { get; set; } + // New package-based fields + public long ActivationFee { get; set; } + public double DiscountMultiplier { get; set; } + public double MagicWalletMultiplier { get; set; } + public long MagicWalletMaxDeposit { get; set; } + public long MagicWalletMaxCredit { get; set; } + public bool IsBasePackage { get; set; } + public bool SupportsDayaPurchase { get; set; } + public bool SupportsDirectPurchase { get; set; } } diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQueryHandler.cs index c923293..cc02c25 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQueryHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetCustomerPurchaseHistory/GetCustomerPurchaseHistoryQueryHandler.cs @@ -33,6 +33,7 @@ public class GetCustomerPurchaseHistoryQueryHandler : IRequestHandler x.UserId == userId && x.PackageId != null) .Include(x => x.Package) + .Include(x => x.Transaction) .AsQueryable(); // Apply date filters if specified diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryHandler.cs index daf2885..1e11601 100644 --- a/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryHandler.cs +++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryHandler.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Application.Common.Exceptions; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Enums; using MediatR; @@ -15,47 +16,49 @@ public class GetUserPackageStatusQueryHandler : IRequestHandler Handle(GetUserPackageStatusQuery request, CancellationToken cancellationToken) { - // TODO: پیاده‌سازی دریافت وضعیت پکیج کاربر - // - // 1. دریافت اطلاعات کاربر: - // - var user = await _context.Users - // .Include(u => u.UserWallet) - // .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken) - // - if (user == null) throw new NotFoundException("کاربر یافت نشد") - // - // 2. دریافت عضویت باشگاه: - // - var clubMembership = await _context.ClubMemberships - // .FirstOrDefaultAsync(c => c.UserId == user.Id && c.IsActive, cancellationToken) - // - // 3. دریافت آخرین سفارش پکیج: - // - var lastPackageOrder = await _context.UserOrders - // .Where(o => o.UserId == user.Id && o.PackageId != null) - // .OrderByDescending(o => o.Created) - // .FirstOrDefaultAsync(cancellationToken) - // - // 4. بررسی شرایط فعالسازی باشگاه: - // - var wallet = user.UserWallet - // - bool canActivate = - // user.PackagePurchaseMethod != PackagePurchaseMethod.None && - // clubMembership == null && - // wallet != null && - // wallet.Balance >= 56_000_000 - // - // 5. برگشت DTO: - // - return new UserPackageStatusDto { - // UserId = user.Id, - // PackagePurchaseMethod = user.PackagePurchaseMethod.ToString(), - // HasPurchasedPackage = user.PackagePurchaseMethod != PackagePurchaseMethod.None, - // IsClubMemberActive = clubMembership != null, - // WalletBalance = wallet?.Balance ?? 0, - // DiscountBalance = wallet?.DiscountBalance ?? 0, - // CanActivateClubMembership = canActivate, - // LastOrderNumber = lastPackageOrder?.OrderNumber, - // LastPurchaseDate = lastPackageOrder?.Created - // } - // - // نکته: این query برای UI مفید است تا وضعیت کاربر را نمایش دهد + // 1. دریافت اطلاعات کاربر + var user = await _context.Users + .Include(u => u.UserWallets) + .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken); - throw new NotImplementedException("GetUserPackageStatus needs implementation"); + if (user == null) + throw new NotFoundException(nameof(User), request.UserId); + + // 2. دریافت عضویت باشگاه + var clubMembership = await _context.ClubMemberships + .FirstOrDefaultAsync(c => c.UserId == user.Id && c.IsActive, cancellationToken); + + // 3. دریافت آخرین سفارش پکیج + var lastPackageOrder = await _context.UserOrders + .Where(o => o.UserId == user.Id && o.PackageId != null) + .OrderByDescending(o => o.Created) + .FirstOrDefaultAsync(cancellationToken); + + // 4. بارگذاری پکیج پایه برای بررسی شرایط فعالسازی + var basePackage = await _context.Packages + .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken); + + var wallet = user.UserWallets.FirstOrDefault(); + + // 5. بررسی شرایط فعالسازی باشگاه + var canActivate = + user.PackagePurchaseMethod != PackagePurchaseMethod.None && + clubMembership == null && + wallet != null && + basePackage != null && + wallet.Balance >= basePackage.Price; + + return new UserPackageStatusDto + { + UserId = user.Id, + PackagePurchaseMethod = user.PackagePurchaseMethod.ToString(), + HasPurchasedPackage = user.PackagePurchaseMethod != PackagePurchaseMethod.None, + IsClubMemberActive = clubMembership?.IsActive ?? false, + WalletBalance = wallet?.Balance ?? 0, + DiscountBalance = wallet?.DiscountBalance ?? 0, + CanActivateClubMembership = canActivate, + LastOrderNumber = lastPackageOrder?.Id.ToString(), + LastPurchaseDate = lastPackageOrder?.Created + }; } } diff --git a/src/CMSMicroservice.Protobuf/Protos/package.proto b/src/CMSMicroservice.Protobuf/Protos/package.proto index 2cc189d..588ab77 100644 --- a/src/CMSMicroservice.Protobuf/Protos/package.proto +++ b/src/CMSMicroservice.Protobuf/Protos/package.proto @@ -114,6 +114,18 @@ message CreateNewPackageRequest string image_path = 3; int64 price = 4; BoostCardFileModel image_file = 5; + int32 sort_order = 6; + bool is_active = 7; + bool is_base_package = 8; + bool supports_daya_purchase = 9; + bool supports_direct_purchase = 10; + int64 activation_fee = 11; + double discount_multiplier = 12; + double magic_wallet_multiplier = 13; + int32 max_balances_per_leg = 14; + int32 max_network_level = 15; + int64 magic_wallet_max_deposit = 16; + int64 magic_wallet_max_credit = 17; } message CreateNewPackageResponse { @@ -127,6 +139,18 @@ message UpdatePackageRequest string image_path = 4; int64 price = 5; BoostCardFileModel image_file = 6; + int32 sort_order = 7; + bool is_active = 8; + bool is_base_package = 9; + bool supports_daya_purchase = 10; + bool supports_direct_purchase = 11; + int64 activation_fee = 12; + double discount_multiplier = 13; + double magic_wallet_multiplier = 14; + int32 max_balances_per_leg = 15; + int32 max_network_level = 16; + int64 magic_wallet_max_deposit = 17; + int64 magic_wallet_max_credit = 18; } message DeletePackageRequest { @@ -143,6 +167,18 @@ message GetPackageResponse string description = 3; string image_path = 4; int64 price = 5; + int32 sort_order = 6; + bool is_active = 7; + bool is_base_package = 8; + bool supports_daya_purchase = 9; + bool supports_direct_purchase = 10; + int64 activation_fee = 11; + double discount_multiplier = 12; + double magic_wallet_multiplier = 13; + int32 max_balances_per_leg = 14; + int32 max_network_level = 15; + int64 magic_wallet_max_deposit = 16; + int64 magic_wallet_max_credit = 17; } message GetAllPackageByFilterRequest { @@ -170,6 +206,18 @@ message GetAllPackageByFilterResponseModel string description = 3; string image_path = 4; int64 price = 5; + int32 sort_order = 6; + bool is_active = 7; + bool is_base_package = 8; + bool supports_daya_purchase = 9; + bool supports_direct_purchase = 10; + int64 activation_fee = 11; + double discount_multiplier = 12; + double magic_wallet_multiplier = 13; + int32 max_balances_per_leg = 14; + int32 max_network_level = 15; + int64 magic_wallet_max_deposit = 16; + int64 magic_wallet_max_credit = 17; } // Package Purchase Messages @@ -350,8 +398,17 @@ message CustomerPackageModel int32 validity_days = 9; bool is_popular = 10; string short_description = 11; - string title = 12; // Alias for frontend compatibility (uses name) - string image_path = 13; // Alias for frontend compatibility (uses image_url) + string title = 12; + string image_path = 13; + // New package-based fields + int64 activation_fee = 14; + double discount_multiplier = 15; + double magic_wallet_multiplier = 16; + int64 magic_wallet_max_deposit = 17; + int64 magic_wallet_max_credit = 18; + bool is_base_package = 19; + bool supports_daya_purchase = 20; + bool supports_direct_purchase = 21; } message PackageFeature diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs index 6949c93..3d99a43 100644 --- a/src/CMSMicroservice.WebApi/Services/PackageService.cs +++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs @@ -317,6 +317,48 @@ public class PackageService : PackageContract.PackageContractBase transaction.PaymentStatus = Domain.Enums.PaymentStatus.Success; transaction.PaymentDate = DateTime.UtcNow; transaction.RefId = verifyResult.RefId; + + // شارژ کیف پول کاربر + var wallet = await _context.UserWallets + .FirstOrDefaultAsync(w => w.UserId == purchase.UserId, context.CancellationToken); + + if (wallet == null) + { + wallet = new CMSMicroservice.Domain.Entities.UserWallet + { + UserId = purchase.UserId, + Balance = 0, + DiscountBalance = 0, + NetworkBalance = 0 + }; + _context.UserWallets.Add(wallet); + await _context.SaveChangesAsync(context.CancellationToken); + } + + var discountAmount = (long)(purchase.Amount * (double)(purchase.Package?.DiscountMultiplier ?? 2.0m)); + wallet.Balance += purchase.Amount; + wallet.DiscountBalance += discountAmount; + + // ثبت لاگ کیف پول + var walletLog = new CMSMicroservice.Domain.Entities.UserWalletChangeLog + { + WalletId = wallet.Id, + CurrentBalance = wallet.Balance, + ChangeValue = purchase.Amount, + CurrentNetworkBalance = wallet.NetworkBalance, + ChangeNerworkValue = 0, + CurrentDiscountBalance = wallet.DiscountBalance, + ChangeDiscountValue = discountAmount, + IsIncrease = true, + RefrenceId = transaction.Id + }; + _context.UserWalletChangeLogs.Add(walletLog); + + // به‌روزرسانی کاربر + var user = await _context.Users + .FirstOrDefaultAsync(u => u.Id == purchase.UserId, context.CancellationToken); + if (user != null) + user.PackagePurchaseMethod = Domain.Enums.PackagePurchaseMethod.DirectPurchase; } else if (transaction != null) {