merge: bring finance report api changes into dev
Some checks failed
Build and Deploy TenantApi + SkuWorker / build-and-deploy (push) Failing after 26s

This commit is contained in:
2026-03-04 22:03:57 +08:00
40 changed files with 14930 additions and 2 deletions

View File

@@ -0,0 +1,218 @@
namespace TakeoutSaaS.Application.App.Finance.Reports.Dto;
/// <summary>
/// 经营报表列表行 DTO。
/// </summary>
public sealed class FinanceBusinessReportListItemDto
{
/// <summary>
/// 报表 ID。
/// </summary>
public string ReportId { get; set; } = string.Empty;
/// <summary>
/// 日期文案。
/// </summary>
public string DateText { get; set; } = string.Empty;
/// <summary>
/// 营业额。
/// </summary>
public decimal RevenueAmount { get; set; }
/// <summary>
/// 订单数。
/// </summary>
public int OrderCount { get; set; }
/// <summary>
/// 客单价。
/// </summary>
public decimal AverageOrderValue { get; set; }
/// <summary>
/// 退款率(百分数)。
/// </summary>
public decimal RefundRatePercent { get; set; }
/// <summary>
/// 成本总额。
/// </summary>
public decimal CostTotalAmount { get; set; }
/// <summary>
/// 净利润。
/// </summary>
public decimal NetProfitAmount { get; set; }
/// <summary>
/// 利润率(百分数)。
/// </summary>
public decimal ProfitRatePercent { get; set; }
/// <summary>
/// 状态编码。
/// </summary>
public string Status { get; set; } = string.Empty;
/// <summary>
/// 状态文案。
/// </summary>
public string StatusText { get; set; } = string.Empty;
/// <summary>
/// 是否可下载。
/// </summary>
public bool CanDownload { get; set; }
}
/// <summary>
/// 经营报表列表结果 DTO。
/// </summary>
public sealed class FinanceBusinessReportListResultDto
{
/// <summary>
/// 列表项。
/// </summary>
public List<FinanceBusinessReportListItemDto> Items { get; set; } = [];
/// <summary>
/// 总数。
/// </summary>
public int Total { get; set; }
/// <summary>
/// 页码。
/// </summary>
public int Page { get; set; }
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; set; }
}
/// <summary>
/// 经营报表 KPI DTO。
/// </summary>
public sealed class FinanceBusinessReportKpiDto
{
/// <summary>
/// 指标键。
/// </summary>
public string Key { get; set; } = string.Empty;
/// <summary>
/// 指标名称。
/// </summary>
public string Label { get; set; } = string.Empty;
/// <summary>
/// 指标值文本。
/// </summary>
public string ValueText { get; set; } = string.Empty;
/// <summary>
/// 同比变化率(百分数)。
/// </summary>
public decimal YoyChangeRate { get; set; }
/// <summary>
/// 环比变化率(百分数)。
/// </summary>
public decimal MomChangeRate { get; set; }
}
/// <summary>
/// 经营报表明细行 DTO。
/// </summary>
public sealed class FinanceBusinessReportBreakdownItemDto
{
/// <summary>
/// 明细键。
/// </summary>
public string Key { get; set; } = string.Empty;
/// <summary>
/// 明细名称。
/// </summary>
public string Label { get; set; } = string.Empty;
/// <summary>
/// 金额。
/// </summary>
public decimal Amount { get; set; }
/// <summary>
/// 占比(百分数)。
/// </summary>
public decimal RatioPercent { get; set; }
}
/// <summary>
/// 经营报表详情 DTO。
/// </summary>
public sealed class FinanceBusinessReportDetailDto
{
/// <summary>
/// 报表 ID。
/// </summary>
public string ReportId { get; set; } = string.Empty;
/// <summary>
/// 标题。
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// 周期类型编码。
/// </summary>
public string PeriodType { get; set; } = string.Empty;
/// <summary>
/// 状态编码。
/// </summary>
public string Status { get; set; } = string.Empty;
/// <summary>
/// 状态文案。
/// </summary>
public string StatusText { get; set; } = string.Empty;
/// <summary>
/// 关键指标。
/// </summary>
public List<FinanceBusinessReportKpiDto> Kpis { get; set; } = [];
/// <summary>
/// 收入明细(按渠道)。
/// </summary>
public List<FinanceBusinessReportBreakdownItemDto> IncomeBreakdowns { get; set; } = [];
/// <summary>
/// 成本明细(按类别)。
/// </summary>
public List<FinanceBusinessReportBreakdownItemDto> CostBreakdowns { get; set; } = [];
}
/// <summary>
/// 经营报表导出 DTO。
/// </summary>
public sealed class FinanceBusinessReportExportDto
{
/// <summary>
/// 文件名。
/// </summary>
public string FileName { get; set; } = string.Empty;
/// <summary>
/// Base64 文件内容。
/// </summary>
public string FileContentBase64 { get; set; } = string.Empty;
/// <summary>
/// 总记录数。
/// </summary>
public int TotalCount { get; set; }
}

