Compare commits

..

34 Commits

Author SHA1 Message Date
masoodafar-web 3eff5b6dfa Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 10m5s
2026-08-09 02:33:49 +03:30
masoodafar-web aa97d25302 feat(report): optimize customer package purchases report by removing pagination and enhancing filter application
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m55s
- Simplified the data retrieval process by eliminating pagination, allowing for all matching records to be fetched in a single request.
- Improved filter application logic to streamline the report generation process.
- Updated the handling of the result set to ensure compatibility with the new data retrieval method.
2026-08-09 00:40:36 +03:30
masoodafar-web 24ae4ec15c feat(date-range-picker): enhance date range picker functionality with external parameters and notifications
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 7m20s
- Added support for external From and To parameters to synchronize date range selections.
- Implemented OnParametersSet to handle updates from parent components without triggering unnecessary updates.
- Introduced NotifyRangeChangedAsync to streamline notifications for date range changes.
- Refactored date range application logic to improve clarity and maintainability.
2026-08-09 00:02:54 +03:30
masoodafar-web c3ee46ff62 feat(club): update club reports and navigation with new customer package insights
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 6m14s
- Updated ReportLabels.cs to refine purchase method labels for clarity.
- Enhanced ClubHub.razor by adding a new tab for customer package reports and adjusting tab indices.
- Modified ClubPackagePurchasesReport.razor to reflect updated purchase method options.
- Updated NavMenu.razor to include a link to the new customer packages report.
- Bumped Foursat.CMSMicroservice.Protobuf version to 0.0.208 for compatibility with recent changes.
2026-08-08 21:59:30 +03:30
masoodafar-web e9f8328404 feat(export): enhance CSV and Excel export functionality across multiple pages
- Updated CsvExportHelper to support Excel XML format for exporting data, improving compatibility with Excel.
- Refactored export logic in DiscountOrdersMainPage, SalesReports, OrderSalesReports, and UserOrderMainPage to utilize the new Excel export method.
- Changed file extensions from .csv to .xls for better clarity in exported files.
- Improved header and row data handling for consistency in exported reports.
2026-08-08 21:43:54 +03:30
masoodafar-web e63b80fe47 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 24m44s
2026-07-26 00:52:14 +03:30
masoodafar-web 7d892c2f6a Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 20m45s
2026-06-26 19:30:31 +03:30
masoodafar-web 5d83eb4e05 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 5m32s
2026-06-20 23:46:06 +03:30
masoodafar-web 0a2c5f913b Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 6m49s
2026-06-08 02:09:47 +03:30
masoodafar-web 933cdb1094 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
Deploy blog slug UI fix to production.
2026-06-08 02:06:52 +03:30
masoodafar-web f2316eafe4 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 19m22s
2026-05-18 01:57:57 +03:30
masoodafar-web c4712a00ac Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 8m43s
2026-05-04 23:54:56 +03:30
masoodafar-web 52c8965704 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 5m44s
2026-05-01 00:15:07 +03:30
masoodafar-web 6d8d681046 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 6m29s
2026-04-28 23:30:42 +03:30
masoodafar-web aed6258fb3 fix: update GW_URL in Dockerfile to correct endpoint
Build and Deploy to Production / build-and-deploy (push) Successful in 15m55s
2026-03-15 13:27:48 +03:30
masoodafar-web e6d1d833ec fix: update GW_URL in backoffice deployment to correct endpoint
Build and Deploy to Production / build-and-deploy (push) Successful in 15m40s
2026-03-15 13:04:13 +03:30
masoodafar-web c0c7bb3ac5 fix: resolve MudBlazor v8 breaking changes (MUD0002 warnings)
Build and Deploy to Production / build-and-deploy (push) Successful in 16m3s
- MudListItem: Selected → Class with conditional CSS
- MudTextField: Dense → Margin="Margin.Dense"
- MudSwitch: remove HelperText (merged into Label)
- MudDatePicker: remove T parameter (no longer generic in v8)
2026-03-15 11:58:37 +03:30
masoodafar-web d0fe99492d fix: update proto to 0.0.192 on production (merge conflict chose wrong version) 2026-03-15 05:29:46 +03:30
masoodafar-web d38f50584c fix: resolve version conflict for Foursat.CMSMicroservice.Protobuf package
Build and Deploy to Production / build-and-deploy (push) Failing after 13m13s
2026-03-15 03:21:52 +03:30
masoodafar-web 1c67d7798a Merge branch 'kub-stage' into production 2026-03-15 03:21:44 +03:30
masoodafar-web ea7414dc7f fix: update Foursat.CMSMicroservice.Protobuf package version to 0.0.191
Build and Deploy to Production / build-and-deploy (push) Successful in 8m40s
2026-02-28 05:07:46 +03:30
masoodafar-web a450d9a001 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 2m41s
2026-02-28 04:46:38 +03:30
masoodafar-web 60c91438ec fix: rename wallet labels — اصلی، اعتباری، پاداش تیمی
Build and Deploy to Production / build-and-deploy (push) Successful in 11m53s
- شبکه → پاداش تیمی (WalletManagement, ManualPayments)
- تخفیف → اعتباری (ManualPayments, OrderDetails, DiscountOrders)
- طلایی → پاداش تیمی (WithdrawalReports)

