Revert "refactor: 清理租户API旧模块代码"

This reverts commit 992930a821.
This commit is contained in:
2026-02-17 12:12:01 +08:00
parent 654b1ae3f7
commit c032608a57
910 changed files with 189923 additions and 266 deletions

View File

@@ -0,0 +1,22 @@
namespace TakeoutSaaS.Domain.Tenants.Enums;
/// <summary>
/// 账单导出格式。
/// </summary>
public enum BillingExportFormat
{
/// <summary>
/// Excel 格式(.xlsx
/// </summary>
Excel = 0,
/// <summary>
/// PDF 格式(.pdf
/// </summary>
Pdf = 1,
/// <summary>
/// CSV 格式(.csv
/// </summary>
Csv = 2
}

View File

@@ -0,0 +1,22 @@
namespace TakeoutSaaS.Domain.Tenants.Events;
/// <summary>
/// 公告发布事件。
/// </summary>
public sealed class AnnouncementPublished
{
/// <summary>
/// 公告 ID。
/// </summary>
public long AnnouncementId { get; init; }
/// <summary>
/// 发布时间UTC
/// </summary>
public DateTime PublishedAt { get; init; }
/// <summary>
/// 目标受众类型。
/// </summary>
public string TargetType { get; init; } = string.Empty;
}

View File

@@ -0,0 +1,17 @@
namespace TakeoutSaaS.Domain.Tenants.Events;
/// <summary>
/// 公告撤销事件。
/// </summary>
public sealed class AnnouncementRevoked
{
/// <summary>
/// 公告 ID。
/// </summary>
public long AnnouncementId { get; init; }
/// <summary>
/// 撤销时间UTC
/// </summary>
public DateTime RevokedAt { get; init; }
}

View File