View File

@@ -0,0 +1,112 @@
using System.Globalization;
using System.IO.Compression;
using MediatR;
using TakeoutSaaS.Application.App.Finance.Reports.Dto;
using TakeoutSaaS.Application.App.Finance.Reports.Queries;
using TakeoutSaaS.Domain.Finance.Repositories;
using TakeoutSaaS.Domain.Finance.Services;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Finance.Reports.Handlers;
/// <summary>
/// 经营报表批量导出处理器ZIPPDF + Excel
/// </summary>
public sealed class ExportFinanceBusinessReportBatchQueryHandler(
IFinanceBusinessReportRepository financeBusinessReportRepository,
IFinanceBusinessReportExportService financeBusinessReportExportService,
ITenantProvider tenantProvider)
: IRequestHandler<ExportFinanceBusinessReportBatchQuery, FinanceBusinessReportExportDto>
{
/// <inheritdoc />
public async Task<FinanceBusinessReportExportDto> Handle(
ExportFinanceBusinessReportBatchQuery request,
CancellationToken cancellationToken)
{
// 1. 读取租户上下文并归一化分页参数。
var tenantId = tenantProvider.GetCurrentTenantId();
var normalizedPage = Math.Max(1, request.Page);
var normalizedPageSize = Math.Clamp(request.PageSize, 1, 200);
// 2. 确保成本配置并补齐快照。
await financeBusinessReportRepository.EnsureDefaultCostProfilesAsync(
tenantId,
request.StoreId,
cancellationToken);
await financeBusinessReportRepository.QueueSnapshotsForPageAsync(
tenantId,
request.StoreId,
request.PeriodType,
normalizedPage,
normalizedPageSize,
cancellationToken);
// 3. 查询导出明细集合(允许实时补算)。
var details = await financeBusinessReportRepository.ListBatchDetailsAsync(
tenantId,
request.StoreId,
request.PeriodType,
normalizedPage,
normalizedPageSize,
allowRealtimeBuild: true,
cancellationToken);
// 4. 生成批量 PDF/Excel 并打包 ZIP。
var periodCode = FinanceBusinessReportMapping.ToPeriodTypeCode(request.PeriodType);
var zipBytes = await CreateZipAsync(
details,
periodCode,
financeBusinessReportExportService,
cancellationToken);
return new FinanceBusinessReportExportDto
{
FileName = string.Create(
CultureInfo.InvariantCulture,
$"business-report-batch-{request.StoreId}-{periodCode}-{DateTime.UtcNow:yyyyMMddHHmmss}.zip"),
FileContentBase64 = Convert.ToBase64String(zipBytes),
TotalCount = details.Count
};
}
private static async Task<byte[]> CreateZipAsync(
IReadOnlyList<Domain.Finance.Models.FinanceBusinessReportDetailSnapshot> details,
string periodCode,
IFinanceBusinessReportExportService exportService,
CancellationToken cancellationToken)
{
if (details.Count == 0)
{
using var emptyStream = new MemoryStream();
using (var emptyArchive = new ZipArchive(emptyStream, ZipArchiveMode.Create, true))
{
var entry = emptyArchive.CreateEntry("README.txt");
await using var writer = new StreamWriter(entry.Open());
await writer.WriteAsync("No business report data in current selection.");
}
return emptyStream.ToArray();
}
var pdfBytes = await exportService.ExportBatchPdfAsync(details, cancellationToken);
var excelBytes = await exportService.ExportBatchExcelAsync(details, cancellationToken);
using var stream = new MemoryStream();
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, true))
{
var pdfEntry = archive.CreateEntry($"business-report-{periodCode}.pdf");
await using (var pdfEntryStream = pdfEntry.Open())
{
await pdfEntryStream.WriteAsync(pdfBytes, cancellationToken);
}
var excelEntry = archive.CreateEntry($"business-report-{periodCode}.xlsx");
await using (var excelEntryStream = excelEntry.Open())
{
await excelEntryStream.WriteAsync(excelBytes, cancellationToken);
}
}
return stream.ToArray();
}
}