Files: WalletManagementPage, ManualPayments, ManualPaymentDialog,
       WithdrawalReports, DiscountOrdersMainPage, OrderDetailsDialog
2026-02-23 23:26:59 +03:30
masoodafar-web 1d8f34ae6c chore: prevent appsettings.Production.json from merging — keep ours strategy
Build and Deploy to Production / build-and-deploy (push) Successful in 8m7s
2026-02-23 00:03:28 +03:30
masoodafar-web bdea2e8aef Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 7m46s
2026-02-22 22:23:35 +03:30
masoodafar-web e541aec115 merge: kub-stage → production (discount autocomplete + smart AddStockDialog + site pages admin)
Build and Deploy to Production / build-and-deploy (push) Successful in 8m9s
2026-02-18 01:01:29 +03:30
masoodafar-web 743403e4d4 fix: nginx image path library/nginx:alpine → nginx:alpine on production
Build and Deploy to Production / build-and-deploy (push) Successful in 7m20s
2026-02-17 02:09:03 +03:30
masoodafar-web 44fd5f29d9 merge: kub-stage into production — fix prod workflow + appsettings GwUrl=cms.kbs2.ir
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-02-17 02:00:29 +03:30
masoodafar-web fac3d2c85e Fix production workflow: use SERVER_PASSWORD, add docker login, use set image for deploy
Build and Deploy to Production / build-and-deploy (push) Failing after 6m25s
2026-02-17 01:24:32 +03:30
masoodafar-web 51c20116a7 Remove staging workflow files from production branch to prevent cross-deployment 2026-02-17 01:16:13 +03:30
masoodafar-web 4454b8dd3a Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 6s
Features merged:
- Discount shop management (CRUD, inventory, bulk edit)
- Blog and content management improvements
- Docker build fixes (NuGet package reference)
- UI improvements across multiple components

