docs: add xml comments for tenant modules

This commit is contained in:
2025-12-03 22:56:35 +08:00
parent 83c81d5fd1
commit bf88f0e041
28 changed files with 661 additions and 40 deletions

View File

@@ -7,9 +7,33 @@ namespace TakeoutSaaS.Application.App.Tenants.Commands;
/// <summary> /// <summary>
/// 套餐升降配命令。 /// 套餐升降配命令。
/// </summary> /// </summary>
public sealed record ChangeTenantSubscriptionPlanCommand( public sealed record ChangeTenantSubscriptionPlanCommand : IRequest<TenantSubscriptionDto>
[property: Required] long TenantId, {
[property: Required] long TenantSubscriptionId, /// <summary>
[property: Required] long TargetPackageId, /// 租户 ID雪花算法
bool Immediate, /// </summary>
string? Notes) : IRequest<TenantSubscriptionDto>; [Required]
public long TenantId { get; init; }
/// <summary>
/// 现有订阅 ID。
/// </summary>
[Required]
public long TenantSubscriptionId { get; init; }
/// <summary>
/// 目标套餐 ID。
/// </summary>
[Required]
public long TargetPackageId { get; init; }
/// <summary>
/// 是否立即生效,否则在下一结算周期生效。
/// </summary>
public bool Immediate { get; init; }
/// <summary>
/// 调整备注。
/// </summary>
public string? Notes { get; init; }
}

View File

@@ -9,12 +9,43 @@ namespace TakeoutSaaS.Application.App.Tenants.Commands;
/// </summary> /// </summary>
public sealed record CreateTenantAnnouncementCommand : IRequest<TenantAnnouncementDto> public sealed record CreateTenantAnnouncementCommand : IRequest<TenantAnnouncementDto>
{ {
/// <summary>
/// 租户 ID雪花算法
/// </summary>
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 公告标题。
/// </summary>
public string Title { get; init; } = string.Empty; public string Title { get; init; } = string.Empty;
/// <summary>
/// 公告正文内容。
/// </summary>
public string Content { get; init; } = string.Empty; public string Content { get; init; } = string.Empty;
/// <summary>
/// 公告类型。
/// </summary>
public TenantAnnouncementType AnnouncementType { get; init; } = TenantAnnouncementType.System; public TenantAnnouncementType AnnouncementType { get; init; } = TenantAnnouncementType.System;
/// <summary>
/// 优先级,数值越大越靠前。
/// </summary>
public int Priority { get; init; } = 0; public int Priority { get; init; } = 0;
/// <summary>
/// 生效开始时间UTC
/// </summary>
public DateTime EffectiveFrom { get; init; } = DateTime.UtcNow; public DateTime EffectiveFrom { get; init; } = DateTime.UtcNow;
/// <summary>
/// 生效结束时间UTC为空则长期有效。
/// </summary>
public DateTime? EffectiveTo { get; init; } public DateTime? EffectiveTo { get; init; }
/// <summary>
/// 是否启用。
/// </summary>
public bool IsActive { get; init; } = true; public bool IsActive { get; init; } = true;
} }

View File

@@ -9,13 +9,48 @@ namespace TakeoutSaaS.Application.App.Tenants.Commands;
/// </summary> /// </summary>
public sealed record CreateTenantBillingCommand : IRequest<TenantBillingDto> public sealed record CreateTenantBillingCommand : IRequest<TenantBillingDto>
{ {
/// <summary>
/// 租户 ID雪花算法
/// </summary>
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 账单编号。
/// </summary>
public string StatementNo { get; init; } = string.Empty; public string StatementNo { get; init; } = string.Empty;
/// <summary>
/// 计费周期开始时间UTC
/// </summary>
public DateTime PeriodStart { get; init; } public DateTime PeriodStart { get; init; }
/// <summary>
/// 计费周期结束时间UTC
/// </summary>
public DateTime PeriodEnd { get; init; } public DateTime PeriodEnd { get; init; }
/// <summary>
/// 应付金额。
/// </summary>
public decimal AmountDue { get; init; } public decimal AmountDue { get; init; }
/// <summary>
/// 已付金额。
/// </summary>
public decimal AmountPaid { get; init; } public decimal AmountPaid { get; init; }
/// <summary>
/// 账单状态。
/// </summary>
public TenantBillingStatus Status { get; init; } = TenantBillingStatus.Pending; public TenantBillingStatus Status { get; init; } = TenantBillingStatus.Pending;
/// <summary>
/// 到期日UTC
/// </summary>
public DateTime DueDate { get; init; } public DateTime DueDate { get; init; }
/// <summary>
/// 账单明细 JSON。
/// </summary>
public string? LineItemsJson { get; init; } public string? LineItemsJson { get; init; }
} }

View File