View File

@@ -0,0 +1,59 @@
using System.Globalization;
using MediatR;
using TakeoutSaaS.Application.App.Finance.Reports.Dto;
using TakeoutSaaS.Application.App.Finance.Reports.Queries;
using TakeoutSaaS.Domain.Finance.Repositories;
using TakeoutSaaS.Domain.Finance.Services;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Finance.Reports.Handlers;
/// <summary>
/// 经营报表 Excel 导出处理器。
/// </summary>
public sealed class ExportFinanceBusinessReportExcelQueryHandler(
IFinanceBusinessReportRepository financeBusinessReportRepository,
IFinanceBusinessReportExportService financeBusinessReportExportService,
ITenantProvider tenantProvider)
: IRequestHandler<ExportFinanceBusinessReportExcelQuery, FinanceBusinessReportExportDto>
{
/// <inheritdoc />
public async Task<FinanceBusinessReportExportDto> Handle(
ExportFinanceBusinessReportExcelQuery request,
CancellationToken cancellationToken)
{
// 1. 读取租户上下文并确保成本配置存在。
var tenantId = tenantProvider.GetCurrentTenantId();
await financeBusinessReportRepository.EnsureDefaultCostProfilesAsync(
tenantId,
request.StoreId,
cancellationToken);
// 2. 查询报表详情(允许实时补算)。
var detail = await financeBusinessReportRepository.GetDetailAsync(
tenantId,
request.StoreId,
request.ReportId,
allowRealtimeBuild: true,
cancellationToken);
if (detail is null)
{
throw new BusinessException(ErrorCodes.NotFound, "经营报表不存在");
}
// 3. 导出 Excel 并返回 Base64。
var fileBytes = await financeBusinessReportExportService.ExportSingleExcelAsync(detail, cancellationToken);
var periodCode = FinanceBusinessReportMapping.ToPeriodTypeCode(detail.PeriodType);
return new FinanceBusinessReportExportDto
{
FileName = string.Create(
CultureInfo.InvariantCulture,
$"business-report-{request.StoreId}-{periodCode}-{request.ReportId}.xlsx"),
FileContentBase64 = Convert.ToBase64String(fileBytes),
TotalCount = 1
};
}
}

View File

@@ -0,0 +1,59 @@
using System.Globalization;
using MediatR;
using TakeoutSaaS.Application.App.Finance.Reports.Dto;
using TakeoutSaaS.Application.App.Finance.Reports.Queries;
using TakeoutSaaS.Domain.Finance.Repositories;
using TakeoutSaaS.Domain.Finance.Services;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Finance.Reports.Handlers;
/// <summary>
/// 经营报表 PDF 导出处理器。
/// </summary>
public sealed class ExportFinanceBusinessReportPdfQueryHandler(
IFinanceBusinessReportRepository financeBusinessReportRepository,
IFinanceBusinessReportExportService financeBusinessReportExportService,
ITenantProvider tenantProvider)
: IRequestHandler<ExportFinanceBusinessReportPdfQuery, FinanceBusinessReportExportDto>
{
/// <inheritdoc />
public async Task<FinanceBusinessReportExportDto> Handle(
ExportFinanceBusinessReportPdfQuery request,
CancellationToken cancellationToken)
{
// 1. 读取租户上下文并确保成本配置存在。
var tenantId = tenantProvider.GetCurrentTenantId();
await financeBusinessReportRepository.EnsureDefaultCostProfilesAsync(
tenantId,
request.StoreId,
cancellationToken);
// 2. 查询报表详情(允许实时补算)。
var detail = await financeBusinessReportRepository.GetDetailAsync(
tenantId,
request.StoreId,
request.ReportId,
allowRealtimeBuild: true,
cancellationToken);
if (detail is null)
{
throw new BusinessException(ErrorCodes.NotFound, "经营报表不存在");
}
// 3. 导出 PDF 并返回 Base64。
var fileBytes = await financeBusinessReportExportService.ExportSinglePdfAsync(detail, cancellationToken);
var periodCode = FinanceBusinessReportMapping.ToPeriodTypeCode(detail.PeriodType);
return new FinanceBusinessReportExportDto
{
FileName = string.Create(
CultureInfo.InvariantCulture,
$"business-report-{request.StoreId}-{periodCode}-{request.ReportId}.pdf"),
FileContentBase64 = Convert.ToBase64String(fileBytes),
TotalCount = 1
};
}
}

