diff --git a/src/BackOffice/BackOffice.csproj b/src/BackOffice/BackOffice.csproj
index cc3aef2..605345f 100644
--- a/src/BackOffice/BackOffice.csproj
+++ b/src/BackOffice/BackOffice.csproj
@@ -143,7 +143,7 @@
-
+
diff --git a/src/BackOffice/Common/Utilities/ReportLabels.cs b/src/BackOffice/Common/Utilities/ReportLabels.cs
index 912915d..cc967fa 100644
--- a/src/BackOffice/Common/Utilities/ReportLabels.cs
+++ b/src/BackOffice/Common/Utilities/ReportLabels.cs
@@ -42,8 +42,9 @@ public static class ReportLabels
public static string GetPurchaseMethodLabel(int method) => method switch
{
- 1 => "وام دایا",
- 2 => "خرید مستقیم",
+ 1 => "دایا",
+ 2 => "درگاه",
+ 3 => "دستی",
_ => "نامشخص"
};
diff --git a/src/BackOffice/Pages/Club/ClubHub.razor b/src/BackOffice/Pages/Club/ClubHub.razor
index ef21fcd..6d0a626 100644
--- a/src/BackOffice/Pages/Club/ClubHub.razor
+++ b/src/BackOffice/Pages/Club/ClubHub.razor
@@ -1,6 +1,7 @@
@page "/club"
@page "/club/members"
@page "/club/purchases"
+@page "/club/customer-packages"
@page "/club/cycles"
@page "/club/history"
@page "/club/statistics"
@@ -18,6 +19,9 @@
+
+
+
@@ -39,9 +43,10 @@
_activeTab = uri switch
{
"club/purchases" => 1,
- "club/cycles" => 2,
- "club/history" => 3,
- "club/statistics" => 4,
+ "club/customer-packages" => 2,
+ "club/cycles" => 3,
+ "club/history" => 4,
+ "club/statistics" => 5,
_ => 0
};
}
diff --git a/src/BackOffice/Pages/Club/Reports/ClubPackagePurchasesReport.razor b/src/BackOffice/Pages/Club/Reports/ClubPackagePurchasesReport.razor
index a4e7a7e..3c49538 100644
--- a/src/BackOffice/Pages/Club/Reports/ClubPackagePurchasesReport.razor
+++ b/src/BackOffice/Pages/Club/Reports/ClubPackagePurchasesReport.razor
@@ -17,8 +17,9 @@
- وام دایا
- خرید مستقیم
+ دایا
+ دستی
+ درگاه
diff --git a/src/BackOffice/Pages/Club/Reports/Components/CustomerPackagePurchaseDetailsDialog.razor b/src/BackOffice/Pages/Club/Reports/Components/CustomerPackagePurchaseDetailsDialog.razor
new file mode 100644
index 0000000..5da3165
--- /dev/null
+++ b/src/BackOffice/Pages/Club/Reports/Components/CustomerPackagePurchaseDetailsDialog.razor
@@ -0,0 +1,105 @@
+@using CMSMicroservice.Protobuf.Protos.UserPackagePurchase
+@using Google.Protobuf.WellKnownTypes
+@using DateTimeConverterCL
+
+@inject UserPackagePurchaseContract.UserPackagePurchaseContractClient PurchaseClient
+
+
+
+
+ جزئیات خرید پکیج — @UserName
+
+
+
+ @if (_loading)
+ {
+
+ }
+ else if (_items.Count == 0)
+ {
+ خریدی برای این کاربر یافت نشد.
+ }
+ else
+ {
+
+
+ شناسه
+ پکیج
+ روش
+ مبلغ (ریال)
+ تاریخ خرید
+
+
+ @context.Id
+ @context.PackageName
+
+
+ @ReportLabels.GetPurchaseMethodLabel(context.PurchaseMethod)
+
+
+ @context.Amount.ToString("N0")
+
+ @(context.PurchasedAt != null
+ ? context.PurchasedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()
+ : "-")
+
+
+
+ }
+
+
+ بستن
+
+
+
+@code {
+ [CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!;
+ [Parameter] public long UserId { get; set; }
+ [Parameter] public string UserName { get; set; } = string.Empty;
+
+ private bool _loading = true;
+ private List _items = new();
+
+ protected override async Task OnInitializedAsync()
+ {
+ try
+ {
+ var result = await PurchaseClient.GetAllUserPackagePurchaseByFilterAsync(
+ new GetAllUserPackagePurchaseByFilterRequest
+ {
+ PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
+ {
+ PageNumber = 1,
+ PageSize = 500
+ },
+ Filter = new GetAllUserPackagePurchaseByFilterFilter
+ {
+ UserId = UserId
+ }
+ });
+
+ _items = result?.Models?
+ .OrderBy(x => x.PurchasedAt?.ToDateTime() ?? DateTime.MinValue)
+ .ToList() ?? new();
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"خطا در بارگذاری جزئیات: {ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ _loading = false;
+ }
+ }
+
+ private static Color GetMethodColor(int method) => method switch
+ {
+ 1 => Color.Info, // دایا
+ 2 => Color.Primary, // درگاه
+ 3 => Color.Secondary, // دستی
+ _ => Color.Default
+ };
+
+ private void Close() => MudDialog.Close();
+}
diff --git a/src/BackOffice/Pages/Club/Reports/CustomerPackagePurchasesReport.razor b/src/BackOffice/Pages/Club/Reports/CustomerPackagePurchasesReport.razor
new file mode 100644
index 0000000..66ed35e
--- /dev/null
+++ b/src/BackOffice/Pages/Club/Reports/CustomerPackagePurchasesReport.razor
@@ -0,0 +1,294 @@
+@using System.Text
+@using BackOffice.Common.BaseComponents
+@using BackOffice.Common.Utilities
+@using BackOffice.Pages.AutoComplete
+@using BackOffice.Pages.Club.Reports.Components
+@using CMSMicroservice.Protobuf.Protos.UserPackagePurchase
+@using Google.Protobuf.WellKnownTypes
+@using DateTimeConverterCL
+@using Microsoft.JSInterop
+
+@inject UserPackagePurchaseContract.UserPackagePurchaseContractClient PurchaseClient
+@inject IDialogService DialogService
+@inject IJSRuntime JsRuntime
+
+
+ گزارش تجمیعی مشتریان — اولین/آخرین پکیج، تفکیک دایا / دستی / درگاه. خروجی Excel مطابق فیلتر فعال است.
+
+
+
+
+
+
+
+
+
+ دایا
+ دستی
+ درگاه
+
+
+
+
+
+
+
+
+
+ خروجی Excel
+
+
+
+
+
+
+
+
+ @context.Item.UserName
+ @context.Item.UserMobile
+
+
+
+
+
+
+ @context.Item.FirstPackageName
+ @context.Item.FirstAmount.ToString("N0") ریال
+
+ @(context.Item.FirstPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-")
+
+
+
+
+
+
+
+ @context.Item.LastPackageName
+ @context.Item.LastAmount.ToString("N0") ریال
+
+ @(context.Item.LastPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-")
+
+
+ @ReportLabels.GetPurchaseMethodLabel(context.Item.LastPurchaseMethod)
+
+
+
+
+
+
+
+ @context.Item.PurchaseCount خرید
+ @context.Item.TotalAmount.ToString("N0") ریال
+
+
+
+
+
+ @context.Item.DayaCount عدد
+ @context.Item.DayaAmount.ToString("N0")
+
+
+
+
+ @context.Item.ManualCount عدد
+ @context.Item.ManualAmount.ToString("N0")
+
+
+
+
+ @context.Item.GatewayCount عدد
+ @context.Item.GatewayAmount.ToString("N0")
+
+
+
+
+
+ جزئیات
+
+
+
+
+
+ موردی یافت نشد.
+
+
+
+
+
+
+
+
+@code {
+ private BasePageComponent? _basePage;
+ private MudDataGrid? _grid;
+ private long? _userId;
+ private int? _purchaseMethod;
+ private DateTime? _fromDate, _toDate;
+ private bool _exporting;
+
+ private GetCustomerPackagePurchaseRollupFilter BuildFilter()
+ {
+ var filter = new GetCustomerPackagePurchaseRollupFilter();
+ if (_userId is > 0)
+ filter.UserId = _userId;
+ if (_purchaseMethod.HasValue)
+ filter.PurchaseMethod = _purchaseMethod.Value;
+ if (_fromDate.HasValue)
+ filter.PurchasedFrom = Timestamp.FromDateTime(DateTime.SpecifyKind(_fromDate.Value.Date, DateTimeKind.Utc));
+ if (_toDate.HasValue)
+ filter.PurchasedTo = Timestamp.FromDateTime(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Utc));
+ return filter;
+ }
+
+ private async Task> LoadData(GridState state)
+ {
+ try
+ {
+ var result = await PurchaseClient.GetCustomerPackagePurchaseRollupAsync(
+ new GetCustomerPackagePurchaseRollupRequest
+ {
+ 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 (_grid != null) await _grid.ReloadServerData();
+ }
+
+ private async Task OnFilterCleared()
+ {
+ _userId = null;
+ _purchaseMethod = null;
+ _fromDate = _toDate = null;
+ if (_grid != null) await _grid.ReloadServerData();
+ }
+
+ private async Task OpenDetails(CustomerPackagePurchaseRollupModel row)
+ {
+ var parameters = new DialogParameters
+ {
+ { nameof(CustomerPackagePurchaseDetailsDialog.UserId), row.UserId },
+ { nameof(CustomerPackagePurchaseDetailsDialog.UserName), row.UserName }
+ };
+ var options = new DialogOptions { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true };
+ await DialogService.ShowAsync("جزئیات خرید پکیج", parameters, options);
+ }
+
+ private async Task ExportToExcel()
+ {
+ _exporting = true;
+ try
+ {
+ var filter = BuildFilter();
+ var all = new List();
+ const int pageSize = 500;
+ var page = 1;
+ long totalCount;
+
+ do
+ {
+ var result = await PurchaseClient.GetCustomerPackagePurchaseRollupAsync(
+ new GetCustomerPackagePurchaseRollupRequest
+ {
+ PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
+ {
+ PageNumber = page,
+ PageSize = pageSize
+ },
+ Filter = filter
+ });
+
+ var batch = result?.Models?.ToList() ?? new();
+ all.AddRange(batch);
+ totalCount = result?.MetaData?.TotalCount ?? 0;
+ page++;
+ } while (all.Count < totalCount && page <= 50);
+
+ if (all.Count == 0)
+ {
+ Snackbar.Add("دادهای برای خروجی با فیلتر فعلی وجود ندارد.", Severity.Info);
+ return;
+ }
+
+ var headers = new[]
+ {
+ "شناسه کاربر", "نام مشتری", "موبایل",
+ "اولین پکیج", "مبلغ اولین", "تاریخ اولین",
+ "آخرین پکیج", "مبلغ آخرین", "تاریخ آخرین", "روش آخرین",
+ "تعداد خرید", "جمع مبلغ",
+ "تعداد دایا", "مبلغ دایا",
+ "تعداد دستی", "مبلغ دستی",
+ "تعداد درگاه", "مبلغ درگاه"
+ };
+
+ var rows = all.Select(o => new object?[]
+ {
+ o.UserId,
+ o.UserName,
+ o.UserMobile,
+ o.FirstPackageName,
+ o.FirstAmount,
+ o.FirstPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-",
+ o.LastPackageName,
+ o.LastAmount,
+ o.LastPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-",
+ ReportLabels.GetPurchaseMethodLabel(o.LastPurchaseMethod),
+ o.PurchaseCount,
+ o.TotalAmount,
+ o.DayaCount,
+ o.DayaAmount,
+ o.ManualCount,
+ o.ManualAmount,
+ o.GatewayCount,
+ o.GatewayAmount
+ });
+
+ var filename = $"customer-packages-{DateTime.Now:yyyyMMddHHmmss}.xls";
+ await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
+ CsvExportHelper.ToExcelXmlBase64("مشتریان پکیج", headers, rows));
+ Snackbar.Add($"خروجی Excel آماده شد ({all.Count} مشتری).", Severity.Success);
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"خطا در خروجی Excel: {ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ _exporting = false;
+ }
+ }
+
+ private static Color GetMethodColor(int method) => method switch
+ {
+ 1 => Color.Info,
+ 2 => Color.Primary,
+ 3 => Color.Secondary,
+ _ => Color.Default
+ };
+}
diff --git a/src/BackOffice/Shared/NavMenu.razor b/src/BackOffice/Shared/NavMenu.razor
index e228e37..c2c9776 100644
--- a/src/BackOffice/Shared/NavMenu.razor
+++ b/src/BackOffice/Shared/NavMenu.razor
@@ -77,6 +77,11 @@
Icon="@Icons.Material.Filled.Groups">
اعضا و گزارشها
+
+ گزارش مشتریان پکیج
+