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,111 @@
using TakeoutSaaS.Domain.Finance.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Finance.Entities;
/// <summary>
/// 经营报表快照实体。
/// </summary>
public sealed class FinanceBusinessReportSnapshot : MultiTenantEntityBase
{
/// <summary>
/// 所属门店 ID。
/// </summary>
public long StoreId { get; set; }
/// <summary>
/// 周期类型。
/// </summary>
public FinanceBusinessReportPeriodType PeriodType { get; set; }
/// <summary>
/// 周期开始时间UTC
/// </summary>
public DateTime PeriodStartAt { get; set; }
/// <summary>
/// 周期结束时间UTC不含
/// </summary>
public DateTime PeriodEndAt { get; set; }
/// <summary>
/// 生成状态。
/// </summary>
public FinanceBusinessReportStatus Status { get; set; } = FinanceBusinessReportStatus.Queued;
/// <summary>
/// 营业额。
/// </summary>
public decimal RevenueAmount { get; set; }
/// <summary>
/// 订单数。
/// </summary>
public int OrderCount { get; set; }
/// <summary>
/// 客单价。
/// </summary>
public decimal AverageOrderValue { get; set; }
/// <summary>
/// 退款率0-1
/// </summary>
public decimal RefundRate { get; set; }
/// <summary>
/// 成本总额。
/// </summary>
public decimal CostTotalAmount { get; set; }
/// <summary>
/// 净利润。
/// </summary>
public decimal NetProfitAmount { get; set; }
/// <summary>
/// 利润率0-1
/// </summary>
public decimal ProfitRate { get; set; }
/// <summary>
/// KPI 比较快照 JSON同比/环比)。
/// </summary>
public string KpiComparisonJson { get; set; } = "[]";
/// <summary>
/// 收入明细快照 JSON按渠道
/// </summary>
public string IncomeBreakdownJson { get; set; } = "[]";
/// <summary>
/// 成本明细快照 JSON按类别
/// </summary>
public string CostBreakdownJson { get; set; } = "[]";
/// <summary>
/// 生成开始时间UTC
/// </summary>
public DateTime? StartedAt { get; set; }
/// <summary>
/// 生成完成时间UTC
/// </summary>
public DateTime? FinishedAt { get; set; }
/// <summary>
/// 最近一次失败信息。
/// </summary>
public string? LastError { get; set; }
/// <summary>
/// 重试次数。
/// </summary>
public int RetryCount { get; set; }
/// <summary>
/// 调度任务 ID。
/// </summary>
public string? HangfireJobId { get; set; }
}

View File

@@ -0,0 +1,36 @@
using TakeoutSaaS.Domain.Finance.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Finance.Entities;
/// <summary>
/// 成本日覆盖实体。
/// </summary>
public sealed class FinanceCostDailyOverride : MultiTenantEntityBase
{
/// <summary>
/// 门店 ID。
/// </summary>
public long StoreId { get; set; }
/// <summary>
/// 业务日期UTC 日期)。
/// </summary>
public DateTime BusinessDate { get; set; }
/// <summary>
/// 成本分类。
/// </summary>
public FinanceCostCategory Category { get; set; }
/// <summary>
/// 覆盖金额。
/// </summary>
public decimal Amount { get; set; }
/// <summary>
/// 备注。
/// </summary>
public string? Remark { get; set; }
}

View File

@@ -0,0 +1,56 @@
using TakeoutSaaS.Domain.Finance.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Finance.Entities;
/// <summary>
/// 成本配置实体(类别级规则)。
/// </summary>
public sealed class FinanceCostProfile : MultiTenantEntityBase
{
/// <summary>
/// 门店 ID。
/// </summary>
public long StoreId { get; set; }
/// <summary>
/// 成本分类。
/// </summary>
public FinanceCostCategory Category { get; set; }
/// <summary>
/// 计算模式。
/// </summary>
public FinanceCostCalcMode CalcMode { get; set; }
/// <summary>
/// 比例值0-1Ratio 模式使用)。
/// </summary>
public decimal Ratio { get; set; }
/// <summary>
/// 固定日金额FixedDaily 模式使用)。
/// </summary>
public decimal FixedDailyAmount { get; set; }
/// <summary>
/// 生效开始日期UTC 日期)。
/// </summary>
public DateTime EffectiveFrom { get; set; }
/// <summary>
/// 生效结束日期UTC 日期null 表示长期)。
/// </summary>
public DateTime? EffectiveTo { get; set; }
/// <summary>
/// 是否启用。
/// </summary>
public bool IsEnabled { get; set; } = true;
/// <summary>
/// 排序值。
/// </summary>
public int SortOrder { get; set; } = 100;
}

View File

@@ -0,0 +1,23 @@
namespace TakeoutSaaS.Domain.Finance.Enums;
/// <summary>
/// 经营报表周期类型。
/// </summary>
public enum FinanceBusinessReportPeriodType
{
/// <summary>
/// 日报。
/// </summary>
Daily = 1,
/// <summary>
/// 周报。
/// </summary>
Weekly = 2,
/// <summary>
/// 月报。
/// </summary>
Monthly = 3
}