View File

@@ -0,0 +1,178 @@
using System.Globalization;
using TakeoutSaaS.Application.App.Finance.Reports.Dto;
using TakeoutSaaS.Domain.Finance.Enums;
using TakeoutSaaS.Domain.Finance.Models;
namespace TakeoutSaaS.Application.App.Finance.Reports.Handlers;
/// <summary>
/// 经营报表映射工具。
/// </summary>
internal static class FinanceBusinessReportMapping
{
/// <summary>
/// 映射列表行 DTO。
/// </summary>
public static FinanceBusinessReportListItemDto ToListItem(FinanceBusinessReportListItemSnapshot source)
{
return new FinanceBusinessReportListItemDto
{
ReportId = source.ReportId.ToString(CultureInfo.InvariantCulture),
DateText = FormatPeriodText(source.PeriodType, source.PeriodStartAt, source.PeriodEndAt),
RevenueAmount = RoundMoney(source.RevenueAmount),
OrderCount = Math.Max(0, source.OrderCount),
AverageOrderValue = RoundMoney(source.AverageOrderValue),
RefundRatePercent = RoundPercent(source.RefundRate),
CostTotalAmount = RoundMoney(source.CostTotalAmount),
NetProfitAmount = RoundMoney(source.NetProfitAmount),
ProfitRatePercent = RoundPercent(source.ProfitRate),
Status = ToStatusCode(source.Status),
StatusText = ToStatusText(source.Status),
CanDownload = source.Status == FinanceBusinessReportStatus.Succeeded
};
}
/// <summary>
/// 映射详情 DTO。
/// </summary>
public static FinanceBusinessReportDetailDto ToDetail(FinanceBusinessReportDetailSnapshot source)
{
return new FinanceBusinessReportDetailDto
{
ReportId = source.ReportId.ToString(CultureInfo.InvariantCulture),
Title = BuildTitle(source.PeriodType, source.PeriodStartAt, source.PeriodEndAt),
PeriodType = ToPeriodTypeCode(source.PeriodType),
Status = ToStatusCode(source.Status),
StatusText = ToStatusText(source.Status),
Kpis = source.Kpis.Select(ToKpi).ToList(),
IncomeBreakdowns = source.IncomeBreakdowns.Select(ToBreakdown).ToList(),
CostBreakdowns = source.CostBreakdowns.Select(ToBreakdown).ToList()
};
}
/// <summary>
/// 周期类型编码。
/// </summary>
public static string ToPeriodTypeCode(FinanceBusinessReportPeriodType value)
{
return value switch
{
FinanceBusinessReportPeriodType.Daily => "daily",
FinanceBusinessReportPeriodType.Weekly => "weekly",
FinanceBusinessReportPeriodType.Monthly => "monthly",
_ => "daily"
};
}
private static FinanceBusinessReportKpiDto ToKpi(FinanceBusinessReportKpiSnapshot source)
{
return new FinanceBusinessReportKpiDto
{
Key = source.Key,
Label = source.Label,
ValueText = FormatKpiValue(source.Key, source.Value),
YoyChangeRate = RoundRate(source.YoyChangeRate),
MomChangeRate = RoundRate(source.MomChangeRate)
};
}
private static FinanceBusinessReportBreakdownItemDto ToBreakdown(FinanceBusinessReportBreakdownSnapshot source)
{
return new FinanceBusinessReportBreakdownItemDto
{
Key = source.Key,
Label = source.Label,
Amount = RoundMoney(source.Amount),
RatioPercent = RoundPercent(source.Ratio)
};
}
private static string FormatPeriodText(
FinanceBusinessReportPeriodType periodType,
DateTime startAt,
DateTime endAt)
{
return periodType switch
{
FinanceBusinessReportPeriodType.Daily => startAt.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
FinanceBusinessReportPeriodType.Weekly =>
$"{startAt:MM-dd} ~ {endAt.AddDays(-1):MM-dd}",
FinanceBusinessReportPeriodType.Monthly => startAt.ToString("yyyy年M月", CultureInfo.InvariantCulture),
_ => startAt.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
};
}
private static string BuildTitle(
FinanceBusinessReportPeriodType periodType,
DateTime startAt,
DateTime endAt)
{
return periodType switch
{
FinanceBusinessReportPeriodType.Daily => $"{startAt:yyyy年M月d日} 经营日报",
FinanceBusinessReportPeriodType.Weekly => $"{startAt:yyyy年M月d日}~{endAt.AddDays(-1):M月d日} 经营周报",
FinanceBusinessReportPeriodType.Monthly => $"{startAt:yyyy年M月} 经营月报",
_ => "经营报表"
};
}
private static string ToStatusCode(FinanceBusinessReportStatus status)
{
return status switch
{
FinanceBusinessReportStatus.Queued => "queued",
FinanceBusinessReportStatus.Running => "running",
FinanceBusinessReportStatus.Succeeded => "succeeded",
FinanceBusinessReportStatus.Failed => "failed",
_ => "queued"
};
}
private static string ToStatusText(FinanceBusinessReportStatus status)
{
return status switch
{
FinanceBusinessReportStatus.Queued => "排队中",
FinanceBusinessReportStatus.Running => "生成中",
FinanceBusinessReportStatus.Succeeded => "已生成",
FinanceBusinessReportStatus.Failed => "生成失败",
_ => "排队中"
};
}
private static string FormatKpiValue(string key, decimal value)
{
if (key is "order_count")
{
return Math.Round(value, 0, MidpointRounding.AwayFromZero).ToString("0", CultureInfo.InvariantCulture);
}
if (key is "refund_rate" or "profit_rate")
{
return $"{RoundPercent(value):0.##}%";
}
if (key is "average_order_value")
{
return $"¥{RoundMoney(value):0.##}";
}
return $"¥{RoundMoney(value):0.##}";
}
private static decimal RoundMoney(decimal value)
{
return decimal.Round(value, 2, MidpointRounding.AwayFromZero);
}
private static decimal RoundPercent(decimal value)
{
return decimal.Round(value * 100m, 2, MidpointRounding.AwayFromZero);
}
private static decimal RoundRate(decimal value)
{
return decimal.Round(value, 2, MidpointRounding.AwayFromZero);
}
}