Production workflow and Dockerfile preserved.
2026-02-17 00:57:14 +03:30
masoodafar-web 2bbb1ba4c7 chore: Update production workflow to use Nexus registry (no proxy needed)
Build and Deploy to Production / build-and-deploy (push) Failing after 0s
- Changed Dockerfile base images to use 194.5.195.53:32082 (Nexus)
- Updated workflow to use docker-sshpass image from Nexus
- Removed HTTP_PROXY/HTTPS_PROXY dependencies
- Changed deploy method to SSH instead of kubeconfig
- Registry: 194.5.195.53:30080 (Gitea internal)
- K8S Server: 45.149.79.127 (Production)
2026-01-29 22:04:31 +03:30
masoodafar-web 978475360c Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 2m23s
2025-12-29 00:43:04 +03:30
masoodafar-web 230453e491 feat(config): add production configuration for gateway URL and authentication
Build and Deploy to Production / build-and-deploy (push) Successful in 4m5s
2025-12-27 07:46:54 +03:30
19 changed files with 647 additions and 153 deletions
-46
View File
@@ -1,46 +0,0 @@
name: Push nuget and docker image Actions Workflow
on:
push:
branches:
- stage-new
jobs:
Deploy:
runs-on: windows
steps:
- name: Checkout
uses: https://git.afrino.co/actions/checkout@v3
- name: Setup dotnet
uses: https://git.afrino.co/actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
- name: Remove Package Source
run: dotnet nuget remove source FourSat
continue-on-error: true
- name: Add Package Source
run: dotnet nuget add source --name FourSat --username systemuser --password sZSA7PTiv3pUSQZ https://git.afrino.co/api/packages/FourSat/nuget/index.json --store-password-in-clear-text
- name: Install dependencies
run: dotnet restore ".\src\BackOffice\BackOffice.csproj"
- name: Build
run: dotnet build ".\src\BackOffice\BackOffice.csproj" --configuration Release --no-restore
- name: Test
run: dotnet test ".\src\BackOffice\BackOffice.csproj" --no-restore --verbosity normal
- name: Recycle Apppool
run: |
& "C:\Windows\System32\inetsrv\appcmd.exe" recycle apppool /apppool.name:admin.kbs1.ir
shell: powershell
- name: Stop Website
run: |
& "C:\Windows\System32\inetsrv\appcmd.exe" stop site /site.name:admin.kbs1.ir
shell: powershell
- name: Publish
run: dotnet publish ".\src\BackOffice\BackOffice.csproj" -c Release -o publish
- name: Copy Publish To IIS Directory
run: Get-ChildItem -Path "publish\*" | Copy-Item -Destination "E:\kbs1.ir\admin.kbs1.ir\" -Recurse -Force
- name: Start Website
run: |
& "C:\Windows\System32\inetsrv\appcmd.exe" start site /site.name:admin.kbs1.ir
shell: powershell
+4 -3
View File
@@ -40,7 +40,7 @@ jobs:
done done
if ! docker info >/dev/null 2>&1; then if ! docker info >/dev/null 2>&1; then
echo "❌ Docker daemon failed to start after 3 minutes" echo "❌ Docker daemon failed to start"
exit 1 exit 1
fi fi
@@ -56,9 +56,10 @@ jobs:
- name: Build Docker Image - name: Build Docker Image
run: | run: |
cd src cd src
DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ DOCKER_BUILDKIT=0 docker build --network host -f BackOffice/Dockerfile \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod \ -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod \
-f BackOffice/Dockerfile . .
- name: Push to Registry - name: Push to Registry
run: | run: |
+1
View File
@@ -1,3 +1,4 @@
# Docs moved to totalDoc # Docs moved to totalDoc
See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation. See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation.
+1 -1
View File
@@ -143,7 +143,7 @@
<ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" /> <ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="!Exists('../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj')"> <ItemGroup Condition="!Exists('../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj')">
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.207" /> <PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.208" />
</ItemGroup> </ItemGroup>
<!-- ============================================================ --> <!-- ============================================================ -->
@@ -8,21 +8,46 @@ namespace BackOffice.Common.BaseComponents;
public partial class DateRangePicker public partial class DateRangePicker
{ {
private DateRange _dateRange = new(); private DateRange _dateRange = new();
private MudDateRangePicker _picker = default!;
private DateTime? _lastSyncedFrom;
private DateTime? _lastSyncedTo;
private MudDateRangePicker _picker;
[Parameter] public string Label { get; set; } = "انتخاب بازه زمانی"; [Parameter] public string Label { get; set; } = "انتخاب بازه زمانی";
[Parameter] public DateTime? DefaultStart { get; set; } [Parameter] public DateTime? DefaultStart { get; set; }
[Parameter] public DateTime? DefaultEnd { get; set; } [Parameter] public DateTime? DefaultEnd { get; set; }
[Parameter] public EventCallback<DateRange> OnChanged { get; set; } [Parameter] public EventCallback<DateRange> OnChanged { get; set; }
[Parameter] public DateTime? From { get; set; }
[Parameter] public EventCallback<DateTime?> FromChanged { get; set; }
[Parameter] public DateTime? To { get; set; }
[Parameter] public EventCallback<DateTime?> ToChanged { get; set; }
protected override void OnInitialized() protected override void OnInitialized()
{ {
base.OnInitialized(); base.OnInitialized();
if (DefaultStart.HasValue) var start = From ?? DefaultStart;
_dateRange.Start = DefaultStart.Value.Date; var end = To ?? DefaultEnd;
ApplyExternalRange(start, end);
if (DefaultEnd.HasValue) _lastSyncedFrom = From;
_dateRange.End = DefaultEnd.Value.Date; _lastSyncedTo = To;
} }
protected override void OnParametersSet()
{
// فقط وقتی والد From/To را عوض کرده (مثلاً پاک کردن فیلتر)، نه هنگام انتخاب داخل پیکر
if (From == _lastSyncedFrom && To == _lastSyncedTo)
return;
_lastSyncedFrom = From;
_lastSyncedTo = To;
ApplyExternalRange(From, To);
}
private void ApplyExternalRange(DateTime? start, DateTime? end)
{
_dateRange = new DateRange(start?.Date, end?.Date);
}
public CultureInfo GetPersianCulture() public CultureInfo GetPersianCulture()
{ {
var culture = new CultureInfo("fa-IR"); var culture = new CultureInfo("fa-IR");
@@ -53,19 +78,35 @@ public partial class DateRangePicker
culture.NumberFormat.NumberNegativePattern = 0; culture.NumberFormat.NumberNegativePattern = 0;
return culture; return culture;
} }
private async Task OnClickOK() private async Task OnClickOK()
{ {
if (_picker.DateRange is not null) var range = _picker.DateRange ?? _dateRange;
{ _dateRange = range ?? new DateRange();
await OnChanged.InvokeAsync(_picker.DateRange); await NotifyRangeChangedAsync(_dateRange);
}
await _picker.CloseAsync(); await _picker.CloseAsync();
} }
private async Task OnClickClear() private async Task OnClickClear()
{ {
_dateRange = new DateRange(); _dateRange = new DateRange();
await OnChanged.InvokeAsync(_dateRange); await NotifyRangeChangedAsync(_dateRange);
await _picker.CloseAsync(); await _picker.CloseAsync();
} }
private async Task NotifyRangeChangedAsync(DateRange range)
{
var start = range.Start?.Date;
var end = range.End?.Date;
_lastSyncedFrom = start;
_lastSyncedTo = end;
if (From != start)
await FromChanged.InvokeAsync(start);
if (To != end)
await ToChanged.InvokeAsync(end);
await OnChanged.InvokeAsync(range);
}
} }
@@ -1,31 +1,35 @@
using System.Globalization; using System.Globalization;
using System.Net;
using System.Text; using System.Text;
namespace BackOffice.Common.Utilities; namespace BackOffice.Common.Utilities;
/// <summary> /// <summary>
/// خروجی CSV سازگار با Excel (UTF-8 BOM + sep=, + اعداد بدون جداکننده هزارگان). /// خروجی سازگار با Excel فارسی:
/// - XML Spreadsheet (.xls) برای ستون‌بندی مطمئن + متن فارسی
/// - CSV با جداکننده ; به‌عنوان جایگزین سبک
/// </summary> /// </summary>
public static class CsvExportHelper public static class CsvExportHelper
{ {
public const char Separator = ','; /// <summary>جداکننده پیش‌فرض ویندوز فارسی برای CSV.</summary>
public const char Separator = ';';
public static string Escape(string? value) public static string Escape(string? value)
{ {
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
return string.Empty; return string.Empty;
if (value.Contains(Separator) || value.Contains('"') || value.Contains('\n') || value.Contains('\r')) // همیشه quote تا Excel فارسی ستون را نشکند
return $"\"{value.Replace("\"", "\"\"")}\""; return $"\"{value.Replace("\"", "\"\"")}\"";
return value;
} }
public static string Cell(object? value) => value switch public static string Cell(object? value) => value switch
{ {
null => string.Empty, null => Escape(string.Empty),
string s => Escape(s), string s => Escape(s),
bool b => b ? "1" : "0", bool b => b ? "1" : "0",
byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal
=> Convert.ToString(value, CultureInfo.InvariantCulture) ?? "0",
IFormattable f => Escape(f.ToString(null, CultureInfo.InvariantCulture) ?? string.Empty), IFormattable f => Escape(f.ToString(null, CultureInfo.InvariantCulture) ?? string.Empty),
_ => Escape(Convert.ToString(value, CultureInfo.InvariantCulture)) _ => Escape(Convert.ToString(value, CultureInfo.InvariantCulture))
}; };
@@ -47,11 +51,95 @@ public static class CsvExportHelper
return "—"; return "—";
} }
public static string ToBase64(StringBuilder bodyWithoutSepHint) public static string ToBase64(StringBuilder body)
{ {
// Excel در لوکال فارسی با این خط، جداکننده کاما را درست تشخیص می‌دهد // CRLF صریح — Excel روی ویندوز با \n لینوکس گاهی به‌هم می‌ریزد
var content = "sep=," + Environment.NewLine + bodyWithoutSepHint; var text = body.ToString().Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", "\r\n");
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(content)).ToArray(); var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(text)).ToArray();
return Convert.ToBase64String(bytes); return Convert.ToBase64String(bytes);
} }
/// <summary>
/// خروجی SpreadsheetML که Excel به‌صورت ستون‌ستون و با UTF-8 درست باز می‌کند.
/// فایل را با پسوند .xls ذخیره کنید.
/// </summary>
public static string ToExcelXmlBase64(string sheetName, IReadOnlyList<string> headers, IEnumerable<object?[]> rows)
{
var sb = new StringBuilder();
sb.AppendLine(@"<?xml version=""1.0"" encoding=""UTF-8""?>");
sb.AppendLine(@"<?mso-application progid=""Excel.Sheet""?>");
sb.AppendLine(@"<Workbook xmlns=""urn:schemas-microsoft-com:office:spreadsheet""");
sb.AppendLine(@" xmlns:o=""urn:schemas-microsoft-com:office:office""");
sb.AppendLine(@" xmlns:x=""urn:schemas-microsoft-com:office:excel""");
sb.AppendLine(@" xmlns:ss=""urn:schemas-microsoft-com:office:spreadsheet""");
sb.AppendLine(@" xmlns:html=""http://www.w3.org/TR/REC-html40"">");
sb.AppendLine(@"<Styles>");
sb.AppendLine(@"<Style ss:ID=""Header""><Font ss:Bold=""1""/></Style>");
sb.AppendLine(@"<Style ss:ID=""Text""><NumberFormat ss:Format=""@""/></Style>");
sb.AppendLine(@"</Styles>");
sb.Append("<Worksheet ss:Name=\"").Append(XmlEscape(sheetName)).AppendLine("\">");
sb.AppendLine("<Table>");
sb.AppendLine("<Row>");
foreach (var header in headers)
{
sb.Append("<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">")
.Append(XmlEscape(header))
.AppendLine("</Data></Cell>");
}
sb.AppendLine("</Row>");
foreach (var row in rows)
{
sb.AppendLine("<Row>");
foreach (var cell in row)
AppendExcelCell(sb, cell);
sb.AppendLine("</Row>");
}
sb.AppendLine("</Table>");
sb.AppendLine("</Worksheet>");
sb.AppendLine("</Workbook>");
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
return Convert.ToBase64String(bytes);
}
private static void AppendExcelCell(StringBuilder sb, object? value)
{
switch (value)
{
case null:
sb.AppendLine(@"<Cell ss:StyleID=""Text""><Data ss:Type=""String""></Data></Cell>");
break;
case string s:
sb.Append(@"<Cell ss:StyleID=""Text""><Data ss:Type=""String"">")
.Append(XmlEscape(s))
.AppendLine("</Data></Cell>");
break;
case bool b:
sb.Append(@"<Cell><Data ss:Type=""Boolean"">")
.Append(b ? "1" : "0")
.AppendLine("</Data></Cell>");
break;
case byte or sbyte or short or ushort or int or uint or long or ulong:
sb.Append(@"<Cell><Data ss:Type=""Number"">")
.Append(Convert.ToString(value, CultureInfo.InvariantCulture))
.AppendLine("</Data></Cell>");
break;
case float or double or decimal:
sb.Append(@"<Cell><Data ss:Type=""Number"">")
.Append(Convert.ToString(value, CultureInfo.InvariantCulture))
.AppendLine("</Data></Cell>");
break;
default:
sb.Append(@"<Cell ss:StyleID=""Text""><Data ss:Type=""String"">")
.Append(XmlEscape(Convert.ToString(value, CultureInfo.InvariantCulture)))
.AppendLine("</Data></Cell>");
break;
}
}
private static string XmlEscape(string? value) =>
WebUtility.HtmlEncode(value ?? string.Empty);
} }
@@ -42,8 +42,9 @@ public static class ReportLabels
public static string GetPurchaseMethodLabel(int method) => method switch public static string GetPurchaseMethodLabel(int method) => method switch
{ {
1 => "وام دایا", 1 => "دایا",
2 => "خرید مستقیم", 2 => "درگاه",
3 => "دستی",
_ => "نامشخص" _ => "نامشخص"
}; };
+1 -1
View File
@@ -32,7 +32,7 @@ RUN chmod +x /docker-entrypoint.sh
# Default Blazor environment (override via K8s env var BLAZOR_ENVIRONMENT) # Default Blazor environment (override via K8s env var BLAZOR_ENVIRONMENT)
ENV BLAZOR_ENVIRONMENT=Staging ENV BLAZOR_ENVIRONMENT=Staging
# Default gateway URL for gRPC calls (injected into appsettings at startup) # Default gateway URL for gRPC calls (injected into appsettings at startup)
ENV GW_URL=https://cms.se.kbs1.ir ENV GW_URL=https://cms.kbs2.ir
EXPOSE 80 EXPOSE 80
CMD ["/docker-entrypoint.sh"] CMD ["/docker-entrypoint.sh"]
+2
View File
@@ -2,6 +2,8 @@
<configuration> <configuration>
<packageSources> <packageSources>
<clear /> <clear />
<!-- Local packages (dev builds) -->
<!-- <add key="Local" value="/home/masoud/Apps/project/FourSat/nupkg" /> -->
<!-- Nexus (hosts FourSat packages + proxies nuget.org) --> <!-- Nexus (hosts FourSat packages + proxies nuget.org) -->
<add key="Nexus" value="http://194.5.195.53:32081/repository/nuget-all/index.json" allowInsecureConnections="true" /> <add key="Nexus" value="http://194.5.195.53:32081/repository/nuget-all/index.json" allowInsecureConnections="true" />
</packageSources> </packageSources>
+8 -3
View File
@@ -1,6 +1,7 @@
@page "/club" @page "/club"
@page "/club/members" @page "/club/members"
@page "/club/purchases" @page "/club/purchases"
@page "/club/customer-packages"
@page "/club/cycles" @page "/club/cycles"
@page "/club/history" @page "/club/history"
@page "/club/statistics" @page "/club/statistics"
@@ -18,6 +19,9 @@
<MudTabPanel Text="گزارش خرید پکیج" Icon="@Icons.Material.Filled.ShoppingBag"> <MudTabPanel Text="گزارش خرید پکیج" Icon="@Icons.Material.Filled.ShoppingBag">
<ClubPackagePurchasesReport /> <ClubPackagePurchasesReport />
</MudTabPanel> </MudTabPanel>
<MudTabPanel Text="گزارش مشتریان پکیج" Icon="@Icons.Material.Filled.PeopleAlt">
<CustomerPackagePurchasesReport />
</MudTabPanel>
<MudTabPanel Text="گزارش دوره‌ها" Icon="@Icons.Material.Filled.Loop"> <MudTabPanel Text="گزارش دوره‌ها" Icon="@Icons.Material.Filled.Loop">
<ClubCyclesReport /> <ClubCyclesReport />
</MudTabPanel> </MudTabPanel>
@@ -39,9 +43,10 @@
_activeTab = uri switch _activeTab = uri switch
{ {
"club/purchases" => 1, "club/purchases" => 1,
"club/cycles" => 2, "club/customer-packages" => 2,
"club/history" => 3, "club/cycles" => 3,
"club/statistics" => 4, "club/history" => 4,
"club/statistics" => 5,
_ => 0 _ => 0
}; };
} }
@@ -17,8 +17,9 @@
<MudItem xs="12" sm="6" md="3"> <MudItem xs="12" sm="6" md="3">
<MudSelect T="int?" Clearable="true" Label="روش خرید" Variant="Variant.Outlined" <MudSelect T="int?" Clearable="true" Label="روش خرید" Variant="Variant.Outlined"
Margin="Margin.Dense" @bind-Value="_purchaseMethod"> Margin="Margin.Dense" @bind-Value="_purchaseMethod">
<MudSelectItem T="int?" Value="1">وام دایا</MudSelectItem> <MudSelectItem T="int?" Value="1">دایا</MudSelectItem>
<MudSelectItem T="int?" Value="2">خرید مستقیم</MudSelectItem> <MudSelectItem T="int?" Value="3">دستی</MudSelectItem>
<MudSelectItem T="int?" Value="2">درگاه</MudSelectItem>
</MudSelect> </MudSelect>
</MudItem> </MudItem>
<MudItem xs="12" sm="6" md="4"> <MudItem xs="12" sm="6" md="4">
@@ -0,0 +1,105 @@
@using CMSMicroservice.Protobuf.Protos.UserPackagePurchase
@using Google.Protobuf.WellKnownTypes
@using DateTimeConverterCL
@inject UserPackagePurchaseContract.UserPackagePurchaseContractClient PurchaseClient
<MudDialog>
<TitleContent>
<MudText Typo="Typo.h6">
جزئیات خرید پکیج — @UserName
</MudText>
</TitleContent>
<DialogContent>
@if (_loading)
{
<MudProgressLinear Indeterminate="true" Color="Color.Primary" Class="my-4" />
}
else if (_items.Count == 0)
{
<MudAlert Severity="Severity.Info" Dense="true">خریدی برای این کاربر یافت نشد.</MudAlert>
}
else
{
<MudTable Items="_items" Dense="true" Hover="true" Breakpoint="Breakpoint.Sm">
<HeaderContent>
<MudTh>شناسه</MudTh>
<MudTh>پکیج</MudTh>
<MudTh>روش</MudTh>
<MudTh>مبلغ (ریال)</MudTh>
<MudTh>تاریخ خرید</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="شناسه">@context.Id</MudTd>
<MudTd DataLabel="پکیج">@context.PackageName</MudTd>
<MudTd DataLabel="روش">
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined"
Color="@GetMethodColor(context.PurchaseMethod)">
@ReportLabels.GetPurchaseMethodLabel(context.PurchaseMethod)
</MudChip>
</MudTd>
<MudTd DataLabel="مبلغ">@context.Amount.ToString("N0")</MudTd>
<MudTd DataLabel="تاریخ">
@(context.PurchasedAt != null
? context.PurchasedAt.ToDateTime().ToLocalTime().MiladiToJalaliWithTime()
: "-")
</MudTd>
</RowTemplate>
</MudTable>
}
</DialogContent>
<DialogActions>
<MudButton OnClick="Close" Variant="Variant.Text">بستن</MudButton>
</DialogActions>
</MudDialog>
@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<UserPackagePurchaseModel> _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();
}
@@ -0,0 +1,282 @@
@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
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3">
گزارش تجمیعی مشتریان — اولین/آخرین پکیج، تفکیک دایا / دستی / درگاه. خروجی Excel مطابق فیلتر فعال است.
</MudAlert>
<BasePageComponent @ref="_basePage" OnSubmitClick="OnFilterSubmit" OnClearFilterClick="OnFilterCleared">
<Filters>
<MudItem xs="12" sm="6" md="3">
<UserAutoComplete Label="کاربر" @bind-SelectedUserId="_userId" />
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudSelect T="int?" Clearable="true" Label="روش خرید" Variant="Variant.Outlined"
Margin="Margin.Dense" @bind-Value="_purchaseMethod">
<MudSelectItem T="int?" Value="1">دایا</MudSelectItem>
<MudSelectItem T="int?" Value="3">دستی</MudSelectItem>
<MudSelectItem T="int?" Value="2">درگاه</MudSelectItem>
</MudSelect>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<DateRangePicker @bind-From="_fromDate" @bind-To="_toDate" Label="بازه خرید" />
</MudItem>
</Filters>
<Content>
<MudStack Row="true" Justify="Justify.FlexEnd" Class="mb-3">
<MudButton Variant="Variant.Outlined" Color="Color.Success"
StartIcon="@Icons.Material.Filled.FileDownload"
OnClick="ExportToExcel" Disabled="_exporting">
خروجی Excel
</MudButton>
</MudStack>
<MudDataGrid @ref="_grid" T="CustomerPackagePurchaseRollupModel" ServerData="LoadData" ReadOnly="true"
Hover="true" Dense="true" Height="calc(100vh - 420px)">
<Columns>
<TemplateColumn Title="مشتری">
<CellTemplate>
<MudStack Spacing="0">
<MudText Typo="Typo.body2">@context.Item.UserName</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">@context.Item.UserMobile</MudText>
</MudStack>
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="اولین پکیج">
<CellTemplate>
<MudStack Spacing="0">
<MudText Typo="Typo.body2">@context.Item.FirstPackageName</MudText>
<MudText Typo="Typo.caption">@context.Item.FirstAmount.ToString("N0") ریال</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">
@(context.Item.FirstPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-")
</MudText>
</MudStack>
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="آخرین پکیج">
<CellTemplate>
<MudStack Spacing="0">
<MudText Typo="Typo.body2">@context.Item.LastPackageName</MudText>
<MudText Typo="Typo.caption">@context.Item.LastAmount.ToString("N0") ریال</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">
@(context.Item.LastPurchasedAt?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-")
</MudText>
<MudChip T="string" Size="Size.Small" Class="mt-1" Variant="Variant.Filled"
Color="@GetMethodColor(context.Item.LastPurchaseMethod)">
@ReportLabels.GetPurchaseMethodLabel(context.Item.LastPurchaseMethod)
</MudChip>
</MudStack>
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="تعداد / جمع">
<CellTemplate>
<MudStack Spacing="0">
<MudText>@context.Item.PurchaseCount خرید</MudText>
<MudText Typo="Typo.caption">@context.Item.TotalAmount.ToString("N0") ریال</MudText>
</MudStack>
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="دایا">
<CellTemplate>
<MudText Typo="Typo.caption">@context.Item.DayaCount عدد</MudText>
<MudText Typo="Typo.caption">@context.Item.DayaAmount.ToString("N0")</MudText>
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="دستی">
<CellTemplate>
<MudText Typo="Typo.caption">@context.Item.ManualCount عدد</MudText>
<MudText Typo="Typo.caption">@context.Item.ManualAmount.ToString("N0")</MudText>
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="درگاه">
<CellTemplate>
<MudText Typo="Typo.caption">@context.Item.GatewayCount عدد</MudText>
<MudText Typo="Typo.caption">@context.Item.GatewayAmount.ToString("N0")</MudText>
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="عملیات">
<CellTemplate>
<MudButton Size="Size.Small" Variant="Variant.Outlined" Color="Color.Primary"
OnClick="() => OpenDetails(context.Item)">
جزئیات
</MudButton>
</CellTemplate>
</TemplateColumn>
</Columns>
<NoRecordsContent>
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Text" Class="my-4">موردی یافت نشد.</MudAlert>
</NoRecordsContent>
<PagerContent>
<MudDataGridPager T="CustomerPackagePurchaseRollupModel" PageSizeOptions="@(new int[] { 20, 50, 100 })"
InfoFormat="سطر {first_item} تا {last_item} از {all_items}"
RowsPerPageString="تعداد در صفحه" />
</PagerContent>
</MudDataGrid>
</Content>
</BasePageComponent>
@code {
private BasePageComponent? _basePage;
private MudDataGrid<CustomerPackagePurchaseRollupModel>? _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<GridData<CustomerPackagePurchaseRollupModel>> LoadData(GridState<CustomerPackagePurchaseRollupModel> 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<CustomerPackagePurchaseRollupModel>
{
Items = result?.Models?.ToList() ?? new(),
TotalItems = (int)(result?.MetaData?.TotalCount ?? 0)
};
}
catch (Exception ex)
{
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
return new GridData<CustomerPackagePurchaseRollupModel>();
}
}
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<CustomerPackagePurchaseDetailsDialog>("جزئیات خرید پکیج", parameters, options);
}
private async Task ExportToExcel()
{
_exporting = true;
try
{
// PageSize=0 → همهٔ ردیف‌های مطابق فیلتر، مستقل از صفحه‌بندی گرید
var result = await PurchaseClient.GetCustomerPackagePurchaseRollupAsync(
new GetCustomerPackagePurchaseRollupRequest
{
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
{
PageNumber = 1,
PageSize = 0
},
Filter = BuildFilter()
});
var all = result?.Models?.ToList() ?? new();
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
};
}
@@ -245,31 +245,32 @@ public partial class DiscountOrdersMainPage
try try
{ {
var sb = new StringBuilder(); var headers = new[]
sb.AppendLine(CsvExportHelper.Row(
"شناسه", "نام مشتری", "موبایل", "مبلغ کل", "کیف پول اعتباری", "پرداخت درگاه",
"تعداد آیتم", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت", "تاریخ ثبت", "آدرس"));
foreach (var o in _orders)
{ {
sb.AppendLine(CsvExportHelper.Row( "شناسه", "نام مشتری", "موبایل", "مبلغ کل", "کیف پول اعتباری", "پرداخت درگاه",
o.Id, "تعداد آیتم", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت", "تاریخ ثبت", "آدرس"
CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId), };
o.UserMobile,
o.TotalPrice,
o.DiscountBalanceUsed,
o.GatewayAmount,
o.ItemsCount,
GetPaymentStatusText(o.PaymentStatusValue),
GetStatusText(o.Status),
o.PaymentDate?.MiladiToJalaliWithTime() ?? "-",
o.Created?.MiladiToJalaliWithTime() ?? "-",
o.ShippingAddress ?? "-"));
}
var filename = $"discount-orders-{DateTime.Now:yyyyMMddHHmmss}.csv"; var rows = _orders.Select(o => new object?[]
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, CsvExportHelper.ToBase64(sb)); {
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success); o.Id,
CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId),
o.UserMobile ?? "",
o.TotalPrice,
o.DiscountBalanceUsed,
o.GatewayAmount,
o.ItemsCount,
GetPaymentStatusText(o.PaymentStatusValue),
GetStatusText(o.Status),
o.PaymentDate?.MiladiToJalaliWithTime() ?? "-",
o.Created?.MiladiToJalaliWithTime() ?? "-",
o.ShippingAddress ?? "-"
});
var filename = $"discount-orders-{DateTime.Now:yyyyMMddHHmmss}.xls";
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
CsvExportHelper.ToExcelXmlBase64("سفارش‌های اعتباری", headers, rows));
Snackbar.Add("خروجی Excel آماده شد.", Severity.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -530,26 +530,31 @@
return; return;
} }
var sb = new StringBuilder(); var headers = new[]
sb.AppendLine(CsvExportHelper.Row(
"شناسه", "نام مشتری", "موبایل", "تاریخ ثبت", "تعداد آیتم", "مبلغ نهایی", "تخفیف", "وضعیت"));
foreach (var o in _orders.Where(o => o.Created.HasValue).OrderBy(o => o.Created))
{ {
sb.AppendLine(CsvExportHelper.Row( "شناسه", "نام مشتری", "موبایل", "تاریخ ثبت", "تعداد آیتم", "مبلغ نهایی", "تخفیف", "وضعیت"
};
var rows = _orders
.OrderBy(o => o.Created ?? o.PaymentDate ?? DateTime.MaxValue)
.Select(o => new object?[]
{
o.Id, o.Id,
CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId), CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId),
o.UserMobile, o.UserMobile ?? "",
o.Created?.MiladiToJalaliWithTime() ?? "-", o.Created?.MiladiToJalaliWithTime()
?? o.PaymentDate?.MiladiToJalaliWithTime()
?? "-",
o.ItemsCount, o.ItemsCount,
o.GatewayAmount, o.GatewayAmount,
o.DiscountBalanceUsed, o.DiscountBalanceUsed,
GetStatusText(o.Status))); GetStatusText(o.Status)
} });
var filename = $"discount-sales-{DateTime.Now:yyyyMMddHHmmss}.csv"; var filename = $"discount-sales-{DateTime.Now:yyyyMMddHHmmss}.xls";
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, CsvExportHelper.ToBase64(sb)); await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success); CsvExportHelper.ToExcelXmlBase64("گزارش فروش اعتباری", headers, rows));
Snackbar.Add("خروجی Excel آماده شد.", Severity.Success);
} }
private async Task ExportToPdf() private async Task ExportToPdf()
@@ -244,25 +244,26 @@ public partial class OrderSalesReports
return; return;
} }
var sb = new StringBuilder(); var headers = new[]
sb.AppendLine(CsvExportHelper.Row(
"شناسه", "کاربر", "نام مشتری", "مبلغ", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت"));
foreach (var o in _orders)
{ {
sb.AppendLine(CsvExportHelper.Row( "شناسه", "کاربر", "نام مشتری", "مبلغ", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت"
o.Id, };
o.UserId,
CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId),
o.Amount,
o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده",
GetDeliveryStatusText(o.DeliveryStatus.GetHashCode()),
o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-"));
}
var filename = $"store-sales-{DateTime.Now:yyyyMMddHHmmss}.csv"; var rows = _orders.Select(o => new object?[]
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, CsvExportHelper.ToBase64(sb)); {
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success); o.Id,
o.UserId,
CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId),
o.Amount,
o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده",
GetDeliveryStatusText(o.DeliveryStatus.GetHashCode()),
o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-"
});
var filename = $"store-sales-{DateTime.Now:yyyyMMddHHmmss}.xls";
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
CsvExportHelper.ToExcelXmlBase64("گزارش فروش", headers, rows));
Snackbar.Add("خروجی Excel آماده شد.", Severity.Success);
} }
protected async Task ExportToPdf() protected async Task ExportToPdf()
@@ -329,26 +329,27 @@ public partial class UserOrderMainPage
return; return;
} }
var sb = new StringBuilder(); var headers = new[]
sb.AppendLine(CsvExportHelper.Row(
"شناسه", "کاربر", "نام مشتری", "مبلغ", "وضعیت پرداخت", "وضعیت ارسال", "روش پرداخت", "تاریخ پرداخت"));
foreach (var o in result.Models)
{ {
sb.AppendLine(CsvExportHelper.Row( "شناسه", "کاربر", "نام مشتری", "مبلغ", "وضعیت پرداخت", "وضعیت ارسال", "روش پرداخت", "تاریخ پرداخت"
o.Id, };
o.UserId,
CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId),
o.Amount,
GetPaymentStatusText(o.PaymentStatus.GetHashCode()),
GetDeliveryStatusText(o.DeliveryStatus.GetHashCode()),
GetPaymentMethodText(o.PaymentMethod.GetHashCode()),
o.PaymentDate?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-"));
}
var filename = $"user-orders-{DateTime.Now:yyyyMMddHHmmss}.csv"; var rows = result.Models.Select(o => new object?[]
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, CsvExportHelper.ToBase64(sb)); {
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success); o.Id,
o.UserId,
CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId),
o.Amount,
GetPaymentStatusText(o.PaymentStatus.GetHashCode()),
GetDeliveryStatusText(o.DeliveryStatus.GetHashCode()),
GetPaymentMethodText(o.PaymentMethod.GetHashCode()),
o.PaymentDate?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-"
});
var filename = $"user-orders-{DateTime.Now:yyyyMMddHHmmss}.xls";
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename,
CsvExportHelper.ToExcelXmlBase64("سفارش‌ها", headers, rows));
Snackbar.Add("خروجی Excel آماده شد.", Severity.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
+5
View File
@@ -77,6 +77,11 @@
Icon="@Icons.Material.Filled.Groups"> Icon="@Icons.Material.Filled.Groups">
اعضا و گزارش‌ها اعضا و گزارش‌ها
</MudNavLink> </MudNavLink>
<MudNavLink Match="NavLinkMatch.Prefix"
Href="/club/customer-packages"
Icon="@Icons.Material.Filled.PeopleAlt">
گزارش مشتریان پکیج
</MudNavLink>
<MudNavLink Match="NavLinkMatch.Prefix" <MudNavLink Match="NavLinkMatch.Prefix"
Href="/club/features" Href="/club/features"
Icon="@Icons.Material.Filled.Star"> Icon="@Icons.Material.Filled.Star">
@@ -1,5 +1,5 @@
{ {
"GwUrl": "https://cms.kbs1.ir", "GwUrl": "https://cms.kbs2.ir",
"Authentication": { "Authentication": {
"Authority": "https://ids.afrino.co/", "Authority": "https://ids.afrino.co/",
"ClientId": "client_backoffice_spa" "ClientId": "client_backoffice_spa"