View File

@@ -0,0 +1,28 @@
namespace TakeoutSaaS.Domain.Finance.Enums;
/// <summary>
/// 经营报表快照状态。
/// </summary>
public enum FinanceBusinessReportStatus
{
/// <summary>
/// 已排队。
/// </summary>
Queued = 1,
/// <summary>
/// 生成中。
/// </summary>
Running = 2,
/// <summary>
/// 已生成。
/// </summary>
Succeeded = 3,
/// <summary>
/// 生成失败。
/// </summary>
Failed = 4
}

View File

@@ -0,0 +1,18 @@
namespace TakeoutSaaS.Domain.Finance.Enums;
/// <summary>
/// 成本计算模式。
/// </summary>
public enum FinanceCostCalcMode
{
/// <summary>
/// 按营业额比例计算。
/// </summary>
Ratio = 1,
/// <summary>
/// 按固定日金额计算。
/// </summary>
FixedDaily = 2
}

View File

@@ -0,0 +1,245 @@
using TakeoutSaaS.Domain.Finance.Enums;
namespace TakeoutSaaS.Domain.Finance.Models;
/// <summary>
/// 经营报表 KPI 快照项。
/// </summary>
public sealed class FinanceBusinessReportKpiSnapshot
{
/// <summary>
/// 指标键。
/// </summary>
public string Key { get; set; } = string.Empty;
/// <summary>
/// 指标名称。
/// </summary>
public string Label { get; set; } = string.Empty;
/// <summary>
/// 指标值。
/// </summary>
public decimal Value { get; set; }
/// <summary>
/// 同比变化率(百分数,如 3.5 表示 +3.5%)。
/// </summary>
public decimal YoyChangeRate { get; set; }
/// <summary>
/// 环比变化率(百分数,如 2.1 表示 +2.1%)。
/// </summary>
public decimal MomChangeRate { get; set; }
}
/// <summary>
/// 经营报表明细行快照。
/// </summary>
public sealed class FinanceBusinessReportBreakdownSnapshot
{
/// <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>
/// 占比0-1
/// </summary>
public decimal Ratio { get; set; }
}
/// <summary>
/// 经营报表列表行快照。
/// </summary>
public sealed class FinanceBusinessReportListItemSnapshot
{
/// <summary>
/// 报表 ID。
/// </summary>
public long ReportId { get; set; }
/// <summary>
/// 周期类型。
/// </summary>
public FinanceBusinessReportPeriodType PeriodType { get; set; }
/// <summary>
/// 周期开始时间UTC
/// </summary>
public DateTime PeriodStartAt { get; set; }
/// <summary>
/// 周期结束时间UTC不含
/// </summary>
public DateTime PeriodEndAt { get; set; }
/// <summary>
/// 状态。
/// </summary>
public FinanceBusinessReportStatus Status { get; set; }
/// <summary>
/// 营业额。
/// </summary>
public decimal RevenueAmount { get; set; }
/// <summary>
/// 订单数。
/// </summary>
public int OrderCount { get; set; }
/// <summary>
/// 客单价。
/// </summary>
public decimal AverageOrderValue { get; set; }
/// <summary>
/// 退款率0-1
/// </summary>
public decimal RefundRate { get; set; }
/// <summary>
/// 成本总额。
/// </summary>
public decimal CostTotalAmount { get; set; }
/// <summary>
/// 净利润。
/// </summary>
public decimal NetProfitAmount { get; set; }
/// <summary>
/// 利润率0-1
/// </summary>
public decimal ProfitRate { get; set; }
}
/// <summary>
/// 经营报表分页快照。
/// </summary>
public sealed class FinanceBusinessReportPageSnapshot
{
/// <summary>
/// 列表。
/// </summary>
public List<FinanceBusinessReportListItemSnapshot> Items { get; set; } = [];
/// <summary>
/// 总数。
/// </summary>
public int TotalCount { get; set; }
}
/// <summary>
/// 经营报表详情快照。
/// </summary>
public sealed class FinanceBusinessReportDetailSnapshot
{
/// <summary>
/// 报表 ID。
/// </summary>
public long ReportId { get; set; }
/// <summary>
/// 门店 ID。
/// </summary>
public long StoreId { get; set; }
/// <summary>
/// 周期类型。
/// </summary>
public FinanceBusinessReportPeriodType PeriodType { get; set; }
/// <summary>
/// 周期开始时间UTC
/// </summary>
public DateTime PeriodStartAt { get; set; }
/// <summary>
/// 周期结束时间UTC不含
/// </summary>
public DateTime PeriodEndAt { get; set; }
/// <summary>
/// 状态。
/// </summary>
public FinanceBusinessReportStatus Status { get; set; }
/// <summary>
/// 营业额。
/// </summary>
public decimal RevenueAmount { get; set; }
/// <summary>
/// 订单数。
/// </summary>
public int OrderCount { get; set; }
/// <summary>
/// 客单价。
/// </summary>
public decimal AverageOrderValue { get; set; }
/// <summary>
/// 退款率0-1
/// </summary>
public decimal RefundRate { get; set; }
/// <summary>
/// 成本总额。
/// </summary>
public decimal CostTotalAmount { get; set; }
/// <summary>
/// 净利润。
/// </summary>
public decimal NetProfitAmount { get; set; }
/// <summary>
/// 利润率0-1
/// </summary>
public decimal ProfitRate { get; set; }
/// <summary>
/// 关键指标快照列表。
/// </summary>
public List<FinanceBusinessReportKpiSnapshot> Kpis { get; set; } = [];
/// <summary>
/// 收入明细(按渠道)。
/// </summary>
public List<FinanceBusinessReportBreakdownSnapshot> IncomeBreakdowns { get; set; } = [];
/// <summary>
/// 成本明细(按类别)。
/// </summary>
public List<FinanceBusinessReportBreakdownSnapshot> CostBreakdowns { get; set; } = [];
}
/// <summary>
/// 待处理报表任务快照。
/// </summary>
public sealed class FinanceBusinessReportPendingSnapshot
{
/// <summary>
/// 快照 ID。
/// </summary>
public long SnapshotId { get; set; }
/// <summary>
/// 租户 ID。
/// </summary>
public long TenantId { get; set; }
}