View File

@@ -0,0 +1,39 @@
using MediatR;
using TakeoutSaaS.Application.App.Finance.Reports.Dto;
using TakeoutSaaS.Application.App.Finance.Reports.Queries;
using TakeoutSaaS.Domain.Finance.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Finance.Reports.Handlers;
/// <summary>
/// 经营报表详情查询处理器。
/// </summary>
public sealed class GetFinanceBusinessReportDetailQueryHandler(
IFinanceBusinessReportRepository financeBusinessReportRepository,
ITenantProvider tenantProvider)
: IRequestHandler<GetFinanceBusinessReportDetailQuery, FinanceBusinessReportDetailDto?>
{
/// <inheritdoc />
public async Task<FinanceBusinessReportDetailDto?> Handle(
GetFinanceBusinessReportDetailQuery request,
CancellationToken cancellationToken)
{
// 1. 读取租户上下文并确保成本配置存在。
var tenantId = tenantProvider.GetCurrentTenantId();
await financeBusinessReportRepository.EnsureDefaultCostProfilesAsync(
tenantId,
request.StoreId,
cancellationToken);
// 2. 查询详情(允许实时补算)并映射输出。
var detail = await financeBusinessReportRepository.GetDetailAsync(
tenantId,
request.StoreId,
request.ReportId,
allowRealtimeBuild: true,
cancellationToken);
return detail is null ? null : FinanceBusinessReportMapping.ToDetail(detail);
}
}