@@ -0,0 +1,131 @@
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// <summary>
/// 配额包仓储。
/// </summary>
public interface IQuotaPackageRepository
{
#region
/// <summary>
/// 按 ID 查找配额包。
/// </summary>
/// <param name="id">配额包 ID雪花算法。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>配额包实体,未找到返回 null。</returns>
Task<QuotaPackage?> FindByIdAsync(long id, CancellationToken cancellationToken = default);
/// <summary>
/// 分页查询配额包。
/// </summary>
/// <param name="quotaType">配额类型,为空不按类型过滤。</param>
/// <param name="isActive">启用状态,为空不按状态过滤。</param>
/// <param name="page">页码(从 1 开始)。</param>
/// <param name="pageSize">每页大小。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>分页数据与总数。</returns>
Task<(IReadOnlyList<QuotaPackage> Items, int Total)> SearchPagedAsync(
TenantQuotaType? quotaType,
bool? isActive,
int page,
int pageSize,
CancellationToken cancellationToken = default);
/// <summary>
/// 新增配额包。
/// </summary>
/// <param name="quotaPackage">配额包实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task AddAsync(QuotaPackage quotaPackage, CancellationToken cancellationToken = default);
/// <summary>
/// 更新配额包。
/// </summary>
/// <param name="quotaPackage">配额包实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task UpdateAsync(QuotaPackage quotaPackage, CancellationToken cancellationToken = default);
/// <summary>
/// 软删除配额包。
/// </summary>
/// <param name="id">配额包 ID雪花算法。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>删除成功返回 true未找到返回 false。</returns>
Task<bool> SoftDeleteAsync(long id, CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 分页查询租户配额购买记录(包含配额包信息)。
/// </summary>
/// <param name="tenantId">租户 ID雪花算法。</param>
/// <param name="page">页码(从 1 开始)。</param>
/// <param name="pageSize">每页大小。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>分页数据与总数。</returns>
Task<(IReadOnlyList<(TenantQuotaPackagePurchase Purchase, QuotaPackage Package)> Items, int Total)> GetPurchasesPagedAsync(
long tenantId,
int page,
int pageSize,
CancellationToken cancellationToken = default);
/// <summary>
/// 新增配额购买记录。
/// </summary>
/// <param name="purchase">购买记录实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task AddPurchaseAsync(TenantQuotaPackagePurchase purchase, CancellationToken cancellationToken = default);
#endregion
#region 使
/// <summary>
/// 查询租户配额使用情况。
/// </summary>
/// <param name="tenantId">租户 ID雪花算法。</param>
/// <param name="quotaType">配额类型,为空查询全部。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>配额使用情况列表。</returns>
Task<IReadOnlyList<TenantQuotaUsage>> GetUsageByTenantAsync(
long tenantId,
TenantQuotaType? quotaType,
CancellationToken cancellationToken = default);
/// <summary>
/// 查找特定配额使用记录。
/// </summary>
/// <param name="tenantId">租户 ID雪花算法。</param>
/// <param name="quotaType">配额类型。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>配额使用记录,未找到返回 null。</returns>
Task<TenantQuotaUsage?> FindUsageAsync(
long tenantId,
TenantQuotaType quotaType,
CancellationToken cancellationToken = default);
/// <summary>
/// 更新配额使用情况。
/// </summary>
/// <param name="usage">配额使用实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task UpdateUsageAsync(TenantQuotaUsage usage, CancellationToken cancellationToken = default);
#endregion
/// <summary>
/// 持久化。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,112 @@
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// <summary>
/// 统计数据仓储接口。
/// </summary>
public interface IStatisticsRepository
{
#region
/// <summary>
/// 获取所有订阅(用于统计)。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>所有订阅记录。</returns>
Task<IReadOnlyList<TenantSubscription>> GetAllSubscriptionsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 获取即将到期的订阅(含租户和套餐信息)。
/// </summary>
/// <param name="daysAhead">到期天数。</param>
/// <param name="onlyWithoutAutoRenew">是否仅查询未开启自动续费的。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>即将到期的订阅信息列表。</returns>
Task<IReadOnlyList<ExpiringSubscriptionInfo>> GetExpiringSubscriptionsAsync(
int daysAhead,
bool onlyWithoutAutoRenew,
CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 获取所有已付款账单(用于收入统计)。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>已付款账单列表。</returns>
Task<IReadOnlyList<TenantBillingStatement>> GetPaidBillsAsync(CancellationToken cancellationToken = default);
#endregion
#region 使
/// <summary>
/// 获取配额使用排行(含租户信息)。
/// </summary>
/// <param name="quotaType">配额类型。</param>
/// <param name="topN">前 N 名。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>配额使用排行列表。</returns>
Task<IReadOnlyList<QuotaUsageRankInfo>> GetQuotaUsageRankingAsync(
TenantQuotaType quotaType,
int topN,
CancellationToken cancellationToken = default);
#endregion
}
/// <summary>
/// 即将到期的订阅信息(含关联数据)。
/// </summary>
public record ExpiringSubscriptionInfo
{
/// <summary>
/// 订阅实体。
/// </summary>
public required TenantSubscription Subscription { get; init; }
/// <summary>
/// 租户名称。
/// </summary>
public required string TenantName { get; init; }
/// <summary>
/// 套餐名称。
/// </summary>
public required string PackageName { get; init; }
}
/// <summary>
/// 配额使用排行信息(含租户名称)。
/// </summary>
public record QuotaUsageRankInfo
{
/// <summary>
/// 租户 ID。
/// </summary>
public long TenantId { get; init; }
/// <summary>
/// 租户名称。
/// </summary>
public required string TenantName { get; init; }
/// <summary>
/// 已使用值。
/// </summary>
public decimal UsedValue { get; init; }
/// <summary>
/// 限制值。
/// </summary>
public decimal LimitValue { get; init; }
/// <summary>
/// 使用百分比。
/// </summary>
public decimal UsagePercentage { get; init; }
}

View File

@@ -0,0 +1,382 @@
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// <summary>
/// 订阅管理仓储接口。
/// </summary>
public interface ISubscriptionRepository
{
#region
/// <summary>
/// 按 ID 查询订阅。
/// </summary>
/// <param name="subscriptionId">订阅 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>订阅实体,未找到返回 null。</returns>
Task<TenantSubscription?> FindByIdAsync(
long subscriptionId,
CancellationToken cancellationToken = default);
/// <summary>
/// 按 ID 列表批量查询订阅。
/// </summary>
/// <param name="subscriptionIds">订阅 ID 列表。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>订阅实体列表。</returns>
Task<IReadOnlyList<TenantSubscription>> FindByIdsAsync(
IEnumerable<long> subscriptionIds,
CancellationToken cancellationToken = default);
/// <summary>
/// 分页查询订阅列表(含关联信息)。
/// </summary>
/// <param name="filter">查询过滤条件。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>分页结果。</returns>
Task<(IReadOnlyList<SubscriptionWithRelations> Items, int Total)> SearchPagedAsync(
SubscriptionSearchFilter filter,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取订阅详情(含关联信息)。
/// </summary>
/// <param name="subscriptionId">订阅 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>订阅详情信息。</returns>
Task<SubscriptionDetailInfo?> GetDetailAsync(
long subscriptionId,
CancellationToken cancellationToken = default);
/// <summary>
/// 按 ID 列表批量查询订阅(含租户信息)。
/// </summary>
/// <param name="subscriptionIds">订阅 ID 列表。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>订阅与租户信息列表。</returns>
Task<IReadOnlyList<SubscriptionWithTenant>> FindByIdsWithTenantAsync(
IEnumerable<long> subscriptionIds,
CancellationToken cancellationToken = default);
/// <summary>
/// 查询自动续费候选订阅(活跃 + 开启自动续费 + 即将到期)。
/// </summary>
/// <param name="now">当前时间UTC。</param>
/// <param name="renewalThreshold">续费阈值时间UTC到期时间小于等于该时间视为候选。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>候选订阅集合(含套餐信息)。</returns>
Task<IReadOnlyList<AutoRenewalCandidate>> FindAutoRenewalCandidatesAsync(
DateTime now,
DateTime renewalThreshold,
CancellationToken cancellationToken = default);
/// <summary>
/// 查询续费提醒候选订阅(活跃 + 未开启自动续费 + 到期时间落在指定日期范围)。
/// </summary>
/// <param name="startOfDay">筛选开始时间UTC。</param>
/// <param name="endOfDay">筛选结束时间UTC不含。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>候选订阅集合(含租户与套餐信息)。</returns>
Task<IReadOnlyList<RenewalReminderCandidate>> FindRenewalReminderCandidatesAsync(
DateTime startOfDay,
DateTime endOfDay,
CancellationToken cancellationToken = default);
/// <summary>
/// 查询已到期仍处于 Active 的订阅(用于进入宽限期)。
/// </summary>
/// <param name="now">当前时间UTC。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>到期订阅集合。</returns>
Task<IReadOnlyList<TenantSubscription>> FindExpiredActiveSubscriptionsAsync(
DateTime now,
CancellationToken cancellationToken = default);
/// <summary>
/// 查询宽限期已结束的订阅(用于自动暂停)。
/// </summary>
/// <param name="now">当前时间UTC。</param>
/// <param name="gracePeriodDays">宽限期天数。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>宽限期到期订阅集合。</returns>
Task<IReadOnlyList<TenantSubscription>> FindGracePeriodExpiredSubscriptionsAsync(
DateTime now,
int gracePeriodDays,
CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 按 ID 查询套餐。
/// </summary>
/// <param name="packageId">套餐 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>套餐实体,未找到返回 null。</returns>
Task<TenantPackage?> FindPackageByIdAsync(long packageId, CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 更新订阅。
/// </summary>
/// <param name="subscription">订阅实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task UpdateAsync(TenantSubscription subscription, CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 添加订阅变更历史。
/// </summary>
/// <param name="history">历史记录实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task AddHistoryAsync(TenantSubscriptionHistory history, CancellationToken cancellationToken = default);
/// <summary>
/// 获取订阅变更历史(含套餐名称)。
/// </summary>
/// <param name="subscriptionId">订阅 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>历史记录列表。</returns>
Task<IReadOnlyList<SubscriptionHistoryWithPackageNames>> GetHistoryAsync(
long subscriptionId,
CancellationToken cancellationToken = default);
#endregion
#region 使
/// <summary>
/// 获取租户配额使用情况。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>配额使用列表。</returns>
Task<IReadOnlyList<TenantQuotaUsage>> GetQuotaUsagesAsync(
long tenantId,
CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 添加租户通知。
/// </summary>
/// <param name="notification">通知实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task AddNotificationAsync(TenantNotification notification, CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 添加操作日志。
/// </summary>
/// <param name="log">日志实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task AddOperationLogAsync(OperationLog log, CancellationToken cancellationToken = default);
#endregion
/// <summary>
/// 保存变更。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}
#region
/// <summary>
/// 订阅查询过滤条件。
/// </summary>
public record SubscriptionSearchFilter
{
/// <summary>
/// 订阅状态。
/// </summary>
public SubscriptionStatus? Status { get; init; }
/// <summary>
/// 套餐 ID。
/// </summary>
public long? TenantPackageId { get; init; }
/// <summary>
/// 租户 ID。
/// </summary>
public long? TenantId { get; init; }
/// <summary>
/// 租户关键词(名称或编码)。
/// </summary>
public string? TenantKeyword { get; init; }
/// <summary>
/// 即将到期天数。
/// </summary>
public int? ExpiringWithinDays { get; init; }
/// <summary>
/// 自动续费状态。
/// </summary>
public bool? AutoRenew { get; init; }
/// <summary>
/// 页码(从 1 开始)。
/// </summary>
public int Page { get; init; } = 1;
/// <summary>
/// 每页数量。
/// </summary>
public int PageSize { get; init; } = 20;
}
/// <summary>
/// 订阅及关联信息。
/// </summary>
public record SubscriptionWithRelations
{
/// <summary>
/// 订阅实体。
/// </summary>
public required TenantSubscription Subscription { get; init; }
/// <summary>
/// 租户名称。
/// </summary>
public required string TenantName { get; init; }
/// <summary>
/// 租户编码。
/// </summary>
public required string TenantCode { get; init; }
/// <summary>
/// 套餐名称。
/// </summary>
public required string PackageName { get; init; }
/// <summary>
/// 排期套餐名称(可选)。
/// </summary>
public string? ScheduledPackageName { get; init; }
}
/// <summary>
/// 订阅详情信息。
/// </summary>
public record SubscriptionDetailInfo
{
/// <summary>
/// 订阅实体。
/// </summary>
public required TenantSubscription Subscription { get; init; }
/// <summary>
/// 租户名称。
/// </summary>
public required string TenantName { get; init; }
/// <summary>
/// 租户编码。
/// </summary>
public required string TenantCode { get; init; }
/// <summary>
/// 当前套餐。
/// </summary>
public TenantPackage? Package { get; init; }
/// <summary>
/// 排期套餐。
/// </summary>
public TenantPackage? ScheduledPackage { get; init; }
}
/// <summary>
/// 订阅与租户信息。
/// </summary>
public record SubscriptionWithTenant
{
/// <summary>
/// 订阅实体。
/// </summary>
public required TenantSubscription Subscription { get; init; }
/// <summary>
/// 租户实体。
/// </summary>
public required Tenant Tenant { get; init; }
}
/// <summary>
/// 自动续费候选订阅信息。
/// </summary>
public sealed record AutoRenewalCandidate
{
/// <summary>
/// 订阅实体。
/// </summary>
public required TenantSubscription Subscription { get; init; }
/// <summary>
/// 当前套餐实体。
/// </summary>
public required TenantPackage Package { get; init; }
}
/// <summary>
/// 续费提醒候选订阅信息。
/// </summary>
public sealed record RenewalReminderCandidate
{
/// <summary>
/// 订阅实体。
/// </summary>
public required TenantSubscription Subscription { get; init; }
/// <summary>
/// 租户实体。
/// </summary>
public required Tenant Tenant { get; init; }
/// <summary>
/// 当前套餐实体。
/// </summary>
public required TenantPackage Package { get; init; }
}
/// <summary>
/// 订阅历史(含套餐名称)。
/// </summary>
public record SubscriptionHistoryWithPackageNames
{
/// <summary>
/// 历史记录实体。
/// </summary>
public required TenantSubscriptionHistory History { get; init; }
/// <summary>
/// 原套餐名称。
/// </summary>
public required string FromPackageName { get; init; }
/// <summary>
/// 目标套餐名称。
/// </summary>
public required string ToPackageName { get; init; }
}
#endregion

View File

@@ -0,0 +1,53 @@
using TakeoutSaaS.Domain.Tenants.Entities;
namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// <summary>
/// 公告已读仓储。
/// </summary>
public interface ITenantAnnouncementReadRepository
{
/// <summary>
/// 按公告查询已读记录。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="announcementId">公告 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>指定公告的已读列表。</returns>
Task<IReadOnlyList<TenantAnnouncementRead>> GetByAnnouncementAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default);
/// <summary>
/// 批量按公告查询已读记录,可选按用户过滤。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="announcementIds">公告 ID 集合。</param>
/// <param name="userId">用户 ID空则不按用户筛选。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>匹配条件的已读列表。</returns>
Task<IReadOnlyList<TenantAnnouncementRead>> GetByAnnouncementAsync(long tenantId, IEnumerable<long> announcementIds, long? userId, CancellationToken cancellationToken = default);
/// <summary>
/// 查询指定用户对某公告的已读记录。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="announcementId">公告 ID。</param>
/// <param name="userId">用户 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>已读记录,未读返回 null。</returns>
Task<TenantAnnouncementRead?> FindAsync(long tenantId, long announcementId, long? userId, CancellationToken cancellationToken = default);
/// <summary>
/// 新增已读记录。
/// </summary>
/// <param name="record">已读实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task AddAsync(TenantAnnouncementRead record, CancellationToken cancellationToken = default);
/// <summary>
/// 保存变更。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,97 @@
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// <summary>
/// 租户公告仓储。
/// </summary>
public interface ITenantAnnouncementRepository
{
/// <summary>
/// 查询公告列表,按类型、状态与生效时间筛选。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="keyword">关键词(标题/内容)。</param>
/// <param name="status">公告状态。</param>
/// <param name="type">公告类型。</param>
/// <param name="isActive">启用状态。</param>
/// <param name="effectiveFrom">生效开始时间筛选。</param>
/// <param name="effectiveTo">生效结束时间筛选。</param>
/// <param name="effectiveAt">生效时间点,为空不限制。</param>
/// <param name="orderByPriority">是否按优先级降序和生效时间降序排序,默认 false。</param>
/// <param name="limit">限制返回数量,为空不限制。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>公告集合。</returns>
Task<IReadOnlyList<TenantAnnouncement>> SearchAsync(
long tenantId,
string? keyword,
AnnouncementStatus? status,
TenantAnnouncementType? type,
bool? isActive,
DateTime? effectiveFrom,
DateTime? effectiveTo,
DateTime? effectiveAt,
bool orderByPriority = false,
int? limit = null,
CancellationToken cancellationToken = default);
/// <summary>
/// 查询未读公告。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="userId">用户 ID。</param>
/// <param name="status">公告状态。</param>
/// <param name="isActive">启用状态。</param>
/// <param name="effectiveAt">生效时间点,为空不限制。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>未读公告集合。</returns>
Task<IReadOnlyList<TenantAnnouncement>> SearchUnreadAsync(
long tenantId,
long? userId,
AnnouncementStatus? status,
bool? isActive,
DateTime? effectiveAt,
CancellationToken cancellationToken = default);
/// <summary>
/// 按 ID 获取公告。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="announcementId">公告 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>公告实体或 null。</returns>
Task<TenantAnnouncement?> FindByIdAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default);
/// <summary>
/// 新增公告。
/// </summary>
/// <param name="announcement">公告实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task AddAsync(TenantAnnouncement announcement, CancellationToken cancellationToken = default);
/// <summary>
/// 更新公告。
/// </summary>
/// <param name="announcement">公告实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task UpdateAsync(TenantAnnouncement announcement, CancellationToken cancellationToken = default);
/// <summary>
/// 删除公告。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="announcementId">公告 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task DeleteAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default);
/// <summary>
/// 保存变更。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,23 @@
using TakeoutSaaS.Domain.Tenants.Entities;
namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// <summary>
/// 租户配额使用历史仓储。
/// </summary>
public interface ITenantQuotaUsageHistoryRepository
{
/// <summary>
/// 新增历史记录。
/// </summary>
/// <param name="history">历史记录实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task AddAsync(TenantQuotaUsageHistory history, CancellationToken cancellationToken = default);
/// <summary>
/// 持久化。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,64 @@
using TakeoutSaaS.Domain.Tenants.Entities;
namespace TakeoutSaaS.Domain.Tenants.Services;
/// <summary>
/// 账单领域服务接口。
/// 负责处理账单生成、账单编号生成、逾期处理等跨实体的业务逻辑。
/// </summary>
public interface IBillingDomainService
{
/// <summary>
/// 根据订阅信息生成账单。
/// </summary>
/// <param name="subscription">租户订阅信息。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>生成的账单实体。</returns>
Task<TenantBillingStatement> GenerateSubscriptionBillingAsync(
TenantSubscription subscription,
CancellationToken cancellationToken = default);
/// <summary>
/// 根据配额包购买信息生成账单。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="quotaPackage">配额包信息。</param>
/// <param name="quantity">购买数量。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>生成的账单实体。</returns>
Task<TenantBillingStatement> GenerateQuotaPurchaseBillingAsync(
long tenantId,
QuotaPackage quotaPackage,
int quantity,
CancellationToken cancellationToken = default);
/// <summary>
/// 生成唯一的账单编号。
/// 格式示例BIL-20251217-000001
/// </summary>
/// <returns>账单编号。</returns>
string GenerateStatementNo();
/// <summary>
/// 处理逾期账单(批量标记逾期状态)。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>处理的账单数量。</returns>
Task<int> ProcessOverdueBillingsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 计算账单总金额(含折扣和税费)。
/// </summary>
/// <param name="baseAmount">基础金额。</param>
/// <param name="discountAmount">折扣金额。</param>
/// <param name="taxAmount">税费金额。</param>
/// <returns>总金额。</returns>
decimal CalculateTotalAmount(decimal baseAmount, decimal discountAmount, decimal taxAmount);
/// <summary>
/// 验证账单状态是否可以进行支付操作。
/// </summary>
/// <param name="billing">账单实体。</param>
/// <returns>是否可以支付。</returns>
bool CanProcessPayment(TenantBillingStatement billing);
}

View File

@@ -0,0 +1,33 @@
using TakeoutSaaS.Domain.Tenants.Entities;
namespace TakeoutSaaS.Domain.Tenants.Services;
/// <summary>
/// 账单导出服务接口。
/// </summary>
public interface IBillingExportService
{
/// <summary>
/// 导出为 ExcelXLSX
/// </summary>
/// <param name="billings">账单数据。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>文件字节数组。</returns>
Task<byte[]> ExportToExcelAsync(IReadOnlyList<TenantBillingStatement> billings, CancellationToken cancellationToken = default);
/// <summary>
/// 导出为 PDF。
/// </summary>
/// <param name="billings">账单数据。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>文件字节数组。</returns>
Task<byte[]> ExportToPdfAsync(IReadOnlyList<TenantBillingStatement> billings, CancellationToken cancellationToken = default);
/// <summary>
/// 导出为 CSV。
/// </summary>
/// <param name="billings">账单数据。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>文件字节数组。</returns>
Task<byte[]> ExportToCsvAsync(IReadOnlyList<TenantBillingStatement> billings, CancellationToken cancellationToken = default);
}