View File

@@ -0,0 +1,77 @@
using TakeoutSaaS.Domain.Finance.Enums;
using TakeoutSaaS.Domain.Finance.Models;
namespace TakeoutSaaS.Domain.Finance.Repositories;
/// <summary>
/// 经营报表仓储契约。
/// </summary>
public interface IFinanceBusinessReportRepository
{
/// <summary>
/// 确保门店存在默认成本配置。
/// </summary>
Task EnsureDefaultCostProfilesAsync(
long tenantId,
long storeId,
CancellationToken cancellationToken = default);
/// <summary>
/// 为指定分页周期补齐快照并排队。
/// </summary>
Task QueueSnapshotsForPageAsync(
long tenantId,
long storeId,
FinanceBusinessReportPeriodType periodType,
int page,
int pageSize,
CancellationToken cancellationToken = default);
/// <summary>
/// 查询经营报表分页结果。
/// </summary>
Task<FinanceBusinessReportPageSnapshot> SearchPageAsync(
long tenantId,
long storeId,
FinanceBusinessReportPeriodType periodType,
int page,
int pageSize,
CancellationToken cancellationToken = default);
/// <summary>
/// 查询经营报表详情。
/// </summary>
Task<FinanceBusinessReportDetailSnapshot?> GetDetailAsync(
long tenantId,
long storeId,
long reportId,
bool allowRealtimeBuild,
CancellationToken cancellationToken = default);
/// <summary>
/// 查询批量导出详情集合。
/// </summary>
Task<IReadOnlyList<FinanceBusinessReportDetailSnapshot>> ListBatchDetailsAsync(
long tenantId,
long storeId,
FinanceBusinessReportPeriodType periodType,
int page,
int pageSize,
bool allowRealtimeBuild,
CancellationToken cancellationToken = default);
/// <summary>
/// 拉取待处理任务。
/// </summary>
Task<IReadOnlyList<FinanceBusinessReportPendingSnapshot>> GetPendingSnapshotsAsync(
int take,
CancellationToken cancellationToken = default);
/// <summary>
/// 执行报表快照生成。
/// </summary>
Task GenerateSnapshotAsync(
long snapshotId,
CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,38 @@
using TakeoutSaaS.Domain.Finance.Models;
namespace TakeoutSaaS.Domain.Finance.Services;
/// <summary>
/// 经营报表导出服务契约。
/// </summary>
public interface IFinanceBusinessReportExportService
{
/// <summary>
/// 导出单条报表 PDF。
/// </summary>
Task<byte[]> ExportSinglePdfAsync(
FinanceBusinessReportDetailSnapshot detail,
CancellationToken cancellationToken = default);
/// <summary>
/// 导出单条报表 Excel。
/// </summary>
Task<byte[]> ExportSingleExcelAsync(
FinanceBusinessReportDetailSnapshot detail,
CancellationToken cancellationToken = default);
/// <summary>
/// 导出批量报表 PDF。
/// </summary>
Task<byte[]> ExportBatchPdfAsync(
IReadOnlyList<FinanceBusinessReportDetailSnapshot> details,
CancellationToken cancellationToken = default);
/// <summary>
/// 导出批量报表 Excel。
/// </summary>
Task<byte[]> ExportBatchExcelAsync(
IReadOnlyList<FinanceBusinessReportDetailSnapshot> details,
CancellationToken cancellationToken = default);
}