@@ -7,9 +7,32 @@ namespace TakeoutSaaS.Application.App.Tenants.Commands;
/// <summary> /// <summary>
/// 新建或续费订阅。 /// 新建或续费订阅。
/// </summary> /// </summary>
public sealed record CreateTenantSubscriptionCommand( public sealed record CreateTenantSubscriptionCommand : IRequest<TenantSubscriptionDto>
[property: Required] long TenantId, {
[property: Required] long TenantPackageId, /// <summary>
int DurationMonths, /// 租户 ID雪花算法
bool AutoRenew, /// </summary>
string? Notes) : IRequest<TenantSubscriptionDto>; [Required]
public long TenantId { get; init; }
/// <summary>
/// 套餐 ID。
/// </summary>
[Required]
public long TenantPackageId { get; init; }
/// <summary>
/// 订阅时长(月)。
/// </summary>
public int DurationMonths { get; init; }
/// <summary>
/// 是否自动续费。
/// </summary>
public bool AutoRenew { get; init; }
/// <summary>
/// 备注信息。
/// </summary>
public string? Notes { get; init; }
}

View File

@@ -7,6 +7,13 @@ namespace TakeoutSaaS.Application.App.Tenants.Commands;
/// </summary> /// </summary>
public sealed record DeleteTenantAnnouncementCommand : IRequest<bool> public sealed record DeleteTenantAnnouncementCommand : IRequest<bool>
{ {
/// <summary>
/// 租户 ID雪花算法
/// </summary>
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 公告 ID。
/// </summary>
public long AnnouncementId { get; init; } public long AnnouncementId { get; init; }
} }

View File

@@ -8,6 +8,13 @@ namespace TakeoutSaaS.Application.App.Tenants.Commands;
/// </summary> /// </summary>
public sealed record MarkTenantAnnouncementReadCommand : IRequest<TenantAnnouncementDto?> public sealed record MarkTenantAnnouncementReadCommand : IRequest<TenantAnnouncementDto?>
{ {
/// <summary>
/// 租户 ID雪花算法
/// </summary>
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 公告 ID。
/// </summary>
public long AnnouncementId { get; init; } public long AnnouncementId { get; init; }
} }

View File

@@ -8,8 +8,23 @@ namespace TakeoutSaaS.Application.App.Tenants.Commands;
/// </summary> /// </summary>
public sealed record MarkTenantBillingPaidCommand : IRequest<TenantBillingDto?> public sealed record MarkTenantBillingPaidCommand : IRequest<TenantBillingDto?>
{ {
/// <summary>
/// 租户 ID雪花算法
/// </summary>
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 账单 ID。
/// </summary>
public long BillingId { get; init; } public long BillingId { get; init; }
/// <summary>
/// 本次支付金额。
/// </summary>
public decimal AmountPaid { get; init; } public decimal AmountPaid { get; init; }
/// <summary>
/// 支付时间UTC
/// </summary>
public DateTime PaidAt { get; init; } = DateTime.UtcNow; public DateTime PaidAt { get; init; } = DateTime.UtcNow;
} }

View File

@@ -8,6 +8,13 @@ namespace TakeoutSaaS.Application.App.Tenants.Commands;
/// </summary> /// </summary>
public sealed record MarkTenantNotificationReadCommand : IRequest<TenantNotificationDto?> public sealed record MarkTenantNotificationReadCommand : IRequest<TenantNotificationDto?>
{ {
/// <summary>
/// 租户 ID雪花算法
/// </summary>
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 通知 ID。
/// </summary>
public long NotificationId { get; init; } public long NotificationId { get; init; }
} }

View File

@@ -7,15 +7,65 @@ namespace TakeoutSaaS.Application.App.Tenants.Commands;
/// <summary> /// <summary>
/// 注册租户命令。 /// 注册租户命令。
/// </summary> /// </summary>
public sealed record RegisterTenantCommand( public sealed record RegisterTenantCommand : IRequest<TenantDto>
[property: Required, StringLength(64)] string Code, {
[property: Required, StringLength(128)] string Name, /// <summary>
string? ShortName, /// 唯一租户编码。
string? Industry, /// </summary>
string? ContactName, [Required]
string? ContactPhone, [StringLength(64)]
string? ContactEmail, public string Code { get; init; } = string.Empty;
[property: Required] long TenantPackageId,
int DurationMonths = 12, /// <summary>
bool AutoRenew = true, /// 租户名称。
DateTime? EffectiveFrom = null) : IRequest<TenantDto>; /// </summary>
[Required]
[StringLength(128)]
public string Name { get; init; } = string.Empty;
/// <summary>
/// 租户简称。
/// </summary>
public string? ShortName { get; init; }
/// <summary>
/// 行业类型。
/// </summary>
public string? Industry { get; init; }
/// <summary>
/// 联系人姓名。
/// </summary>
public string? ContactName { get; init; }
/// <summary>
/// 联系人电话。
/// </summary>
public string? ContactPhone { get; init; }
/// <summary>
/// 联系人邮箱。
/// </summary>
public string? ContactEmail { get; init; }
/// <summary>
/// 购买套餐 ID。
/// </summary>
[Required]
public long TenantPackageId { get; init; }
/// <summary>
/// 订阅时长(月),默认 12 个月。
/// </summary>
public int DurationMonths { get; init; } = 12;
/// <summary>
/// 是否自动续费。
/// </summary>
public bool AutoRenew { get; init; } = true;
/// <summary>
/// 生效时间UTC为空则立即生效。
/// </summary>
public DateTime? EffectiveFrom { get; init; }
}

View File

