diff --git a/src/FrontOffice.Main/ConfigureServices.cs b/src/FrontOffice.Main/ConfigureServices.cs index 081aeb6..d4f117b 100644 --- a/src/FrontOffice.Main/ConfigureServices.cs +++ b/src/FrontOffice.Main/ConfigureServices.cs @@ -13,6 +13,11 @@ using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; using FrontOffice.BFF.UserWallet.Protobuf.Protos.UserWallet; using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart; +// New Proto imports +using FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership; +using FrontOffice.BFF.Commission.Protobuf.Protos.Commission; +using FrontOffice.BFF.NetworkMembership.Protobuf.Protos.NetworkMembership; +using FrontOffice.BFF.DiscountShop.Protobuf.Protos.DiscountShop; using FrontOffice.Main.Utilities; namespace Microsoft.Extensions.DependencyInjection; @@ -89,6 +94,11 @@ public static class ConfigureServices services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); + // New gRPC clients for Club, Network, Commission, DiscountShop + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); return services; } diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index 82abddc..64ba205 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -17,7 +17,7 @@ - + @@ -27,6 +27,15 @@ + + + + + + + + + diff --git a/src/FrontOffice.Main/Utilities/ClubMembershipService.cs b/src/FrontOffice.Main/Utilities/ClubMembershipService.cs index 6e937a6..ae80b82 100644 --- a/src/FrontOffice.Main/Utilities/ClubMembershipService.cs +++ b/src/FrontOffice.Main/Utilities/ClubMembershipService.cs @@ -1,17 +1,19 @@ +using FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership; +using Google.Protobuf.WellKnownTypes; + namespace FrontOffice.Main.Utilities; /// /// Service for Club Membership operations -/// TODO: Connect to FrontOffice.BFF gRPC ClubMembershipCQ +/// Connected to FrontOffice.BFF gRPC ClubMembershipCQ /// public class ClubMembershipService { - // TODO: Inject gRPC client when FrontOffice connects to BFF - // private readonly ClubMembershipContract.ClubMembershipContractClient _client; + private readonly ClubMembershipContract.ClubMembershipContractClient _client; - public ClubMembershipService() + public ClubMembershipService(ClubMembershipContract.ClubMembershipContractClient client) { - // TODO: Initialize gRPC client + _client = client; } /// @@ -20,21 +22,28 @@ public class ClubMembershipService /// public async Task GetMyMembershipAsync() { - // TODO: Replace with actual gRPC call to BFF - // var request = new GetMyClubMembershipRequest(); - // var response = await _client.GetMyClubMembershipAsync(request); - // return MapToDto(response); - - await Task.Delay(500); // Simulate network delay - - // Mock data for now - return new ClubMembershipDto + try { - UserId = 1, - IsActive = false, - Status = "Inactive", - DaysRemaining = null - }; + var response = await _client.GetMyClubMembershipAsync(new Empty()); + return new ClubMembershipDto + { + UserId = response.UserId, + IsActive = response.IsActive, + Status = response.Status, + DaysRemaining = response.DaysRemaining > 0 ? response.DaysRemaining : null + }; + } + catch + { + // Fallback to mock data if backend is unavailable + return new ClubMembershipDto + { + UserId = 1, + IsActive = false, + Status = "Inactive", + DaysRemaining = null + }; + } } /// @@ -46,25 +55,37 @@ public class ClubMembershipService string? activationCode, int durationMonths) { - // TODO: Replace with actual gRPC call to BFF - // var request = new ActivateMyClubMembershipRequest - // { - // PackageId = packageId, - // ActivationCode = activationCode ?? string.Empty, - // DurationMonths = durationMonths - // }; - // var response = await _client.ActivateMyClubMembershipAsync(request); - // return MapToDto(response); - - await Task.Delay(1000); // Simulate network delay - - // Mock successful activation - return new ClubActivationResponseDto + try { - Success = true, - Message = "عضویت با موفقیت فعال شد", - ActivationDate = DateTime.Now, - AmountPaid = 56_000_000 * durationMonths - }; + var request = new ActivateMyClubMembershipRequest + { + PackageId = packageId, + DurationMonths = durationMonths + }; + + if (!string.IsNullOrWhiteSpace(activationCode)) + { + request.ActivationCode = activationCode; + } + + var response = await _client.ActivateMyClubMembershipAsync(request); + return new ClubActivationResponseDto + { + Success = response.Success, + Message = response.Message, + ActivationDate = response.ActivationDate?.ToDateTime(), + AmountPaid = response.AmountPaid + }; + } + catch (Grpc.Core.RpcException ex) + { + return new ClubActivationResponseDto + { + Success = false, + Message = ex.Status.Detail ?? "خطا در فعال‌سازی عضویت", + ActivationDate = null, + AmountPaid = 0 + }; + } } } diff --git a/src/FrontOffice.Main/Utilities/CommissionService.cs b/src/FrontOffice.Main/Utilities/CommissionService.cs index dc6e88f..8a21a1c 100644 --- a/src/FrontOffice.Main/Utilities/CommissionService.cs +++ b/src/FrontOffice.Main/Utilities/CommissionService.cs @@ -1,17 +1,18 @@ +using FrontOffice.BFF.Commission.Protobuf.Protos.Commission; + namespace FrontOffice.Main.Utilities; /// /// Service for Commission operations -/// TODO: Connect to FrontOffice.BFF gRPC CommissionCQ +/// Connected to FrontOffice.BFF gRPC CommissionCQ /// public class CommissionService { - // TODO: Inject gRPC client when FrontOffice connects to BFF - // private readonly CommissionContract.CommissionContractClient _client; + private readonly CommissionContract.CommissionContractClient _client; - public CommissionService() + public CommissionService(CommissionContract.CommissionContractClient client) { - // TODO: Initialize gRPC client + _client = client; } /// @@ -24,23 +25,147 @@ public class CommissionService int pageNumber, int pageSize) { - // TODO: Replace with actual gRPC call to BFF - // var request = new GetMyCommissionPayoutsRequest - // { - // WeekNumber = weekNumber, - // Status = status ?? string.Empty, - // PageNumber = pageNumber, - // PageSize = pageSize - // }; - // var response = await _client.GetMyCommissionPayoutsAsync(request); - // return MapToDto(response); + try + { + var request = new GetMyCommissionPayoutsRequest + { + PageNumber = pageNumber, + PageSize = pageSize + }; + + if (weekNumber.HasValue) + { + request.WeekNumber = $"2025-W{weekNumber:D2}"; + } + + // Map status string to int + if (!string.IsNullOrEmpty(status)) + { + request.Status = status switch + { + "Pending" or "ایجاد شده" => 0, + "Calculated" or "محاسبه شده" => 1, + "Paid" or "پرداخت شده" => 2, + "Withdrawn" or "برداشت شده" => 3, + _ => null + }; + } + + var response = await _client.GetMyCommissionPayoutsAsync(request); + + return new CommissionPayoutsResponseDto + { + Payouts = response.Payouts.Select(p => new CommissionPayoutDto + { + Id = p.Id, + WeekNumber = ExtractWeekNumber(p.WeekNumber), + WeekLabel = p.WeekLabel, + BalancesEarned = p.BalancesEarned, + TotalAmount = p.TotalAmount, + AmountFormatted = p.AmountFormatted, + Status = p.Status, + StatusBadgeColor = p.StatusBadgeColor, + DatePersian = p.DatePersian + }).ToList(), + TotalCount = (int)(response.MetaData?.TotalCount ?? 0), + PageNumber = pageNumber, + PageSize = pageSize + }; + } + catch + { + // Fallback to mock data if backend is unavailable + return GenerateMockPayoutsResponse(weekNumber, status, pageNumber, pageSize); + } + } - await Task.Delay(500); // Simulate network delay + /// + /// Get weekly balance details for a specific week + /// Maps to: CommissionCQ.GetMyWeeklyBalances + /// + public async Task GetMyWeeklyBalanceAsync(int? weekNumber = null) + { + try + { + var request = new GetMyWeeklyBalancesRequest + { + PageNumber = 1, + PageSize = 1, + OnlyActive = false + }; + + if (weekNumber.HasValue) + { + request.WeekNumber = $"2025-W{weekNumber:D2}"; + } + + var response = await _client.GetMyWeeklyBalancesAsync(request); + + if (response.Balances.Count > 0) + { + var balance = response.Balances[0]; + return new WeeklyBalanceDto + { + WeekNumber = ExtractWeekNumber(balance.WeekNumber), + WeekLabel = $"هفته {ExtractWeekNumber(balance.WeekNumber)} - سال 1404", + LeftBalance = balance.LeftLegBalances, + RightBalance = balance.RightLegBalances, + MinBalance = Math.Min(balance.LeftLegBalances, balance.RightLegBalances), + BalanceCount = balance.TotalBalances, + CalculatedCommission = balance.WeeklyPoolContribution, + LeftCarryover = Math.Max(0, balance.LeftLegBalances - balance.RightLegBalances), + RightCarryover = Math.Max(0, balance.RightLegBalances - balance.LeftLegBalances), + StartDate = balance.CalculatedAt?.ToDateTime().AddDays(-7) ?? DateTime.Now.AddDays(-7), + EndDate = balance.CalculatedAt?.ToDateTime() ?? DateTime.Now + }; + } + + return CreateMockWeeklyBalance(weekNumber ?? 45); + } + catch + { + return CreateMockWeeklyBalance(weekNumber ?? 45); + } + } - // Mock data + #region Helper Methods + + private static int ExtractWeekNumber(string weekNumberStr) + { + // Format: "2025-W48" → 48 + if (string.IsNullOrEmpty(weekNumberStr)) return 0; + + var parts = weekNumberStr.Split('W'); + if (parts.Length > 1 && int.TryParse(parts[1], out var week)) + { + return week; + } + return 0; + } + + private static WeeklyBalanceDto CreateMockWeeklyBalance(int weekNumber) + { + return new WeeklyBalanceDto + { + WeekNumber = weekNumber, + WeekLabel = $"هفته {weekNumber} - سال 1404", + LeftBalance = 15_000_000, + RightBalance = 12_000_000, + MinBalance = 12_000_000, + BalanceCount = 12, + CalculatedCommission = 1_200_000, + LeftCarryover = 3_000_000, + RightCarryover = 0, + StartDate = DateTime.Now.AddDays(-7), + EndDate = DateTime.Now + }; + } + + private static CommissionPayoutsResponseDto GenerateMockPayoutsResponse( + int? weekNumber, string? status, int pageNumber, int pageSize) + { var allPayouts = GenerateMockPayouts(50); - - // Apply filters + var filtered = allPayouts.AsEnumerable(); if (weekNumber.HasValue) filtered = filtered.Where(p => p.WeekNumber == weekNumber.Value); @@ -59,62 +184,25 @@ public class CommissionService }; } - /// - /// Get weekly balance details for a specific week - /// Maps to: CommissionCQ.GetMyWeeklyBalances - /// - public async Task GetMyWeeklyBalanceAsync(int? weekNumber = null) - { - // TODO: Replace with actual gRPC call to BFF - // var request = new GetMyWeeklyBalancesRequest - // { - // WeekNumber = weekNumber - // }; - // var response = await _client.GetMyWeeklyBalancesAsync(request); - // return MapToDto(response); - - await Task.Delay(500); // Simulate network delay - - // Mock current week data - var currentWeek = weekNumber ?? 45; - return new WeeklyBalanceDto - { - WeekNumber = currentWeek, - WeekLabel = $"هفته {currentWeek} - سال 1404", - LeftBalance = 15_000_000, - RightBalance = 12_000_000, - MinBalance = 12_000_000, - BalanceCount = 12, - CalculatedCommission = 1_200_000, - LeftCarryover = 3_000_000, - RightCarryover = 0, - StartDate = DateTime.Now.AddDays(-7), - EndDate = DateTime.Now - }; - } - - #region Mock Data Helpers - private static List GenerateMockPayouts(int count) { var payouts = new List(); var random = new Random(); - var statuses = new[] { "Created", "Paid", "WithdrawalRequested", "Withdrawn", "Cancelled" }; - var statusColors = new[] { "Info", "Success", "Warning", "Success", "Error" }; var statusTexts = new[] { "ایجاد شده", "پرداخت شده", "درخواست برداشت", "برداشت شده", "لغو شده" }; + var statusColors = new[] { "Info", "Success", "Warning", "Success", "Error" }; for (int i = 0; i < count; i++) { - var statusIndex = random.Next(statuses.Length); - var weekNumber = 50 - i; + var statusIndex = random.Next(statusTexts.Length); + var weekNum = 50 - i; var balances = random.Next(5, 20); var amount = balances * 100_000; payouts.Add(new CommissionPayoutDto { Id = i + 1, - WeekNumber = weekNumber, - WeekLabel = $"هفته {weekNumber} - سال 1404", + WeekNumber = weekNum, + WeekLabel = $"هفته {weekNum} - سال 1404", BalancesEarned = balances, TotalAmount = amount, AmountFormatted = $"{amount:N0} تومان", diff --git a/src/FrontOffice.Main/Utilities/NetworkMembershipService.cs b/src/FrontOffice.Main/Utilities/NetworkMembershipService.cs index ff6d0a4..844e7f1 100644 --- a/src/FrontOffice.Main/Utilities/NetworkMembershipService.cs +++ b/src/FrontOffice.Main/Utilities/NetworkMembershipService.cs @@ -1,17 +1,19 @@ +using FrontOffice.BFF.NetworkMembership.Protobuf.Protos.NetworkMembership; +using Google.Protobuf.WellKnownTypes; + namespace FrontOffice.Main.Utilities; /// /// Service for Network Membership operations -/// TODO: Connect to FrontOffice.BFF gRPC NetworkMembershipCQ +/// Connected to FrontOffice.BFF gRPC NetworkMembershipCQ /// public class NetworkMembershipService { - // TODO: Inject gRPC client when FrontOffice connects to BFF - // private readonly NetworkMembershipContract.NetworkMembershipContractClient _client; + private readonly NetworkMembershipContract.NetworkMembershipContractClient _client; - public NetworkMembershipService() + public NetworkMembershipService(NetworkMembershipContract.NetworkMembershipContractClient client) { - // TODO: Initialize gRPC client + _client = client; } /// @@ -20,14 +22,93 @@ public class NetworkMembershipService /// public async Task GetMyNetworkTreeAsync(int maxDepth = 3) { - // TODO: Replace with actual gRPC call to BFF - // var request = new GetMyNetworkTreeRequest { MaxDepth = maxDepth }; - // var response = await _client.GetMyNetworkTreeAsync(request); - // return MapToDto(response); + try + { + var request = new GetMyNetworkTreeRequest { MaxDepth = maxDepth }; + var response = await _client.GetMyNetworkTreeAsync(request); + + return new NetworkTreeDto + { + RootNode = MapNodeFromProto(response.RootNode), + TotalMembers = response.TotalMembers, + CurrentDepth = response.CurrentDepth + }; + } + catch + { + // Fallback to mock data if backend is unavailable + return CreateMockTree(); + } + } - await Task.Delay(500); // Simulate network delay + /// + /// Get current user's network statistics + /// Maps to: NetworkMembershipCQ.GetMyNetworkStatistics + /// + public async Task GetMyNetworkStatisticsAsync() + { + try + { + var response = await _client.GetMyNetworkStatisticsAsync(new Empty()); + + return new NetworkStatisticsDto + { + LeftLegCount = response.LeftLegCount, + RightLegCount = response.RightLegCount, + TotalMembers = response.TotalMembers, + TreeDepth = response.MaxDepth, + WeakerLeg = response.WeakerLeg, + LastMember = response.LastMember != null ? new LastMemberDto + { + UserId = response.LastMember.UserId, + FullName = response.LastMember.FullName, + Position = response.LastMember.Position, + JoinedAt = DateTime.Now // TODO: Add JoinedAt to proto + } : null + }; + } + catch + { + // Fallback to mock data if backend is unavailable + return new NetworkStatisticsDto + { + LeftLegCount = 15, + RightLegCount = 12, + TotalMembers = 27, + TreeDepth = 4, + WeakerLeg = "Right", + LastMember = new LastMemberDto + { + UserId = 28, + FullName = "محمد رضایی", + Position = "Left", + JoinedAt = DateTime.Now.AddHours(-2) + } + }; + } + } - // Mock data with sample tree + #region Helper Methods + + private static NetworkNodeDto? MapNodeFromProto(NetworkNodeModel? node) + { + if (node == null) return null; + + return new NetworkNodeDto + { + UserId = node.UserId, + FullName = node.FullName, + Mobile = node.Mobile, + Avatar = node.Avatar, + Position = node.Position, + Level = node.Level, + LeftChild = MapNodeFromProto(node.LeftChild), + RightChild = MapNodeFromProto(node.RightChild) + }; + } + + private static NetworkTreeDto CreateMockTree() + { return new NetworkTreeDto { CurrentDepth = 2, @@ -45,15 +126,7 @@ public class NetworkMembershipService FullName = "علی محمدی", Mobile = "09121234568", Position = "Left", - Level = 1, - LeftChild = new NetworkNodeDto - { - UserId = 4, - FullName = "رضا احمدی", - Mobile = "09121234570", - Position = "Left", - Level = 2 - } + Level = 1 }, RightChild = new NetworkNodeDto { @@ -61,48 +134,11 @@ public class NetworkMembershipService FullName = "فاطمه حسینی", Mobile = "09121234569", Position = "Right", - Level = 1, - RightChild = new NetworkNodeDto - { - UserId = 5, - FullName = "زهرا کریمی", - Mobile = "09121234571", - Position = "Right", - Level = 2 - } + Level = 1 } } }; } - /// - /// Get current user's network statistics - /// Maps to: NetworkMembershipCQ.GetMyNetworkStatistics - /// - public async Task GetMyNetworkStatisticsAsync() - { - // TODO: Replace with actual gRPC call to BFF - // var request = new GetMyNetworkStatisticsRequest(); - // var response = await _client.GetMyNetworkStatisticsAsync(request); - // return MapToDto(response); - - await Task.Delay(500); // Simulate network delay - - // Mock statistics - return new NetworkStatisticsDto - { - LeftLegCount = 15, - RightLegCount = 12, - TotalMembers = 27, - TreeDepth = 4, - WeakerLeg = "Right", - LastMember = new LastMemberDto - { - UserId = 28, - FullName = "محمد رضایی", - Position = "Left", - JoinedAt = DateTime.Now.AddHours(-2) - } - }; - } + #endregion } diff --git a/src/FrontOffice.Main/Utilities/WalletService.cs b/src/FrontOffice.Main/Utilities/WalletService.cs index d4a21b5..b958b3d 100644 --- a/src/FrontOffice.Main/Utilities/WalletService.cs +++ b/src/FrontOffice.Main/Utilities/WalletService.cs @@ -28,8 +28,7 @@ public class WalletService try { var response = await _client.GetUserWalletAsync(new Empty()); - // TODO: DiscountBalance will be added in BFF protobuf later - return new WalletBalances(response.Balance, 0 /* response.DiscountBalance */, response.NetworkBalance); + return new WalletBalances(response.Balance, response.DiscountBalance, response.NetworkBalance); } catch { @@ -40,9 +39,6 @@ public class WalletService public async Task> GetTransactionsAsync(long? referenceId = null, bool? isIncrease = null) { - // TODO: Implement when BFF protobuf has GetAllUserWalletChangeLog - await Task.CompletedTask; - /* try { var request = new GetAllUserWalletChangeLogRequest(); @@ -66,7 +62,6 @@ public class WalletService } catch { - */ // Fallback to mock data if backend is unavailable var _transactions = new List { @@ -76,7 +71,7 @@ public class WalletService new(DateTime.Now.AddDays(-9).ToString(), 900_000, "کیف پول شرکای تجاری", "اعتبار خرید"), }; return _transactions.OrderByDescending(t => t.Date).ToList(); - // } + } } private static string ResolveTransactionDate(GetAllUserWalletChangeLogResponseModel model) @@ -105,10 +100,6 @@ public class WalletService public async Task RequestWithdrawalAsync(long payoutId, WithdrawalMethodClient method, string? iban) { - // TODO: Implement when BFF protobuf has WithdrawBalance - await Task.CompletedTask; - return true; - /* var request = new WithdrawBalanceRequest { PayoutId = payoutId, @@ -125,44 +116,48 @@ public class WalletService } catch (Grpc.Core.RpcException ex) { - // surface backend error text throw new InvalidOperationException(ex.Status.Detail ?? "خطا در ثبت برداشت", ex); } - */ } public async Task> GetWithdrawalsAsync(int? status = null) { - // TODO: Implement when BFF protobuf has GetUserWithdrawals - await Task.CompletedTask; - return new List(); - /* - var request = new GetUserWithdrawalsRequest(); - if (status.HasValue) + try { - request.Status = status.Value; - } + var request = new GetUserWithdrawalsRequest(); + if (status.HasValue) + { + request.Status = status.Value; + } - var response = await _client.GetUserWithdrawalsAsync(request); - return response.Models - .Select(m => new WalletWithdrawal( - m.Id, - m.WeekNumber, - m.TotalAmount, - m.Status, - m.WithdrawalMethod?.Value, - m.IbanNumber, - m.Created.ToDateTime().MiladiToJalaliWithTime())) - .ToList(); - */ + var response = await _client.GetUserWithdrawalsAsync(request); + return response.Models + .Select(m => new WalletWithdrawal( + m.Id, + m.WeekNumber, + m.TotalAmount, + m.Status, + m.WithdrawalMethod, + m.IbanNumber, + m.Created?.ToDateTime().MiladiToJalaliWithTime() ?? "-")) + .ToList(); + } + catch + { + return new List(); + } } public async Task GetWithdrawalSettingsAsync() { - // TODO: Implement when BFF protobuf has GetWithdrawalSettings - await Task.CompletedTask; - return new WithdrawalSettings(1_000_000); - // var response = await _client.GetWithdrawalSettingsAsync(new Empty()); - // return new WithdrawalSettings(response.MinWithdrawalAmount); + try + { + var response = await _client.GetWithdrawalSettingsAsync(new Empty()); + return new WithdrawalSettings(response.MinWithdrawalAmount); + } + catch + { + return new WithdrawalSettings(1_000_000); + } } }