From 5a004e5c881fb57058c3c5dba477e924f5b57cda Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 22 Jun 2026 23:38:23 +0330 Subject: [PATCH] feat(club): enhance club management with new reports and update navigation - Added new report tabs for package purchases, cycles, and history in ClubHub.razor. - Updated MemberDetailsDialog.razor to improve display of membership details and alert for history access. - Introduced PaymentTransaction reporting in Transactions.razor with enhanced filtering options and summary statistics. - Updated BackOffice.csproj to conditionally reference the CMS Protobuf project for local development. - Modified NavMenu.razor to include a link to the new transaction report page. --- docs/ADMIN-SERVICES.md | 7 +- src/BackOffice/BackOffice.csproj | 9 +- .../Common/Configure/ConfigureService.cs | 10 + .../Common/Utilities/ReportLabels.cs | 46 ++++ src/BackOffice/Pages/Club/ClubHub.razor | 22 +- .../Club/Components/MemberDetailsDialog.razor | 101 +------- .../Pages/Club/Reports/ClubCyclesReport.razor | 153 ++++++++++++ .../Club/Reports/ClubHistoriesReport.razor | 153 ++++++++++++ .../Reports/ClubPackagePurchasesReport.razor | 130 ++++++++++ .../Pages/Payment/Transactions.razor | 223 ++++++++++++++---- .../Pages/Payment/Transactions.razor.cs | 95 -------- src/BackOffice/Shared/NavMenu.razor | 8 +- 12 files changed, 707 insertions(+), 250 deletions(-) create mode 100644 src/BackOffice/Common/Utilities/ReportLabels.cs create mode 100644 src/BackOffice/Pages/Club/Reports/ClubCyclesReport.razor create mode 100644 src/BackOffice/Pages/Club/Reports/ClubHistoriesReport.razor create mode 100644 src/BackOffice/Pages/Club/Reports/ClubPackagePurchasesReport.razor delete mode 100644 src/BackOffice/Pages/Payment/Transactions.razor.cs diff --git a/docs/ADMIN-SERVICES.md b/docs/ADMIN-SERVICES.md index bad2c63..ce55f9b 100644 --- a/docs/ADMIN-SERVICES.md +++ b/docs/ADMIN-SERVICES.md @@ -144,7 +144,10 @@ | 10.2 | `ManualPaymentContract.CreateManualPayment` | `ManualPaymentContract` | `ManualPaymentDialog` | ثبت پرداخت دستی (مثلاً Daya) | | 10.3 | `ManualPaymentContract.ApproveManualPayment` | `ManualPaymentContract` | ManualPayments, Dialog | تأیید پرداخت | | 10.4 | `ManualPaymentContract.RejectManualPayment` | `ManualPaymentContract` | ManualPayments, Dialog | رد پرداخت | -| 10.5 | `/payment/transactions` | — | Transactions page | **TODO — هنوز به API وصل نشده** | +| 10.5 | `PaymentTransactionContract.GetAllPaymentTransactionByFilter` | `PaymentTransactionContract` | `/payment/transactions` | گزارش read-only تراکنش درگاه | +| 10.6 | `UserPackagePurchaseContract.GetAllUserPackagePurchaseByFilter` | `UserPackagePurchaseContract` | `/club/purchases` | گزارش خرید پکیج | +| 10.7 | `ClubMembershipCycleContract.GetAllClubMembershipCycleByFilter` | `ClubMembershipCycleContract` | `/club/cycles` | گزارش دوره عضویت | +| 10.8 | `ClubMembershipCycleHistoryContract.GetAllClubMembershipCycleHistoryByFilter` | `ClubMembershipCycleHistoryContract` | `/club/history` (زیرتب دوره) | تاریخچه تغییرات دوره | --- @@ -206,7 +209,7 @@ | 15.1 | `ClubMembershipContract.GetAllClubMemberships` | `ClubMembershipContract` | `/club/members`, Index | لیست اعضا | | 15.2 | `ClubMembershipContract.ActivateClubMembership` | `ClubMembershipContract` | `ActivateClubDialog` | فعال‌سازی دستی عضویت | | 15.3 | `ClubMembershipContract.DeactivateClubMembership` | `ClubMembershipContract` | `DeactivateClubDialog` | غیرفعال‌سازی | -| 15.4 | `ClubMembershipContract.GetClubMembershipHistory` | `ClubMembershipContract` | `MemberDetailsDialog` | تاریخچه عضویت | +| 15.4 | `ClubMembershipContract.GetClubMembershipHistory` | `ClubMembershipContract` | `/club/history` (زیرتب عضویت) | گزارش تاریخچه عضویت | | 15.5 | `ClubMembershipContract.GetClubStatistics` | `ClubMembershipContract` | `/club/statistics` | آمار باشگاه | | 15.6 | `ConfigurationContract.GetClubFeatures` | `ConfigurationContract` | `/club/features` | لیست featureهای باشگاه | | 15.7 | `ClubMembershipContract.GetUserClubFeatures` | `ClubMembershipContract` | `/club/features` | featureهای یک کاربر | diff --git a/src/BackOffice/BackOffice.csproj b/src/BackOffice/BackOffice.csproj index ff82465..1b6aee7 100644 --- a/src/BackOffice/BackOffice.csproj +++ b/src/BackOffice/BackOffice.csproj @@ -138,9 +138,12 @@ - - - + + + + + + diff --git a/src/BackOffice/Common/Configure/ConfigureService.cs b/src/BackOffice/Common/Configure/ConfigureService.cs index cdcf926..2f6835e 100644 --- a/src/BackOffice/Common/Configure/ConfigureService.cs +++ b/src/BackOffice/Common/Configure/ConfigureService.cs @@ -31,6 +31,10 @@ using CMSMicroservice.Protobuf.Protos.SitePageSettings; using CMSMicroservice.Protobuf.Protos.UserContract; using CMSMicroservice.Protobuf.Protos.UserWallet; using CMSMicroservice.Protobuf.Protos.UserWalletHistory; +using CMSMicroservice.Protobuf.Protos.PaymentTransaction; +using CMSMicroservice.Protobuf.Protos.UserPackagePurchase; +using CMSMicroservice.Protobuf.Protos.ClubMembershipCycle; +using CMSMicroservice.Protobuf.Protos.ClubMembershipCycleHistory; using BackOffice.Common.Utilities; using BackOffice.Services.DiscountProduct; using BackOffice.Services.DiscountCategory; @@ -169,6 +173,12 @@ public static class ConfigureServices services.AddTransient(sp => new UserWalletContract.UserWalletContractClient(sp.GetRequiredService())); services.AddTransient(sp => new UserWalletHistoryContract.UserWalletHistoryContractClient(sp.GetRequiredService())); + // Admin Report Services + services.AddTransient(sp => new PaymentTransactionContract.PaymentTransactionContractClient(sp.GetRequiredService())); + services.AddTransient(sp => new UserPackagePurchaseContract.UserPackagePurchaseContractClient(sp.GetRequiredService())); + services.AddTransient(sp => new ClubMembershipCycleContract.ClubMembershipCycleContractClient(sp.GetRequiredService())); + services.AddTransient(sp => new ClubMembershipCycleHistoryContract.ClubMembershipCycleHistoryContractClient(sp.GetRequiredService())); + return services; } diff --git a/src/BackOffice/Common/Utilities/ReportLabels.cs b/src/BackOffice/Common/Utilities/ReportLabels.cs new file mode 100644 index 0000000..41fd281 --- /dev/null +++ b/src/BackOffice/Common/Utilities/ReportLabels.cs @@ -0,0 +1,46 @@ +namespace BackOffice.Common.Utilities; + +public static class ReportLabels +{ + public static string GetPaymentStatusLabel(bool success) => success ? "موفق" : "در انتظار / ناموفق"; + + public static string GetPurchaseMethodLabel(int method) => method switch + { + 1 => "وام دایا", + 2 => "خرید مستقیم", + _ => "نامشخص" + }; + + public static string GetClubMembershipActionLabel(int action) => action switch + { + 0 => "فعال‌سازی", + 1 => "غیرفعال‌سازی", + 2 => "ویرایش", + 3 => "اصلاح دستی", + _ => action.ToString() + }; + + public static string GetClubCycleActionLabel(int action) => action switch + { + 0 => "شروع دوره", + 1 => "ورود به حالت جادویی", + 2 => "پایان حالت جادویی", + 3 => "تکمیل دوره", + 4 => "لغو", + 5 => "اصلاح دستی", + _ => action.ToString() + }; + + public static string GetMagicStateLabel(int? magicStarted, DateTime? started, DateTime? completed) + { + if (completed.HasValue) return "جادویی تکمیل‌شده"; + if (started.HasValue) return "در حالت جادویی"; + return "عادی"; + } + + public static string FormatActiveChange(bool oldActive, bool newActive) => + $"{(oldActive ? "فعال" : "غیرفعال")} → {(newActive ? "فعال" : "غیرفعال")}"; + + public static string FormatBoolChange(bool oldValue, bool newValue) => + $"{(oldValue ? "بله" : "خیر")} → {(newValue ? "بله" : "خیر")}"; +} diff --git a/src/BackOffice/Pages/Club/ClubHub.razor b/src/BackOffice/Pages/Club/ClubHub.razor index bc90f85..ef21fcd 100644 --- a/src/BackOffice/Pages/Club/ClubHub.razor +++ b/src/BackOffice/Pages/Club/ClubHub.razor @@ -1,17 +1,30 @@ @page "/club" @page "/club/members" +@page "/club/purchases" +@page "/club/cycles" +@page "/club/history" @page "/club/statistics" @attribute [Authorize] +@using BackOffice.Pages.Club.Reports
باشگاه مشتریان - + - + + + + + + + + + + @@ -25,7 +38,10 @@ var uri = Navigation.ToBaseRelativePath(Navigation.Uri).ToLowerInvariant().TrimEnd('/'); _activeTab = uri switch { - "club/statistics" => 1, + "club/purchases" => 1, + "club/cycles" => 2, + "club/history" => 3, + "club/statistics" => 4, _ => 0 }; } diff --git a/src/BackOffice/Pages/Club/Components/MemberDetailsDialog.razor b/src/BackOffice/Pages/Club/Components/MemberDetailsDialog.razor index 16912ef..9485092 100644 --- a/src/BackOffice/Pages/Club/Components/MemberDetailsDialog.razor +++ b/src/BackOffice/Pages/Club/Components/MemberDetailsDialog.razor @@ -33,23 +33,11 @@ پکیج: - @Member.PackageName - - - کد فعال‌سازی: - @Member.ActivationCode + @(string.IsNullOrEmpty(Member.PackageName) ? "—" : Member.PackageName) تاریخ فعال‌سازی: - @Member.ActivatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() - - - تاریخ انقضا: - - - @Member.ExpiresAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() - - + @(Member.ActivatedAt != null ? Member.ActivatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() : "—") وضعیت: @@ -61,60 +49,17 @@ - - منقضی شده: - - - @(Member.IsExpired ? "بله" : "خیر") - - - - - - - - - تاریخچه - - - تاریخ ایجاد: @Member.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() - - @if (_historyLoading) - { - - } - else if (_history.Any()) - { - - - عملیات - تاریخ - دلیل - توسط - - - - @context.Action - - @context.Created.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() - @(string.IsNullOrEmpty(context.Reason) ? "-" : context.Reason) - @(string.IsNullOrEmpty(context.PerformedBy) ? "-" : context.PerformedBy) - - - } - else - { - تاریخچه‌ای یافت نشد. - } + + + تاریخچه تغییرات عضویت در تب «گزارش تاریخچه» باشگاه قابل مشاهده است. + } @@ -126,39 +71,5 @@ @code { [CascadingParameter] IMudDialogInstance MudDialog { get; set; } [Parameter] public ClubMembershipModel Member { get; set; } - [Inject] public ClubMembershipContract.ClubMembershipContractClient ClubClient { get; set; } - - private List _history = new(); - private bool _historyLoading; - - protected override async Task OnInitializedAsync() - { - await LoadHistory(); - } - - private async Task LoadHistory() - { - _historyLoading = true; - try - { - var request = new GetClubMembershipHistoryRequest - { - UserId = Member.UserId, - PageIndex = 0, - PageSize = 50 - }; - var response = await ClubClient.GetClubMembershipHistoryAsync(request); - _history = response.Models?.ToList() ?? new(); - } - catch - { - _history = new(); - } - finally - { - _historyLoading = false; - } - } - private void Close() => MudDialog.Close(); } diff --git a/src/BackOffice/Pages/Club/Reports/ClubCyclesReport.razor b/src/BackOffice/Pages/Club/Reports/ClubCyclesReport.razor new file mode 100644 index 0000000..e2e4c09 --- /dev/null +++ b/src/BackOffice/Pages/Club/Reports/ClubCyclesReport.razor @@ -0,0 +1,153 @@ +@using BackOffice.Common.BaseComponents +@using BackOffice.Pages.AutoComplete +@using CMSMicroservice.Protobuf.Protos.ClubMembershipCycle +@using Google.Protobuf.WellKnownTypes + +@inject ClubMembershipCycleContract.ClubMembershipCycleContractClient CycleClient + + + گزارش دوره‌های عضویت باشگاه — هر خرید پکیج یک دور جدید ایجاد می‌کند. + + + + + + + + + + فقط دوره جاری + دوره‌های قبلی + + + + + عادی + در حالت جادویی + جادویی تکمیل‌شده + + + + + + + + + + + کل دوره‌ها + @_totalCount.ToString("N0") + + + + + دوره جاری + @_currentCount.ToString("N0") + + + + + جادویی فعال + @_magicActive.ToString("N0") + + + + + + + + + + + + @context.Item.PackageAmount.ToString("N0") + + + @ReportLabels.GetPurchaseMethodLabel(context.Item.PurchaseMethod) + + + @context.Item.PackagePurchasedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() + + + + @ReportLabels.GetMagicStateLabel(null, + context.Item.MagicStartedAt?.ToDateTime(), + context.Item.MagicCompletedAt?.ToDateTime()) + + + + + + @(context.Item.IsCurrentCycle ? "بله" : "خیر") + + + + + + موردی یافت نشد. + + + + + + + + +@code { + private BasePageComponent? _basePage; + private MudDataGrid? _grid; + private long? _userId; + private bool? _isCurrent; + private int? _magicState; + private DateTime? _fromDate, _toDate; + private long _totalCount, _currentCount, _magicActive; + + private GetAllClubMembershipCycleByFilterFilter BuildFilter() => new() + { + UserId = _userId > 0 ? _userId : null, + IsCurrentCycle = _isCurrent, + MagicState = _magicState, + PackagePurchasedFrom = _fromDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_fromDate.Value, DateTimeKind.Utc)) : null, + PackagePurchasedTo = _toDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc)) : null + }; + + private async Task> LoadData(GridState state) + { + try + { + var filter = BuildFilter(); + var summary = await CycleClient.GetClubMembershipCycleSummaryAsync(new GetClubMembershipCycleSummaryRequest { Filter = filter }); + _totalCount = summary.TotalCount; + _currentCount = summary.CurrentCycleCount; + _magicActive = summary.MagicActiveCount; + + var result = await CycleClient.GetAllClubMembershipCycleByFilterAsync(new GetAllClubMembershipCycleByFilterRequest + { + PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize }, + Filter = filter + }); + return new GridData + { + Items = result?.Models?.ToList() ?? new(), + TotalItems = (int)(result?.MetaData?.TotalCount ?? 0) + }; + } + catch (Exception ex) + { + Snackbar.Add($"خطا: {ex.Message}", Severity.Error); + return new GridData(); + } + } + + private async Task OnFilterSubmit() { if (_grid != null) await _grid.ReloadServerData(); } + private async Task OnFilterCleared() + { + _userId = null; _isCurrent = null; _magicState = null; _fromDate = _toDate = null; + if (_grid != null) await _grid.ReloadServerData(); + } +} diff --git a/src/BackOffice/Pages/Club/Reports/ClubHistoriesReport.razor b/src/BackOffice/Pages/Club/Reports/ClubHistoriesReport.razor new file mode 100644 index 0000000..11f9eca --- /dev/null +++ b/src/BackOffice/Pages/Club/Reports/ClubHistoriesReport.razor @@ -0,0 +1,153 @@ +@using BackOffice.Common.BaseComponents +@using BackOffice.Pages.AutoComplete +@using CMSMicroservice.Protobuf.Protos.ClubMembership +@using CMSMicroservice.Protobuf.Protos.ClubMembershipCycleHistory +@using Google.Protobuf.WellKnownTypes + +@inject ClubMembershipContract.ClubMembershipContractClient ClubClient +@inject ClubMembershipCycleHistoryContract.ClubMembershipCycleHistoryContractClient CycleHistoryClient + + + + + تاریخچه فعال/غیرفعال شدن عضویت باشگاه. + + + + + فعال‌سازی + غیرفعال‌سازی + ویرایش + اصلاح دستی + + جستجو + + + + + + + @ReportLabels.FormatActiveChange(context.Item.OldIsActive, context.Item.NewIsActive) + + + + @(context.Item.OldInitialContribution != null ? context.Item.OldInitialContribution.Value.ToString("N0") : "—") + → @(context.Item.NewInitialContribution != null ? context.Item.NewInitialContribution.Value.ToString("N0") : "—") + + + + @ReportLabels.GetClubMembershipActionLabel(context.Item.Action) + + + + + @context.Item.CreatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() + + + + موردی یافت نشد. + + + + + + + + + + ردیابی تغییرات دوره از زمان deploy فعال شد. رکوردهای قدیمی‌تر ممکن است خالی باشند. + + + + جستجو + + + + + + + + @ReportLabels.FormatBoolChange(context.Item.OldIsCurrentCycle, context.Item.NewIsCurrentCycle) + + + @ReportLabels.GetClubCycleActionLabel(context.Item.Action) + + + + + @context.Item.CreatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() + + + + موردی یافت نشد. + + + + + + + + +@code { + private MudDataGrid? _memGrid; + private MudDataGrid? _cycleGrid; + private long? _memUserId, _cycleUserId; + private int? _memAction; + + private async Task> LoadMembershipHistory(GridState state) + { + try + { + var result = await ClubClient.GetClubMembershipHistoryAsync(new GetClubMembershipHistoryRequest + { + PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize }, + Filter = new GetClubMembershipHistoryFilter + { + UserId = _memUserId > 0 ? _memUserId : null, + Action = _memAction + } + }); + return new GridData + { + Items = result?.Models?.ToList() ?? new(), + TotalItems = (int)(result?.MetaData?.TotalCount ?? 0) + }; + } + catch (Exception ex) + { + Snackbar.Add($"خطا: {ex.Message}", Severity.Error); + return new GridData(); + } + } + + private async Task> LoadCycleHistory(GridState state) + { + try + { + var result = await CycleHistoryClient.GetAllClubMembershipCycleHistoryByFilterAsync(new GetAllClubMembershipCycleHistoryByFilterRequest + { + PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize }, + Filter = new GetAllClubMembershipCycleHistoryByFilterFilter + { + UserId = _cycleUserId > 0 ? _cycleUserId : null + } + }); + return new GridData + { + Items = result?.Models?.ToList() ?? new(), + TotalItems = (int)(result?.MetaData?.TotalCount ?? 0) + }; + } + catch (Exception ex) + { + Snackbar.Add($"خطا: {ex.Message}", Severity.Error); + return new GridData(); + } + } + + private async Task ReloadMembership() { if (_memGrid != null) await _memGrid.ReloadServerData(); } + private async Task ReloadCycle() { if (_cycleGrid != null) await _cycleGrid.ReloadServerData(); } +} diff --git a/src/BackOffice/Pages/Club/Reports/ClubPackagePurchasesReport.razor b/src/BackOffice/Pages/Club/Reports/ClubPackagePurchasesReport.razor new file mode 100644 index 0000000..a4e7a7e --- /dev/null +++ b/src/BackOffice/Pages/Club/Reports/ClubPackagePurchasesReport.razor @@ -0,0 +1,130 @@ +@using BackOffice.Common.BaseComponents +@using BackOffice.Pages.AutoComplete +@using CMSMicroservice.Protobuf.Protos.UserPackagePurchase +@using Google.Protobuf.WellKnownTypes + +@inject UserPackagePurchaseContract.UserPackagePurchaseContractClient PurchaseClient + + + گزارش read-only خریدهای پکیج — فقط مشاهده و فیلتر. + + + + + + + + + + وام دایا + خرید مستقیم + + + + + + + + + + + تعداد + @_totalCount.ToString("N0") + + + + + جمع مبلغ (ریال) + @_totalAmount.ToString("N0") + + + + + + + + + + + @context.Item.UserName + @context.Item.UserMobile + + + + + + @ReportLabels.GetPurchaseMethodLabel(context.Item.PurchaseMethod) + + + @context.Item.Amount.ToString("N0") + + + @context.Item.PurchasedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() + + + + + + موردی یافت نشد. + + + + + + + + +@code { + private BasePageComponent? _basePage; + private MudDataGrid? _grid; + private long? _userId; + private int? _purchaseMethod; + private DateTime? _fromDate, _toDate; + private long _totalCount, _totalAmount; + + private GetAllUserPackagePurchaseByFilterFilter BuildFilter() => new() + { + UserId = _userId > 0 ? _userId : null, + PurchaseMethod = _purchaseMethod, + PurchasedFrom = _fromDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_fromDate.Value, DateTimeKind.Utc)) : null, + PurchasedTo = _toDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc)) : null + }; + + private async Task> LoadData(GridState state) + { + try + { + var filter = BuildFilter(); + var summary = await PurchaseClient.GetUserPackagePurchaseSummaryAsync(new GetUserPackagePurchaseSummaryRequest { Filter = filter }); + _totalCount = summary.TotalCount; + _totalAmount = summary.TotalAmount; + + var result = await PurchaseClient.GetAllUserPackagePurchaseByFilterAsync(new GetAllUserPackagePurchaseByFilterRequest + { + PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize }, + Filter = filter + }); + return new GridData + { + Items = result?.Models?.ToList() ?? new(), + TotalItems = (int)(result?.MetaData?.TotalCount ?? 0) + }; + } + catch (Exception ex) + { + Snackbar.Add($"خطا: {ex.Message}", Severity.Error); + return new GridData(); + } + } + + private async Task OnFilterSubmit() { if (_grid != null) await _grid.ReloadServerData(); } + private async Task OnFilterCleared() + { + _userId = null; _purchaseMethod = null; _fromDate = _toDate = null; + if (_grid != null) await _grid.ReloadServerData(); + } +} diff --git a/src/BackOffice/Pages/Payment/Transactions.razor b/src/BackOffice/Pages/Payment/Transactions.razor index ac7aef5..f25ecd1 100644 --- a/src/BackOffice/Pages/Payment/Transactions.razor +++ b/src/BackOffice/Pages/Payment/Transactions.razor @@ -1,78 +1,115 @@ @page "/payment/transactions" @using BackOffice.Common.BaseComponents +@using BackOffice.Pages.AutoComplete +@using CMSMicroservice.Protobuf.Protos.PaymentTransaction +@using Google.Protobuf.WellKnownTypes + +@inject PaymentTransactionContract.PaymentTransactionContractClient PaymentTxClient - - + + + گزارش read-only تراکنش‌های درگاه پرداخت. برای ثبت پرداخت دستی به «پرداخت‌های دستی» مراجعه کنید. + - - - در انتظار - موفق - ناموفق - بازگشت وجه + + + + + + + + + + + + + + + + + + + + + موفق + ناموفق / در انتظار - - نامشخص - خرید پکیج - پرداخت دستی - شارژ کیف‌پول - بازگشت وجه - + - + + + + تعداد + @_summaryTotalCount.ToString("N0") + + + + + جمع مبلغ (تومان) + @_summaryTotalAmount.ToString("N0") + + + + + موفق + @_summarySuccessful.ToString("N0") + + + + + در انتظار + @_summaryPending.ToString("N0") + + + + + + + گزارش تراکنش درگاه + + CSV + - - - - @context.Item.Amount.ToString("N0") - - + + + + + @context.Item.Amount.ToString("N0") + + + + - - @GetStatusText(context.Item.PaymentStatus) + + @ReportLabels.GetPaymentStatusLabel(context.Item.PaymentStatus) - - - @context.Item.PaymentDate.ToLocalTime().MiladiToJalaliWithTime() - - - - - @GetTypeText(context.Item.Type) - - - - - - - - + + + + @context.Item.CreatedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() - موردی یافت نشد. + + با فیلتر فعلی موردی یافت نشد — بازه تاریخ یا کاربر را تغییر دهید. + - @@ -80,3 +117,87 @@ +@code { + private BasePageComponent? _basePage; + private MudDataGrid? _dataGrid; + private long? _userId; + private string? _mobile, _authority, _refId, _orderId, _gateway; + private bool? _paymentStatus; + private DateTime? _fromDate, _toDate; + private long _summaryTotalCount, _summaryTotalAmount, _summarySuccessful, _summaryPending; + + private GetAllPaymentTransactionByFilterFilter BuildFilter() => new() + { + UserId = _userId > 0 ? _userId : null, + Mobile = string.IsNullOrWhiteSpace(_mobile) ? null : _mobile, + Authority = string.IsNullOrWhiteSpace(_authority) ? null : _authority, + RefId = string.IsNullOrWhiteSpace(_refId) ? null : _refId, + OrderId = string.IsNullOrWhiteSpace(_orderId) ? null : _orderId, + GatewayProvider = string.IsNullOrWhiteSpace(_gateway) ? null : _gateway, + PaymentStatus = _paymentStatus, + CreatedFrom = _fromDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_fromDate.Value, DateTimeKind.Utc)) : null, + CreatedTo = _toDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc)) : null + }; + + private async Task LoadSummary() + { + try + { + var s = await PaymentTxClient.GetPaymentTransactionSummaryAsync(new GetPaymentTransactionSummaryRequest { Filter = BuildFilter() }); + _summaryTotalCount = s.TotalCount; + _summaryTotalAmount = s.TotalAmount; + _summarySuccessful = s.SuccessfulCount; + _summaryPending = s.PendingCount; + } + catch { } + } + + private async Task> LoadData(GridState state) + { + try + { + await LoadSummary(); + var result = await PaymentTxClient.GetAllPaymentTransactionByFilterAsync(new GetAllPaymentTransactionByFilterRequest + { + PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = state.Page + 1, PageSize = state.PageSize }, + Filter = BuildFilter() + }); + return new GridData + { + Items = result?.Models?.ToList() ?? new(), + TotalItems = (int)(result?.MetaData?.TotalCount ?? 0) + }; + } + catch (Exception ex) + { + Snackbar.Add($"خطا: {ex.Message}", Severity.Error); + return new GridData(); + } + } + + private async Task OnFilterSubmit() { if (_dataGrid != null) await _dataGrid.ReloadServerData(); } + private async Task OnFilterCleared() + { + _userId = null; _mobile = _authority = _refId = _orderId = _gateway = null; + _paymentStatus = null; _fromDate = _toDate = null; + if (_dataGrid != null) await _dataGrid.ReloadServerData(); + } + + private async Task ExportCsv() + { + try + { + var result = await PaymentTxClient.GetAllPaymentTransactionByFilterAsync(new GetAllPaymentTransactionByFilterRequest + { + PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState { PageNumber = 1, PageSize = 5000 }, + Filter = BuildFilter() + }); + var sb = new System.Text.StringBuilder(); + sb.AppendLine("Id,UserId,Mobile,Gateway,Amount,Authority,RefId,Status,OrderId,Created"); + foreach (var r in result.Models) + sb.AppendLine($"{r.Id},{r.UserId},{r.Mobile},{r.GatewayProvider},{r.Amount},{r.Authority},{r.RefId},{r.PaymentStatus},{r.OrderId},{r.CreatedAt.ToDateTime():yyyy-MM-dd HH:mm}"); + await jsRuntime.InvokeVoidAsync("jsSaveAsFile", "payment-transactions.csv", sb.ToString()); + } + catch (Exception ex) { Snackbar.Add($"خطا در export: {ex.Message}", Severity.Error); } + } +} diff --git a/src/BackOffice/Pages/Payment/Transactions.razor.cs b/src/BackOffice/Pages/Payment/Transactions.razor.cs deleted file mode 100644 index 0dd1825..0000000 --- a/src/BackOffice/Pages/Payment/Transactions.razor.cs +++ /dev/null @@ -1,95 +0,0 @@ -using BackOffice.Common.BaseComponents; -using MudBlazor; - -namespace BackOffice.Pages.Payment; - -public partial class Transactions -{ - private BasePageComponent? _basePage; - private MudDataGrid? _dataGrid; - - private DateTime? _fromDate; - private DateTime? _toDate; - private int? _statusFilter; - private int? _typeFilter; - - private async Task> LoadData(GridState state) - { - // TODO: Connect to BackOffice.BFF Transactions when API is ready - await Task.CompletedTask; - - return new GridData - { - Items = Array.Empty(), - TotalItems = 0 - }; - } - - private async Task OnFilterSubmit() - { - if (_dataGrid != null) - await _dataGrid.ReloadServerData(); - } - - private async Task OnFilterCleared() - { - _fromDate = null; - _toDate = null; - _statusFilter = null; - _typeFilter = null; - if (_dataGrid != null) - await _dataGrid.ReloadServerData(); - } - - private void OpenDetails(TransactionModel model) - { - // TODO: Open TransactionDetailsDialog with model - } - - private static string GetStatusText(int status) - { - return status switch - { - 0 => "در انتظار", - 1 => "موفق", - 2 => "ناموفق", - 3 => "بازگشت وجه", - _ => "نامشخص" - }; - } - - private static Color GetStatusColor(int status) - { - return status switch - { - 0 => Color.Warning, - 1 => Color.Success, - 2 => Color.Error, - 3 => Color.Info, - _ => Color.Default - }; - } - - private static string GetTypeText(int type) - { - return type switch - { - 1 => "خرید پکیج", - 2 => "پرداخت دستی", - 3 => "شارژ کیف‌پول", - 4 => "بازگشت وجه", - _ => "نامشخص" - }; - } - - private class TransactionModel - { - public long Id { get; set; } - public string? RefId { get; set; } - public long Amount { get; set; } - public int PaymentStatus { get; set; } - public DateTime PaymentDate { get; set; } - public int Type { get; set; } - } -} - diff --git a/src/BackOffice/Shared/NavMenu.razor b/src/BackOffice/Shared/NavMenu.razor index 6a61a93..b360d7d 100644 --- a/src/BackOffice/Shared/NavMenu.razor +++ b/src/BackOffice/Shared/NavMenu.razor @@ -75,7 +75,7 @@ - اعضا و آمار + اعضا و گزارش‌ها } + + گزارش تراکنش درگاه + +