@@ -7,7 +7,21 @@ namespace TakeoutSaaS.Application.App.Tenants.Commands;
/// <summary> /// <summary>
/// 审核租户命令。 /// 审核租户命令。
/// </summary> /// </summary>
public sealed record ReviewTenantCommand( public sealed record ReviewTenantCommand : IRequest<TenantDto>
[property: Required] long TenantId, {
bool Approve, /// <summary>
string? Reason) : IRequest<TenantDto>; /// 租户 ID雪花算法
/// </summary>
[Required]
public long TenantId { get; init; }
/// <summary>
/// 是否通过审核。
/// </summary>
public bool Approve { get; init; }
/// <summary>
/// 审核备注或拒绝原因。
/// </summary>
public string? Reason { get; init; }
}

View File

@@ -7,15 +7,61 @@ namespace TakeoutSaaS.Application.App.Tenants.Commands;
/// <summary> /// <summary>
/// 提交租户实名认证资料。 /// 提交租户实名认证资料。
/// </summary> /// </summary>
public sealed record SubmitTenantVerificationCommand( public sealed record SubmitTenantVerificationCommand : IRequest<TenantVerificationDto>
[property: Required] long TenantId, {
string? BusinessLicenseNumber, /// <summary>
string? BusinessLicenseUrl, /// 租户 ID雪花算法
string? LegalPersonName, /// </summary>
string? LegalPersonIdNumber, [Required]
string? LegalPersonIdFrontUrl, public long TenantId { get; init; }
string? LegalPersonIdBackUrl,
string? BankAccountName, /// <summary>
string? BankAccountNumber, /// 营业执照编号。
string? BankName, /// </summary>
string? AdditionalDataJson) : IRequest<TenantVerificationDto>; public string? BusinessLicenseNumber { get; init; }
/// <summary>
/// 营业执照扫描件地址。
/// </summary>
public string? BusinessLicenseUrl { get; init; }
/// <summary>
/// 法人姓名。
/// </summary>
public string? LegalPersonName { get; init; }
/// <summary>
/// 法人身份证号。
/// </summary>
public string? LegalPersonIdNumber { get; init; }
/// <summary>
/// 法人身份证人像面图片地址。
/// </summary>
public string? LegalPersonIdFrontUrl { get; init; }
/// <summary>
/// 法人身份证国徽面图片地址。
/// </summary>
public string? LegalPersonIdBackUrl { get; init; }
/// <summary>
/// 对公账户户名。
/// </summary>
public string? BankAccountName { get; init; }
/// <summary>
/// 对公银行账号。
/// </summary>
public string? BankAccountNumber { get; init; }
/// <summary>
/// 开户行名称。
/// </summary>
public string? BankName { get; init; }
/// <summary>
/// 其他补充资料 JSON。
/// </summary>
public string? AdditionalDataJson { get; init; }
}

View File

@@ -9,13 +9,48 @@ namespace TakeoutSaaS.Application.App.Tenants.Commands;
/// </summary> /// </summary>
public sealed record UpdateTenantAnnouncementCommand : IRequest<TenantAnnouncementDto?> public sealed record UpdateTenantAnnouncementCommand : IRequest<TenantAnnouncementDto?>
{ {
/// <summary>
/// 租户 ID雪花算法
/// </summary>
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 公告 ID。
/// </summary>
public long AnnouncementId { get; init; } public long AnnouncementId { get; init; }
/// <summary>
/// 公告标题。
/// </summary>
public string Title { get; init; } = string.Empty; public string Title { get; init; } = string.Empty;
/// <summary>
/// 公告内容。
/// </summary>
public string Content { get; init; } = string.Empty; public string Content { get; init; } = string.Empty;
/// <summary>
/// 公告类型。
/// </summary>
public TenantAnnouncementType AnnouncementType { get; init; } = TenantAnnouncementType.System; public TenantAnnouncementType AnnouncementType { get; init; } = TenantAnnouncementType.System;
/// <summary>
/// 优先级,数值越大越靠前。
/// </summary>
public int Priority { get; init; } = 0; public int Priority { get; init; } = 0;
/// <summary>
/// 生效开始时间UTC
/// </summary>
public DateTime EffectiveFrom { get; init; } = DateTime.UtcNow; public DateTime EffectiveFrom { get; init; } = DateTime.UtcNow;
/// <summary>
/// 生效结束时间UTC为空则长期有效。
/// </summary>
public DateTime? EffectiveTo { get; init; } public DateTime? EffectiveTo { get; init; }
/// <summary>
/// 是否启用。
/// </summary>
public bool IsActive { get; init; } = true; public bool IsActive { get; init; } = true;
} }

View File

