feat:商户管理
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using System.Globalization;
|
||||
using TakeoutSaaS.Domain.Common.Enums;
|
||||
using TakeoutSaaS.Domain.Merchants.Entities;
|
||||
using TakeoutSaaS.Domain.Merchants.Enums;
|
||||
using TakeoutSaaS.Domain.Merchants.Services;
|
||||
using TakeoutSaaS.Domain.Stores.Entities;
|
||||
using TakeoutSaaS.Domain.Stores.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.App.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 商户导出服务实现(PDF)。
|
||||
/// </summary>
|
||||
public sealed class MerchantExportService : IMerchantExportService
|
||||
{
|
||||
public MerchantExportService()
|
||||
{
|
||||
QuestPDF.Settings.License = LicenseType.Community;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<byte[]> ExportToPdfAsync(
|
||||
Merchant merchant,
|
||||
string? tenantName,
|
||||
IReadOnlyList<Store> stores,
|
||||
IReadOnlyList<MerchantAuditLog> auditLogs,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(merchant);
|
||||
|
||||
var safeStores = stores ?? Array.Empty<Store>();
|
||||
var safeAuditLogs = auditLogs ?? Array.Empty<MerchantAuditLog>();
|
||||
|
||||
var document = Document.Create(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(24);
|
||||
page.DefaultTextStyle(x => x.FontSize(10));
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
column.Item().Text("Merchant Export").FontSize(16).SemiBold();
|
||||
|
||||
column.Item().Element(section => BuildBasicSection(section, merchant, tenantName));
|
||||
column.Item().Element(section => BuildStoresSection(section, safeStores, cancellationToken));
|
||||
column.Item().Element(section => BuildAuditSection(section, safeAuditLogs, cancellationToken));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Task.FromResult(document.GeneratePdf());
|
||||
}
|
||||
|
||||
private static void BuildBasicSection(IContainer container, Merchant merchant, string? tenantName)
|
||||
{
|
||||
container.Border(1).BorderColor(Colors.Grey.Lighten2).Padding(10).Column(column =>
|
||||
{
|
||||
column.Spacing(4);
|
||||
column.Item().Text("Basic Information").SemiBold();
|
||||
column.Item().Text($"Merchant: {merchant.BrandName}");
|
||||
column.Item().Text($"Tenant: {tenantName ?? "-"} (ID: {merchant.TenantId})");
|
||||
column.Item().Text($"Operating Mode: {ResolveOperatingMode(merchant.OperatingMode)}");
|
||||
column.Item().Text($"Status: {merchant.Status}");
|
||||
column.Item().Text($"Frozen: {(merchant.IsFrozen ? "Yes" : "No")}");
|
||||
column.Item().Text($"License Number: {merchant.BusinessLicenseNumber ?? "-"}");
|
||||
column.Item().Text($"Legal Representative: {merchant.LegalPerson ?? "-"}");
|
||||
column.Item().Text($"Registered Address: {merchant.Address ?? "-"}");
|
||||
column.Item().Text($"Contact Phone: {merchant.ContactPhone}");
|
||||
column.Item().Text($"Contact Email: {merchant.ContactEmail ?? "-"}");
|
||||
column.Item().Text($"Approved At: {FormatDateTime(merchant.ApprovedAt)}");
|
||||
column.Item().Text($"Approved By: {merchant.ApprovedBy?.ToString() ?? "-"}");
|
||||
});
|
||||
}
|
||||
|
||||
private static void BuildStoresSection(IContainer container, IReadOnlyList<Store> stores, CancellationToken cancellationToken)
|
||||
{
|
||||
container.Border(1).BorderColor(Colors.Grey.Lighten2).Padding(10).Column(column =>
|
||||
{
|
||||
column.Spacing(4);
|
||||
column.Item().Text("Stores").SemiBold();
|
||||
|
||||
if (stores.Count == 0)
|
||||
{
|
||||
column.Item().Text("No stores.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < stores.Count; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var store = stores[i];
|
||||
column.Item().Text($"{i + 1}. {store.Name} | {ResolveStoreStatus(store.Status)} | {store.Address ?? "-"} | {store.Phone ?? "-"}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void BuildAuditSection(IContainer container, IReadOnlyList<MerchantAuditLog> auditLogs, CancellationToken cancellationToken)
|
||||
{
|
||||
container.Border(1).BorderColor(Colors.Grey.Lighten2).Padding(10).Column(column =>
|
||||
{
|
||||
column.Spacing(4);
|
||||
column.Item().Text("Audit History").SemiBold();
|
||||
|
||||
if (auditLogs.Count == 0)
|
||||
{
|
||||
column.Item().Text("No audit records.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < auditLogs.Count; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var log = auditLogs[i];
|
||||
var title = string.IsNullOrWhiteSpace(log.Title) ? log.Action.ToString() : log.Title;
|
||||
column.Item().Text($"{i + 1}. {title} | {log.OperatorName ?? "-"} | {FormatDateTime(log.CreatedAt)}");
|
||||
if (!string.IsNullOrWhiteSpace(log.Description))
|
||||
{
|
||||
column.Item().Text($" {log.Description}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static string ResolveOperatingMode(OperatingMode? mode)
|
||||
=> mode switch
|
||||
{
|
||||
OperatingMode.SameEntity => "SameEntity",
|
||||
OperatingMode.DifferentEntity => "DifferentEntity",
|
||||
_ => "-"
|
||||
};
|
||||
|
||||
private static string ResolveStoreStatus(StoreStatus status)
|
||||
=> status switch
|
||||
{
|
||||
StoreStatus.Closed => "Closed",
|
||||
StoreStatus.Preparing => "Preparing",
|
||||
StoreStatus.Operating => "Operating",
|
||||
StoreStatus.Suspended => "Suspended",
|
||||
_ => status.ToString()
|
||||
};
|
||||
|
||||
private static string FormatDateTime(DateTime? value)
|
||||
=> value.HasValue ? value.Value.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture) : "-";
|
||||
}
|
||||
Reference in New Issue
Block a user