diff --git a/src/BackOffice/Common/Utilities/CsvExportHelper.cs b/src/BackOffice/Common/Utilities/CsvExportHelper.cs
new file mode 100644
index 0000000..6e6b4cf
--- /dev/null
+++ b/src/BackOffice/Common/Utilities/CsvExportHelper.cs
@@ -0,0 +1,57 @@
+using System.Globalization;
+using System.Text;
+
+namespace BackOffice.Common.Utilities;
+
+///
+/// خروجی CSV سازگار با Excel (UTF-8 BOM + sep=, + اعداد بدون جداکننده هزارگان).
+///
+public static class CsvExportHelper
+{
+ public const char Separator = ',';
+
+ public static string Escape(string? value)
+ {
+ if (string.IsNullOrEmpty(value))
+ return string.Empty;
+
+ if (value.Contains(Separator) || value.Contains('"') || value.Contains('\n') || value.Contains('\r'))
+ return $"\"{value.Replace("\"", "\"\"")}\"";
+
+ return value;
+ }
+
+ public static string Cell(object? value) => value switch
+ {
+ null => string.Empty,
+ string s => Escape(s),
+ bool b => b ? "1" : "0",
+ IFormattable f => Escape(f.ToString(null, CultureInfo.InvariantCulture) ?? string.Empty),
+ _ => Escape(Convert.ToString(value, CultureInfo.InvariantCulture))
+ };
+
+ public static string Row(params object?[] cells) =>
+ string.Join(Separator, cells.Select(Cell));
+
+ ///
+ /// نام نمایشی مشتری؛ اگر خالی باشد موبایل یا شناسه کاربر.
+ ///
+ public static string CustomerName(string? fullName, string? mobile = null, long? userId = null)
+ {
+ if (!string.IsNullOrWhiteSpace(fullName))
+ return fullName.Trim();
+ if (!string.IsNullOrWhiteSpace(mobile))
+ return mobile.Trim();
+ if (userId is > 0)
+ return $"کاربر {userId}";
+ return "—";
+ }
+
+ public static string ToBase64(StringBuilder bodyWithoutSepHint)
+ {
+ // Excel در لوکال فارسی با این خط، جداکننده کاما را درست تشخیص میدهد
+ var content = "sep=," + Environment.NewLine + bodyWithoutSepHint;
+ var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(content)).ToArray();
+ return Convert.ToBase64String(bytes);
+ }
+}
diff --git a/src/BackOffice/Pages/DiscountShop/DiscountOrdersMainPage.razor.cs b/src/BackOffice/Pages/DiscountShop/DiscountOrdersMainPage.razor.cs
index 0dd0373..50db2e1 100644
--- a/src/BackOffice/Pages/DiscountShop/DiscountOrdersMainPage.razor.cs
+++ b/src/BackOffice/Pages/DiscountShop/DiscountOrdersMainPage.razor.cs
@@ -1,5 +1,6 @@
using System.Text;
using BackOffice.Common.BaseComponents;
+using BackOffice.Common.Utilities;
using BackOffice.Services.DiscountOrder;
using BackOffice.Pages.DiscountShop.Components;
using Microsoft.AspNetCore.Components;
@@ -245,30 +246,29 @@ public partial class DiscountOrdersMainPage
try
{
var sb = new StringBuilder();
- sb.AppendLine("شناسه,نام کاربر,موبایل,مبلغ کل,کیف پول اعتباری,پرداخت درگاه,تعداد آیتم,وضعیت پرداخت,وضعیت ارسال,تاریخ پرداخت,تاریخ ثبت,آدرس");
+ sb.AppendLine(CsvExportHelper.Row(
+ "شناسه", "نام مشتری", "موبایل", "مبلغ کل", "کیف پول اعتباری", "پرداخت درگاه",
+ "تعداد آیتم", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت", "تاریخ ثبت", "آدرس"));
foreach (var o in _orders)
{
- sb.AppendLine(string.Join(",",
+ sb.AppendLine(CsvExportHelper.Row(
o.Id,
- EscapeCsv(o.UserFullName),
- EscapeCsv(o.UserMobile),
- o.TotalPrice.ToString("N0"),
- o.DiscountBalanceUsed.ToString("N0"),
- o.GatewayAmount.ToString("N0"),
+ CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId),
+ o.UserMobile,
+ o.TotalPrice,
+ o.DiscountBalanceUsed,
+ o.GatewayAmount,
o.ItemsCount,
- EscapeCsv(GetPaymentStatusText(o.PaymentStatusValue)),
- EscapeCsv(GetStatusText(o.Status)),
+ GetPaymentStatusText(o.PaymentStatusValue),
+ GetStatusText(o.Status),
o.PaymentDate?.MiladiToJalaliWithTime() ?? "-",
o.Created?.MiladiToJalaliWithTime() ?? "-",
- EscapeCsv(o.ShippingAddress ?? "-")));
+ o.ShippingAddress ?? "-"));
}
- var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
- var base64 = Convert.ToBase64String(bytes);
var filename = $"discount-orders-{DateTime.Now:yyyyMMddHHmmss}.csv";
-
- await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
+ await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, CsvExportHelper.ToBase64(sb));
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
}
catch (Exception ex)
@@ -276,12 +276,4 @@ public partial class DiscountOrdersMainPage
Snackbar.Add($"خطا در خروجی Excel: {ex.Message}", Severity.Error);
}
}
-
- private static string EscapeCsv(string value)
- {
- if (string.IsNullOrEmpty(value)) return "";
- if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
- return $"\"{value.Replace("\"", "\"\"")}\"";
- return value;
- }
}
diff --git a/src/BackOffice/Pages/DiscountShop/SalesReports.razor b/src/BackOffice/Pages/DiscountShop/SalesReports.razor
index a58b7af..fc65969 100644
--- a/src/BackOffice/Pages/DiscountShop/SalesReports.razor
+++ b/src/BackOffice/Pages/DiscountShop/SalesReports.razor
@@ -531,14 +531,15 @@
}
var sb = new StringBuilder();
- sb.AppendLine("شناسه,نام کاربر,موبایل,تاریخ ثبت,تعداد آیتم,مبلغ نهایی,تخفیف,وضعیت");
+ sb.AppendLine(CsvExportHelper.Row(
+ "شناسه", "نام مشتری", "موبایل", "تاریخ ثبت", "تعداد آیتم", "مبلغ نهایی", "تخفیف", "وضعیت"));
foreach (var o in _orders.Where(o => o.Created.HasValue).OrderBy(o => o.Created))
{
- sb.AppendLine(string.Join(",",
+ sb.AppendLine(CsvExportHelper.Row(
o.Id,
- EscapeCsv(o.UserFullName),
- EscapeCsv(o.UserMobile),
+ CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId),
+ o.UserMobile,
o.Created?.MiladiToJalaliWithTime() ?? "-",
o.ItemsCount,
o.GatewayAmount,
@@ -546,11 +547,8 @@
GetStatusText(o.Status)));
}
- var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
- var base64 = Convert.ToBase64String(bytes);
var filename = $"discount-sales-{DateTime.Now:yyyyMMddHHmmss}.csv";
-
- await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
+ await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, CsvExportHelper.ToBase64(sb));
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
}
@@ -575,12 +573,12 @@
sb.AppendLine();
sb.AppendLine("جزئیات سفارشها:");
- sb.AppendLine("شناسه | نام کاربر | تاریخ | تعداد آیتم | مبلغ نهایی | تخفیف | وضعیت");
+ sb.AppendLine("شناسه | نام مشتری | تاریخ | تعداد آیتم | مبلغ نهایی | تخفیف | وضعیت");
foreach (var o in _orders.Where(o => o.Created.HasValue).OrderBy(o => o.Created))
{
sb.AppendLine(
- $"{o.Id} | {o.UserFullName} | {o.Created?.MiladiToJalaliWithTime() ?? "-"} | {o.ItemsCount} | {o.GatewayAmount:N0} | {o.DiscountBalanceUsed:N0} | {GetStatusText(o.Status)}");
+ $"{o.Id} | {CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId)} | {o.Created?.MiladiToJalaliWithTime() ?? "-"} | {o.ItemsCount} | {o.GatewayAmount:N0} | {o.DiscountBalanceUsed:N0} | {GetStatusText(o.Status)}");
}
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
@@ -590,14 +588,4 @@
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
Snackbar.Add("خروجی PDF (نسخه متنی) آماده شد.", Severity.Success);
}
-
- private static string EscapeCsv(string value)
- {
- if (string.IsNullOrEmpty(value))
- return "";
-
- var needsQuotes = value.Contains(',') || value.Contains('"') || value.Contains('\n');
- var escaped = value.Replace("\"", "\"\"");
- return needsQuotes ? $"\"{escaped}\"" : escaped;
- }
}
diff --git a/src/BackOffice/Pages/UserOrder/OrderSalesReports.razor.cs b/src/BackOffice/Pages/UserOrder/OrderSalesReports.razor.cs
index a49698c..4fe7a70 100644
--- a/src/BackOffice/Pages/UserOrder/OrderSalesReports.razor.cs
+++ b/src/BackOffice/Pages/UserOrder/OrderSalesReports.razor.cs
@@ -1,4 +1,5 @@
using System.Text;
+using BackOffice.Common.Utilities;
using CMSMicroservice.Protobuf.Protos;
using CMSMicroservice.Protobuf.Protos.UserOrder;
using Microsoft.AspNetCore.Components;
@@ -244,25 +245,23 @@ public partial class OrderSalesReports
}
var sb = new StringBuilder();
- sb.AppendLine("شناسه,کاربر,نام کاربر,مبلغ,وضعیت پرداخت,وضعیت ارسال,تاریخ پرداخت");
+ sb.AppendLine(CsvExportHelper.Row(
+ "شناسه", "کاربر", "نام مشتری", "مبلغ", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت"));
foreach (var o in _orders)
{
- sb.AppendLine(string.Join(",",
+ sb.AppendLine(CsvExportHelper.Row(
o.Id,
o.UserId,
- EscapeCsv(o.UserFullName),
+ CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId),
o.Amount,
- EscapeCsv(o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده"),
- EscapeCsv(GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())),
+ o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده",
+ GetDeliveryStatusText(o.DeliveryStatus.GetHashCode()),
o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-"));
}
- var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
- var base64 = Convert.ToBase64String(bytes);
var filename = $"store-sales-{DateTime.Now:yyyyMMddHHmmss}.csv";
-
- await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
+ await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, CsvExportHelper.ToBase64(sb));
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
}
@@ -292,7 +291,7 @@ public partial class OrderSalesReports
foreach (var o in _orders)
{
sb.AppendLine(
- $"{o.Id} | {o.UserFullName} | {o.Amount:N0} | {(o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده")} | {GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())} | {(o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-")}");
+ $"{o.Id} | {CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId)} | {o.Amount:N0} | {(o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده")} | {GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())} | {(o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-")}");
}
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
@@ -351,12 +350,4 @@ public partial class OrderSalesReports
}
};
}
-
- private static string EscapeCsv(string value)
- {
- if (string.IsNullOrEmpty(value)) return "";
- if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
- return $"\"{value.Replace("\"", "\"\"")}\"";
- return value;
- }
}
diff --git a/src/BackOffice/Pages/UserOrder/UserOrderMainPage.razor.cs b/src/BackOffice/Pages/UserOrder/UserOrderMainPage.razor.cs
index 2681678..04aaeb4 100644
--- a/src/BackOffice/Pages/UserOrder/UserOrderMainPage.razor.cs
+++ b/src/BackOffice/Pages/UserOrder/UserOrderMainPage.razor.cs
@@ -1,8 +1,9 @@
-using System.Text.Json;
-using System.Text;
+using System.Text;
+using System.Text.Json;
+using BackOffice.Common.BaseComponents;
+using BackOffice.Common.Utilities;
using CMSMicroservice.Protobuf.Protos;
using CMSMicroservice.Protobuf.Protos.UserOrder;
-using BackOffice.Common.BaseComponents;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using MudBlazor;
@@ -242,6 +243,15 @@ public partial class UserOrderMainPage
};
}
+ // Domain PaymentStatus: Success=0, Reject=1, Pending=2
+ private static string GetPaymentStatusText(int status) => status switch
+ {
+ 0 => "پرداخت شده",
+ 1 => "پرداخت ناموفق",
+ 2 => "در انتظار پرداخت",
+ _ => "نامشخص"
+ };
+
private async Task OpenChangeStatus(DataModel model)
{
var parameters = new DialogParameters
@@ -320,26 +330,24 @@ public partial class UserOrderMainPage
}
var sb = new StringBuilder();
- sb.AppendLine("شناسه,کاربر,نام کاربر,مبلغ,وضعیت پرداخت,وضعیت ارسال,روش پرداخت,تاریخ پرداخت");
+ sb.AppendLine(CsvExportHelper.Row(
+ "شناسه", "کاربر", "نام مشتری", "مبلغ", "وضعیت پرداخت", "وضعیت ارسال", "روش پرداخت", "تاریخ پرداخت"));
foreach (var o in result.Models)
{
- sb.AppendLine(string.Join(",",
+ sb.AppendLine(CsvExportHelper.Row(
o.Id,
o.UserId,
- EscapeCsv(o.UserFullName),
+ CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId),
o.Amount,
- EscapeCsv(o.PaymentStatus.ToString()),
- EscapeCsv(GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())),
- EscapeCsv(GetPaymentMethodText(o.PaymentMethod.GetHashCode())),
+ GetPaymentStatusText(o.PaymentStatus.GetHashCode()),
+ GetDeliveryStatusText(o.DeliveryStatus.GetHashCode()),
+ GetPaymentMethodText(o.PaymentMethod.GetHashCode()),
o.PaymentDate?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-"));
}
- var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
- var base64 = Convert.ToBase64String(bytes);
var filename = $"user-orders-{DateTime.Now:yyyyMMddHHmmss}.csv";
-
- await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
+ await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, CsvExportHelper.ToBase64(sb));
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
}
catch (Exception ex)
@@ -347,12 +355,4 @@ public partial class UserOrderMainPage
Snackbar.Add($"خطا در خروجی Excel: {ex.Message}", Severity.Error);
}
}
-
- private static string EscapeCsv(string value)
- {
- if (string.IsNullOrEmpty(value)) return "";
- if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
- return $"\"{value.Replace("\"", "\"\"")}\"";
- return value;
- }
}