@@ -9,27 +9,60 @@ namespace TakeoutSaaS.Application.App.Tenants.Dto;
/// </summary> /// </summary>
public sealed class TenantAnnouncementDto public sealed class TenantAnnouncementDto
{ {
/// <summary>
/// 公告 ID雪花算法序列化为字符串
/// </summary>
[JsonConverter(typeof(SnowflakeIdJsonConverter))] [JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long Id { get; init; } public long Id { get; init; }
/// <summary>
/// 租户 ID雪花算法序列化为字符串
/// </summary>
[JsonConverter(typeof(SnowflakeIdJsonConverter))] [JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 公告标题。
/// </summary>
public string Title { get; init; } = string.Empty; public string Title { get; init; } = string.Empty;
/// <summary>
/// 公告正文内容。
/// </summary>
public string Content { get; init; } = string.Empty; public string Content { get; init; } = string.Empty;
/// <summary>
/// 公告类型。
/// </summary>
public TenantAnnouncementType AnnouncementType { get; init; } public TenantAnnouncementType AnnouncementType { get; init; }
/// <summary>
/// 优先级,数值越大越靠前。
/// </summary>
public int Priority { get; init; } public int Priority { get; init; }
/// <summary>
/// 生效开始时间UTC
/// </summary>
public DateTime EffectiveFrom { get; init; } public DateTime EffectiveFrom { get; init; }
/// <summary>
/// 生效结束时间UTC为空则长期有效。
/// </summary>
public DateTime? EffectiveTo { get; init; } public DateTime? EffectiveTo { get; init; }
/// <summary>
/// 是否启用。
/// </summary>
public bool IsActive { get; init; } public bool IsActive { get; init; }
/// <summary>
/// 当前用户是否已读。
/// </summary>
public bool IsRead { get; init; } public bool IsRead { get; init; }
/// <summary>
/// 已读时间UTC
/// </summary>
public DateTime? ReadAt { get; init; } public DateTime? ReadAt { get; init; }
} }

View File

@@ -9,25 +9,55 @@ namespace TakeoutSaaS.Application.App.Tenants.Dto;
/// </summary> /// </summary>
public sealed class TenantBillingDto public sealed class TenantBillingDto
{ {
/// <summary>
/// 账单 ID雪花算法序列化为字符串
/// </summary>
[JsonConverter(typeof(SnowflakeIdJsonConverter))] [JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long Id { get; init; } public long Id { get; init; }
/// <summary>
/// 租户 ID雪花算法序列化为字符串
/// </summary>
[JsonConverter(typeof(SnowflakeIdJsonConverter))] [JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 账单编号。
/// </summary>
public string StatementNo { get; init; } = string.Empty; public string StatementNo { get; init; } = string.Empty;
/// <summary>
/// 计费周期开始时间UTC
/// </summary>
public DateTime PeriodStart { get; init; } public DateTime PeriodStart { get; init; }
/// <summary>
/// 计费周期结束时间UTC
/// </summary>
public DateTime PeriodEnd { get; init; } public DateTime PeriodEnd { get; init; }
/// <summary>
/// 应付金额。
/// </summary>
public decimal AmountDue { get; init; } public decimal AmountDue { get; init; }
/// <summary>
/// 已付金额。
/// </summary>
public decimal AmountPaid { get; init; } public decimal AmountPaid { get; init; }
/// <summary>
/// 账单状态。
/// </summary>
public TenantBillingStatus Status { get; init; } public TenantBillingStatus Status { get; init; }
/// <summary>
/// 到期日UTC
/// </summary>
public DateTime DueDate { get; init; } public DateTime DueDate { get; init; }
/// <summary>
/// 账单明细 JSON。
/// </summary>
public string? LineItemsJson { get; init; } public string? LineItemsJson { get; init; }
} }

View File

@@ -9,23 +9,50 @@ namespace TakeoutSaaS.Application.App.Tenants.Dto;
/// </summary> /// </summary>
public sealed class TenantNotificationDto public sealed class TenantNotificationDto
{ {
/// <summary>
/// 通知 ID雪花算法序列化为字符串
/// </summary>
[JsonConverter(typeof(SnowflakeIdJsonConverter))] [JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long Id { get; init; } public long Id { get; init; }
/// <summary>
/// 租户 ID雪花算法序列化为字符串
/// </summary>
[JsonConverter(typeof(SnowflakeIdJsonConverter))] [JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 通知标题。
/// </summary>
public string Title { get; init; } = string.Empty; public string Title { get; init; } = string.Empty;
/// <summary>
/// 通知内容。
/// </summary>
public string Message { get; init; } = string.Empty; public string Message { get; init; } = string.Empty;
/// <summary>
/// 通道类型(如站内信、短信、邮件)。
/// </summary>
public TenantNotificationChannel Channel { get; init; } public TenantNotificationChannel Channel { get; init; }
/// <summary>
/// 通知等级。
/// </summary>
public TenantNotificationSeverity Severity { get; init; } public TenantNotificationSeverity Severity { get; init; }
/// <summary>
/// 发送时间UTC
/// </summary>
public DateTime SentAt { get; init; } public DateTime SentAt { get; init; }
/// <summary>
/// 阅读时间UTC
/// </summary>
public DateTime? ReadAt { get; init; } public DateTime? ReadAt { get; init; }
/// <summary>
/// 附加元数据 JSON。
/// </summary>
public string? MetadataJson { get; init; } public string? MetadataJson { get; init; }
} }

View File

@@ -8,6 +8,13 @@ namespace TakeoutSaaS.Application.App.Tenants.Queries;
/// </summary> /// </summary>
public sealed record GetTenantAnnouncementQuery : IRequest<TenantAnnouncementDto?> public sealed record GetTenantAnnouncementQuery : IRequest<TenantAnnouncementDto?>
{ {
/// <summary>
/// 租户 ID雪花算法
/// </summary>
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 公告 ID。
/// </summary>
public long AnnouncementId { get; init; } public long AnnouncementId { get; init; }
} }

View File

@@ -8,6 +8,13 @@ namespace TakeoutSaaS.Application.App.Tenants.Queries;
/// </summary> /// </summary>
public sealed record GetTenantBillQuery : IRequest<TenantBillingDto?> public sealed record GetTenantBillQuery : IRequest<TenantBillingDto?>
{ {
/// <summary>
/// 租户 ID雪花算法
/// </summary>
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 账单 ID。
/// </summary>
public long BillingId { get; init; } public long BillingId { get; init; }
} }

View File

@@ -10,10 +10,33 @@ namespace TakeoutSaaS.Application.App.Tenants.Queries;
/// </summary> /// </summary>
public sealed record SearchTenantAnnouncementsQuery : IRequest<PagedResult<TenantAnnouncementDto>> public sealed record SearchTenantAnnouncementsQuery : IRequest<PagedResult<TenantAnnouncementDto>>
{ {
/// <summary>
/// 租户 ID雪花算法
/// </summary>
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 公告类型筛选。
/// </summary>
public TenantAnnouncementType? AnnouncementType { get; init; } public TenantAnnouncementType? AnnouncementType { get; init; }
/// <summary>
/// 是否筛选启用状态。
/// </summary>
public bool? IsActive { get; init; } public bool? IsActive { get; init; }
/// <summary>
/// 仅返回当前有效期内的公告。
/// </summary>
public bool? OnlyEffective { get; init; } public bool? OnlyEffective { get; init; }
/// <summary>
/// 页码(从 1 开始)。
/// </summary>
public int Page { get; init; } = 1; public int Page { get; init; } = 1;
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; init; } = 20; public int PageSize { get; init; } = 20;
} }

View File

@@ -10,10 +10,33 @@ namespace TakeoutSaaS.Application.App.Tenants.Queries;
/// </summary> /// </summary>
public sealed record SearchTenantBillsQuery : IRequest<PagedResult<TenantBillingDto>> public sealed record SearchTenantBillsQuery : IRequest<PagedResult<TenantBillingDto>>
{ {
/// <summary>
/// 租户 ID雪花算法
/// </summary>
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 账单状态筛选。
/// </summary>
public TenantBillingStatus? Status { get; init; } public TenantBillingStatus? Status { get; init; }
/// <summary>
/// 账单起始时间UTC筛选。
/// </summary>
public DateTime? From { get; init; } public DateTime? From { get; init; }
/// <summary>
/// 账单结束时间UTC筛选。
/// </summary>
public DateTime? To { get; init; } public DateTime? To { get; init; }
/// <summary>
/// 页码(从 1 开始)。
/// </summary>
public int Page { get; init; } = 1; public int Page { get; init; } = 1;
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; init; } = 20; public int PageSize { get; init; } = 20;
} }

View File

@@ -10,9 +10,28 @@ namespace TakeoutSaaS.Application.App.Tenants.Queries;
/// </summary> /// </summary>
public sealed record SearchTenantNotificationsQuery : IRequest<PagedResult<TenantNotificationDto>> public sealed record SearchTenantNotificationsQuery : IRequest<PagedResult<TenantNotificationDto>>
{ {
/// <summary>
/// 租户 ID雪花算法
/// </summary>
public long TenantId { get; init; } public long TenantId { get; init; }
/// <summary>
/// 通知等级筛选。
/// </summary>
public TenantNotificationSeverity? Severity { get; init; } public TenantNotificationSeverity? Severity { get; init; }
/// <summary>
/// 仅返回未读通知。
/// </summary>
public bool? UnreadOnly { get; init; } public bool? UnreadOnly { get; init; }
/// <summary>
/// 页码(从 1 开始)。
/// </summary>
public int Page { get; init; } = 1; public int Page { get; init; } = 1;
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; init; } = 20; public int PageSize { get; init; } = 20;
} }

View File

@@ -10,13 +10,45 @@ namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// </summary> /// </summary>
public interface ITenantAnnouncementReadRepository 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); 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); 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); Task<TenantAnnouncementRead?> FindAsync(long tenantId, long announcementId, long? userId, CancellationToken cancellationToken = default);
/// <summary>
/// 新增已读记录。
/// </summary>
/// <param name="record">已读实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task AddAsync(TenantAnnouncementRead record, CancellationToken cancellationToken = default); Task AddAsync(TenantAnnouncementRead record, CancellationToken cancellationToken = default);
/// <summary>
/// 保存变更。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
Task SaveChangesAsync(CancellationToken cancellationToken = default); Task SaveChangesAsync(CancellationToken cancellationToken = default);
} }