View File

@@ -0,0 +1,57 @@
using MediatR;
using TakeoutSaaS.Application.App.Finance.Reports.Dto;
using TakeoutSaaS.Application.App.Finance.Reports.Queries;
using TakeoutSaaS.Domain.Finance.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Finance.Reports.Handlers;
/// <summary>
/// 经营报表分页查询处理器。
/// </summary>
public sealed class SearchFinanceBusinessReportListQueryHandler(
IFinanceBusinessReportRepository financeBusinessReportRepository,
ITenantProvider tenantProvider)
: IRequestHandler<SearchFinanceBusinessReportListQuery, FinanceBusinessReportListResultDto>
{
/// <inheritdoc />
public async Task<FinanceBusinessReportListResultDto> Handle(
SearchFinanceBusinessReportListQuery request,
CancellationToken cancellationToken)
{
// 1. 读取租户上下文并归一化分页参数。
var tenantId = tenantProvider.GetCurrentTenantId();
var normalizedPage = Math.Max(1, request.Page);
var normalizedPageSize = Math.Clamp(request.PageSize, 1, 200);
// 2. 确保成本配置并补齐分页周期快照。
await financeBusinessReportRepository.EnsureDefaultCostProfilesAsync(
tenantId,
request.StoreId,
cancellationToken);
await financeBusinessReportRepository.QueueSnapshotsForPageAsync(
tenantId,
request.StoreId,
request.PeriodType,
normalizedPage,
normalizedPageSize,
cancellationToken);
// 3. 查询分页快照并映射输出。
var pageSnapshot = await financeBusinessReportRepository.SearchPageAsync(
tenantId,
request.StoreId,
request.PeriodType,
normalizedPage,
normalizedPageSize,
cancellationToken);
return new FinanceBusinessReportListResultDto
{
Items = pageSnapshot.Items.Select(FinanceBusinessReportMapping.ToListItem).ToList(),
Total = pageSnapshot.TotalCount,
Page = normalizedPage,
PageSize = normalizedPageSize
};
}
}

View File

@@ -0,0 +1,31 @@
using MediatR;
using TakeoutSaaS.Application.App.Finance.Reports.Dto;
using TakeoutSaaS.Domain.Finance.Enums;
namespace TakeoutSaaS.Application.App.Finance.Reports.Queries;
/// <summary>
/// 批量导出经营报表ZIPPDF + Excel
/// </summary>
public sealed class ExportFinanceBusinessReportBatchQuery : IRequest<FinanceBusinessReportExportDto>
{
/// <summary>
/// 门店 ID。
/// </summary>
public long StoreId { get; init; }
/// <summary>
/// 周期类型。
/// </summary>
public FinanceBusinessReportPeriodType PeriodType { get; init; } = FinanceBusinessReportPeriodType.Daily;
/// <summary>
/// 页码。
/// </summary>
public int Page { get; init; } = 1;
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; init; } = 20;
}

View File

@@ -0,0 +1,20 @@
using MediatR;
using TakeoutSaaS.Application.App.Finance.Reports.Dto;
namespace TakeoutSaaS.Application.App.Finance.Reports.Queries;
/// <summary>
/// 导出经营报表 Excel。
/// </summary>
public sealed class ExportFinanceBusinessReportExcelQuery : IRequest<FinanceBusinessReportExportDto>
{
/// <summary>
/// 门店 ID。
/// </summary>
public long StoreId { get; init; }
/// <summary>
/// 报表 ID。
/// </summary>
public long ReportId { get; init; }
}

View File

@@ -0,0 +1,20 @@
using MediatR;
using TakeoutSaaS.Application.App.Finance.Reports.Dto;
namespace TakeoutSaaS.Application.App.Finance.Reports.Queries;
/// <summary>
/// 导出经营报表 PDF。
/// </summary>
public sealed class ExportFinanceBusinessReportPdfQuery : IRequest<FinanceBusinessReportExportDto>
{
/// <summary>
/// 门店 ID。
/// </summary>
public long StoreId { get; init; }
/// <summary>
/// 报表 ID。
/// </summary>
public long ReportId { get; init; }
}

View File

@@ -0,0 +1,20 @@
using MediatR;
using TakeoutSaaS.Application.App.Finance.Reports.Dto;
namespace TakeoutSaaS.Application.App.Finance.Reports.Queries;
/// <summary>
/// 查询经营报表详情。
/// </summary>
public sealed class GetFinanceBusinessReportDetailQuery : IRequest<FinanceBusinessReportDetailDto?>
{
/// <summary>
/// 门店 ID。
/// </summary>
public long StoreId { get; init; }
/// <summary>
/// 报表 ID。
/// </summary>
public long ReportId { get; init; }
}

View File

@@ -0,0 +1,31 @@
using MediatR;
using TakeoutSaaS.Application.App.Finance.Reports.Dto;
using TakeoutSaaS.Domain.Finance.Enums;
namespace TakeoutSaaS.Application.App.Finance.Reports.Queries;
/// <summary>
/// 查询经营报表分页列表。
/// </summary>
public sealed class SearchFinanceBusinessReportListQuery : IRequest<FinanceBusinessReportListResultDto>
{
/// <summary>
/// 门店 ID。
/// </summary>
public long StoreId { get; init; }
/// <summary>
/// 周期类型。
/// </summary>
public FinanceBusinessReportPeriodType PeriodType { get; init; } = FinanceBusinessReportPeriodType.Daily;
/// <summary>
/// 页码。
/// </summary>
public int Page { get; init; } = 1;
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; init; } = 20;
}

View File

@@ -0,0 +1,20 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Finance.Reports.Queries;
namespace TakeoutSaaS.Application.App.Finance.Reports.Validators;
/// <summary>
/// 经营报表批量导出查询验证器。
/// </summary>
public sealed class ExportFinanceBusinessReportBatchQueryValidator : AbstractValidator<ExportFinanceBusinessReportBatchQuery>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public ExportFinanceBusinessReportBatchQueryValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.Page).GreaterThan(0);
RuleFor(x => x.PageSize).InclusiveBetween(1, 200);
}
}

View File

@@ -0,0 +1,19 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Finance.Reports.Queries;
namespace TakeoutSaaS.Application.App.Finance.Reports.Validators;
/// <summary>
/// 经营报表 Excel 导出查询验证器。
/// </summary>
public sealed class ExportFinanceBusinessReportExcelQueryValidator : AbstractValidator<ExportFinanceBusinessReportExcelQuery>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public ExportFinanceBusinessReportExcelQueryValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.ReportId).GreaterThan(0);
}
}

View File

@@ -0,0 +1,19 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Finance.Reports.Queries;
namespace TakeoutSaaS.Application.App.Finance.Reports.Validators;
/// <summary>
/// 经营报表 PDF 导出查询验证器。
/// </summary>
public sealed class ExportFinanceBusinessReportPdfQueryValidator : AbstractValidator<ExportFinanceBusinessReportPdfQuery>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public ExportFinanceBusinessReportPdfQueryValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.ReportId).GreaterThan(0);
}
}

View File

@@ -0,0 +1,19 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Finance.Reports.Queries;
namespace TakeoutSaaS.Application.App.Finance.Reports.Validators;
/// <summary>
/// 经营报表详情查询验证器。
/// </summary>
public sealed class GetFinanceBusinessReportDetailQueryValidator : AbstractValidator<GetFinanceBusinessReportDetailQuery>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public GetFinanceBusinessReportDetailQueryValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.ReportId).GreaterThan(0);
}
}

View File

@@ -0,0 +1,20 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Finance.Reports.Queries;
namespace TakeoutSaaS.Application.App.Finance.Reports.Validators;
/// <summary>
/// 经营报表分页查询验证器。
/// </summary>
public sealed class SearchFinanceBusinessReportListQueryValidator : AbstractValidator<SearchFinanceBusinessReportListQuery>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public SearchFinanceBusinessReportListQueryValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.Page).GreaterThan(0);
RuleFor(x => x.PageSize).InclusiveBetween(1, 200);
}
}