View File

@@ -11,6 +11,15 @@ namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// </summary> /// </summary>
public interface ITenantAnnouncementRepository public interface ITenantAnnouncementRepository
{ {
/// <summary>
/// 查询公告列表,按类型、启用状态与生效时间筛选。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="type">公告类型。</param>
/// <param name="isActive">启用状态。</param>
/// <param name="effectiveAt">生效时间点,为空不限制。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>公告集合。</returns>
Task<IReadOnlyList<TenantAnnouncement>> SearchAsync( Task<IReadOnlyList<TenantAnnouncement>> SearchAsync(
long tenantId, long tenantId,
TenantAnnouncementType? type, TenantAnnouncementType? type,
@@ -18,13 +27,40 @@ public interface ITenantAnnouncementRepository
DateTime? effectiveAt, DateTime? effectiveAt,
CancellationToken cancellationToken = default); 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); Task<TenantAnnouncement?> FindByIdAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default);
/// <summary>
/// 新增公告。
/// </summary>
/// <param name="announcement">公告实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task AddAsync(TenantAnnouncement announcement, CancellationToken cancellationToken = default); Task AddAsync(TenantAnnouncement announcement, CancellationToken cancellationToken = default);
/// <summary>
/// 更新公告。
/// </summary>
/// <param name="announcement">公告实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task UpdateAsync(TenantAnnouncement announcement, CancellationToken cancellationToken = default); Task UpdateAsync(TenantAnnouncement announcement, CancellationToken cancellationToken = default);
/// <summary>
/// 删除公告。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="announcementId">公告 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
Task DeleteAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default); Task DeleteAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default);
/// <summary>
/// 保存变更。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
Task SaveChangesAsync(CancellationToken cancellationToken = default); Task SaveChangesAsync(CancellationToken cancellationToken = default);
} }

View File

@@ -11,6 +11,15 @@ namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// </summary> /// </summary>
public interface ITenantBillingRepository public interface ITenantBillingRepository
{ {
/// <summary>
/// 查询账单列表,按状态与时间范围筛选。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="status">账单状态。</param>
/// <param name="from">开始时间UTC。</param>
/// <param name="to">结束时间UTC。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>账单集合。</returns>
Task<IReadOnlyList<TenantBillingStatement>> SearchAsync( Task<IReadOnlyList<TenantBillingStatement>> SearchAsync(
long tenantId, long tenantId,
TenantBillingStatus? status, TenantBillingStatus? status,
@@ -18,13 +27,41 @@ public interface ITenantBillingRepository
DateTime? to, DateTime? to,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary>
/// 按 ID 获取账单。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="billingId">账单 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>账单实体或 null。</returns>
Task<TenantBillingStatement?> FindByIdAsync(long tenantId, long billingId, CancellationToken cancellationToken = default); Task<TenantBillingStatement?> FindByIdAsync(long tenantId, long billingId, CancellationToken cancellationToken = default);
/// <summary>
/// 按账单编号获取账单。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="statementNo">账单编号。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>账单实体或 null。</returns>
Task<TenantBillingStatement?> FindByStatementNoAsync(long tenantId, string statementNo, CancellationToken cancellationToken = default); Task<TenantBillingStatement?> FindByStatementNoAsync(long tenantId, string statementNo, CancellationToken cancellationToken = default);
/// <summary>
/// 新增账单。
/// </summary>
/// <param name="bill">账单实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task AddAsync(TenantBillingStatement bill, CancellationToken cancellationToken = default); Task AddAsync(TenantBillingStatement bill, CancellationToken cancellationToken = default);
/// <summary>
/// 更新账单。
/// </summary>
/// <param name="bill">账单实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task UpdateAsync(TenantBillingStatement bill, CancellationToken cancellationToken = default); Task UpdateAsync(TenantBillingStatement bill, CancellationToken cancellationToken = default);
/// <summary>
/// 保存变更。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
Task SaveChangesAsync(CancellationToken cancellationToken = default); Task SaveChangesAsync(CancellationToken cancellationToken = default);
} }

View File

@@ -11,6 +11,16 @@ namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// </summary> /// </summary>
public interface ITenantNotificationRepository public interface ITenantNotificationRepository
{ {
/// <summary>
/// 查询通知列表,按等级、未读状态与时间范围筛选。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="severity">通知等级。</param>
/// <param name="unreadOnly">仅返回未读。</param>
/// <param name="from">开始时间UTC。</param>
/// <param name="to">结束时间UTC。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>通知集合。</returns>
Task<IReadOnlyList<TenantNotification>> SearchAsync( Task<IReadOnlyList<TenantNotification>> SearchAsync(
long tenantId, long tenantId,
TenantNotificationSeverity? severity, TenantNotificationSeverity? severity,
@@ -19,11 +29,32 @@ public interface ITenantNotificationRepository
DateTime? to, DateTime? to,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary>
/// 按 ID 获取通知。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="notificationId">通知 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>通知实体或 null。</returns>
Task<TenantNotification?> FindByIdAsync(long tenantId, long notificationId, CancellationToken cancellationToken = default); Task<TenantNotification?> FindByIdAsync(long tenantId, long notificationId, CancellationToken cancellationToken = default);
/// <summary>
/// 新增通知。
/// </summary>
/// <param name="notification">通知实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task AddAsync(TenantNotification notification, CancellationToken cancellationToken = default); Task AddAsync(TenantNotification notification, CancellationToken cancellationToken = default);
/// <summary>
/// 更新通知。
/// </summary>
/// <param name="notification">通知实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task UpdateAsync(TenantNotification notification, CancellationToken cancellationToken = default); Task UpdateAsync(TenantNotification notification, CancellationToken cancellationToken = default);
/// <summary>
/// 保存变更。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
Task SaveChangesAsync(CancellationToken cancellationToken = default); Task SaveChangesAsync(CancellationToken cancellationToken = default);
} }

View File

@@ -11,6 +11,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// </summary> /// </summary>
public sealed class EfTenantAnnouncementReadRepository(TakeoutAppDbContext context) : ITenantAnnouncementReadRepository public sealed class EfTenantAnnouncementReadRepository(TakeoutAppDbContext context) : ITenantAnnouncementReadRepository
{ {
/// <inheritdoc />
public Task<IReadOnlyList<TenantAnnouncementRead>> GetByAnnouncementAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default) public Task<IReadOnlyList<TenantAnnouncementRead>> GetByAnnouncementAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default)
{ {
return context.TenantAnnouncementReads.AsNoTracking() return context.TenantAnnouncementReads.AsNoTracking()
@@ -20,6 +21,7 @@ public sealed class EfTenantAnnouncementReadRepository(TakeoutAppDbContext conte
.ContinueWith(t => (IReadOnlyList<TenantAnnouncementRead>)t.Result, cancellationToken); .ContinueWith(t => (IReadOnlyList<TenantAnnouncementRead>)t.Result, cancellationToken);
} }
/// <inheritdoc />
public Task<IReadOnlyList<TenantAnnouncementRead>> GetByAnnouncementAsync(long tenantId, IEnumerable<long> announcementIds, long? userId, CancellationToken cancellationToken = default) public Task<IReadOnlyList<TenantAnnouncementRead>> GetByAnnouncementAsync(long tenantId, IEnumerable<long> announcementIds, long? userId, CancellationToken cancellationToken = default)
{ {
var ids = announcementIds.Distinct().ToArray(); var ids = announcementIds.Distinct().ToArray();
@@ -46,17 +48,20 @@ public sealed class EfTenantAnnouncementReadRepository(TakeoutAppDbContext conte
.ContinueWith(t => (IReadOnlyList<TenantAnnouncementRead>)t.Result, cancellationToken); .ContinueWith(t => (IReadOnlyList<TenantAnnouncementRead>)t.Result, cancellationToken);
} }
/// <inheritdoc />
public Task<TenantAnnouncementRead?> FindAsync(long tenantId, long announcementId, long? userId, CancellationToken cancellationToken = default) public Task<TenantAnnouncementRead?> FindAsync(long tenantId, long announcementId, long? userId, CancellationToken cancellationToken = default)
{ {
return context.TenantAnnouncementReads return context.TenantAnnouncementReads
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.AnnouncementId == announcementId && x.UserId == userId, cancellationToken); .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.AnnouncementId == announcementId && x.UserId == userId, cancellationToken);
} }
/// <inheritdoc />
public Task AddAsync(TenantAnnouncementRead record, CancellationToken cancellationToken = default) public Task AddAsync(TenantAnnouncementRead record, CancellationToken cancellationToken = default)
{ {
return context.TenantAnnouncementReads.AddAsync(record, cancellationToken).AsTask(); return context.TenantAnnouncementReads.AddAsync(record, cancellationToken).AsTask();
} }
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default) public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{ {
return context.SaveChangesAsync(cancellationToken); return context.SaveChangesAsync(cancellationToken);

View File

@@ -12,6 +12,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// </summary> /// </summary>
public sealed class EfTenantAnnouncementRepository(TakeoutAppDbContext context) : ITenantAnnouncementRepository public sealed class EfTenantAnnouncementRepository(TakeoutAppDbContext context) : ITenantAnnouncementRepository
{ {
/// <inheritdoc />
public Task<IReadOnlyList<TenantAnnouncement>> SearchAsync( public Task<IReadOnlyList<TenantAnnouncement>> SearchAsync(
long tenantId, long tenantId,
TenantAnnouncementType? type, TenantAnnouncementType? type,
@@ -45,23 +46,27 @@ public sealed class EfTenantAnnouncementRepository(TakeoutAppDbContext context)
.ContinueWith(t => (IReadOnlyList<TenantAnnouncement>)t.Result, cancellationToken); .ContinueWith(t => (IReadOnlyList<TenantAnnouncement>)t.Result, cancellationToken);
} }
/// <inheritdoc />
public Task<TenantAnnouncement?> FindByIdAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default) public Task<TenantAnnouncement?> FindByIdAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default)
{ {
return context.TenantAnnouncements.AsNoTracking() return context.TenantAnnouncements.AsNoTracking()
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == announcementId, cancellationToken); .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == announcementId, cancellationToken);
} }
/// <inheritdoc />
public Task AddAsync(TenantAnnouncement announcement, CancellationToken cancellationToken = default) public Task AddAsync(TenantAnnouncement announcement, CancellationToken cancellationToken = default)
{ {
return context.TenantAnnouncements.AddAsync(announcement, cancellationToken).AsTask(); return context.TenantAnnouncements.AddAsync(announcement, cancellationToken).AsTask();
} }
/// <inheritdoc />
public Task UpdateAsync(TenantAnnouncement announcement, CancellationToken cancellationToken = default) public Task UpdateAsync(TenantAnnouncement announcement, CancellationToken cancellationToken = default)
{ {
context.TenantAnnouncements.Update(announcement); context.TenantAnnouncements.Update(announcement);
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <inheritdoc />
public async Task DeleteAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default) public async Task DeleteAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default)
{ {
var entity = await context.TenantAnnouncements.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == announcementId, cancellationToken); var entity = await context.TenantAnnouncements.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == announcementId, cancellationToken);
@@ -71,6 +76,7 @@ public sealed class EfTenantAnnouncementRepository(TakeoutAppDbContext context)
} }
} }
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default) public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{ {
return context.SaveChangesAsync(cancellationToken); return context.SaveChangesAsync(cancellationToken);

View File

@@ -12,6 +12,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// </summary> /// </summary>
public sealed class EfTenantBillingRepository(TakeoutAppDbContext context) : ITenantBillingRepository public sealed class EfTenantBillingRepository(TakeoutAppDbContext context) : ITenantBillingRepository
{ {
/// <inheritdoc />
public Task<IReadOnlyList<TenantBillingStatement>> SearchAsync( public Task<IReadOnlyList<TenantBillingStatement>> SearchAsync(
long tenantId, long tenantId,
TenantBillingStatus? status, TenantBillingStatus? status,
@@ -43,29 +44,34 @@ public sealed class EfTenantBillingRepository(TakeoutAppDbContext context) : ITe
.ContinueWith(t => (IReadOnlyList<TenantBillingStatement>)t.Result, cancellationToken); .ContinueWith(t => (IReadOnlyList<TenantBillingStatement>)t.Result, cancellationToken);
} }
/// <inheritdoc />
public Task<TenantBillingStatement?> FindByIdAsync(long tenantId, long billingId, CancellationToken cancellationToken = default) public Task<TenantBillingStatement?> FindByIdAsync(long tenantId, long billingId, CancellationToken cancellationToken = default)
{ {
return context.TenantBillingStatements.AsNoTracking() return context.TenantBillingStatements.AsNoTracking()
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == billingId, cancellationToken); .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == billingId, cancellationToken);
} }
/// <inheritdoc />
public Task<TenantBillingStatement?> FindByStatementNoAsync(long tenantId, string statementNo, CancellationToken cancellationToken = default) public Task<TenantBillingStatement?> FindByStatementNoAsync(long tenantId, string statementNo, CancellationToken cancellationToken = default)
{ {
return context.TenantBillingStatements.AsNoTracking() return context.TenantBillingStatements.AsNoTracking()
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StatementNo == statementNo, cancellationToken); .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StatementNo == statementNo, cancellationToken);
} }
/// <inheritdoc />
public Task AddAsync(TenantBillingStatement bill, CancellationToken cancellationToken = default) public Task AddAsync(TenantBillingStatement bill, CancellationToken cancellationToken = default)
{ {
return context.TenantBillingStatements.AddAsync(bill, cancellationToken).AsTask(); return context.TenantBillingStatements.AddAsync(bill, cancellationToken).AsTask();
} }
/// <inheritdoc />
public Task UpdateAsync(TenantBillingStatement bill, CancellationToken cancellationToken = default) public Task UpdateAsync(TenantBillingStatement bill, CancellationToken cancellationToken = default)
{ {
context.TenantBillingStatements.Update(bill); context.TenantBillingStatements.Update(bill);
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default) public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{ {
return context.SaveChangesAsync(cancellationToken); return context.SaveChangesAsync(cancellationToken);

View File

@@ -12,6 +12,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// </summary> /// </summary>
public sealed class EfTenantNotificationRepository(TakeoutAppDbContext context) : ITenantNotificationRepository public sealed class EfTenantNotificationRepository(TakeoutAppDbContext context) : ITenantNotificationRepository
{ {
/// <inheritdoc />
public Task<IReadOnlyList<TenantNotification>> SearchAsync( public Task<IReadOnlyList<TenantNotification>> SearchAsync(
long tenantId, long tenantId,
TenantNotificationSeverity? severity, TenantNotificationSeverity? severity,
@@ -49,23 +50,27 @@ public sealed class EfTenantNotificationRepository(TakeoutAppDbContext context)
.ContinueWith(t => (IReadOnlyList<TenantNotification>)t.Result, cancellationToken); .ContinueWith(t => (IReadOnlyList<TenantNotification>)t.Result, cancellationToken);
} }
/// <inheritdoc />
public Task<TenantNotification?> FindByIdAsync(long tenantId, long notificationId, CancellationToken cancellationToken = default) public Task<TenantNotification?> FindByIdAsync(long tenantId, long notificationId, CancellationToken cancellationToken = default)
{ {
return context.TenantNotifications return context.TenantNotifications
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == notificationId, cancellationToken); .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == notificationId, cancellationToken);
} }
/// <inheritdoc />
public Task AddAsync(TenantNotification notification, CancellationToken cancellationToken = default) public Task AddAsync(TenantNotification notification, CancellationToken cancellationToken = default)
{ {
return context.TenantNotifications.AddAsync(notification, cancellationToken).AsTask(); return context.TenantNotifications.AddAsync(notification, cancellationToken).AsTask();
} }
/// <inheritdoc />
public Task UpdateAsync(TenantNotification notification, CancellationToken cancellationToken = default) public Task UpdateAsync(TenantNotification notification, CancellationToken cancellationToken = default)
{ {
context.TenantNotifications.Update(notification); context.TenantNotifications.Update(notification);
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default) public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{ {
return context.SaveChangesAsync(cancellationToken); return context.SaveChangesAsync(cancellationToken);