feat:商户管理

This commit is contained in:
2025-12-29 16:40:27 +08:00
parent 57f4c2d394
commit dd91c1010a
62 changed files with 10536 additions and 165 deletions

View File

@@ -0,0 +1,17 @@
namespace TakeoutSaaS.Domain.Common.Enums;
/// <summary>
/// 经营模式。
/// </summary>
public enum OperatingMode
{
/// <summary>
/// 同一主体。
/// </summary>
SameEntity = 1,
/// <summary>
/// 不同主体。
/// </summary>
DifferentEntity = 2
}

View File

@@ -1,3 +1,4 @@
using TakeoutSaaS.Domain.Common.Enums;
using TakeoutSaaS.Domain.Merchants.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
@@ -103,6 +104,31 @@ public sealed class Merchant : MultiTenantEntityBase
/// </summary>
public MerchantStatus Status { get; set; } = MerchantStatus.Pending;
/// <summary>
/// 经营模式(同一主体/不同主体)。
/// </summary>
public OperatingMode? OperatingMode { get; set; }
/// <summary>
/// 是否冻结业务。
/// </summary>
public bool IsFrozen { get; set; }
/// <summary>
/// 冻结原因。
/// </summary>
public string? FrozenReason { get; set; }
/// <summary>
/// 冻结时间。
/// </summary>
public DateTime? FrozenAt { get; set; }
/// <summary>
/// 最近一次审核人。
/// </summary>
public long? LastReviewedBy { get; set; }
/// <summary>
/// 审核备注或驳回原因。
/// </summary>
@@ -117,4 +143,39 @@ public sealed class Merchant : MultiTenantEntityBase
/// 最近一次审核时间。
/// </summary>
public DateTime? LastReviewedAt { get; set; }
/// <summary>
/// 审核通过人。
/// </summary>
public long? ApprovedBy { get; set; }
/// <summary>
/// 审核通过时间。
/// </summary>
public DateTime? ApprovedAt { get; set; }
/// <summary>
/// 当前领取人。
/// </summary>
public long? ClaimedBy { get; set; }
/// <summary>
/// 当前领取人姓名。
/// </summary>
public string? ClaimedByName { get; set; }
/// <summary>
/// 领取时间。
/// </summary>
public DateTime? ClaimedAt { get; set; }
/// <summary>
/// 领取过期时间。
/// </summary>
public DateTime? ClaimExpiresAt { get; set; }
/// <summary>
/// 并发控制版本。
/// </summary>
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
}

View File

@@ -37,4 +37,9 @@ public sealed class MerchantAuditLog : MultiTenantEntityBase
/// 操作人名称。
/// </summary>
public string? OperatorName { get; set; }
/// <summary>
/// 操作 IP。
/// </summary>
public string? IpAddress { get; set; }
}

View File

@@ -0,0 +1,49 @@
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Merchants.Entities;
/// <summary>
/// 商户变更日志。
/// </summary>
public sealed class MerchantChangeLog : MultiTenantEntityBase
{
/// <summary>
/// 商户标识。
/// </summary>
public long MerchantId { get; set; }
/// <summary>
/// 变更字段名。
/// </summary>
public string FieldName { get; set; } = string.Empty;
/// <summary>
/// 变更前值。
/// </summary>
public string? OldValue { get; set; }
/// <summary>
/// 变更后值。
/// </summary>
public string? NewValue { get; set; }
/// <summary>
/// 变更类型。
/// </summary>
public string ChangeType { get; set; } = "Update";
/// <summary>
/// 变更人 ID。
/// </summary>
public long? ChangedBy { get; set; }
/// <summary>
/// 变更人名称。
/// </summary>
public string? ChangedByName { get; set; }
/// <summary>
/// 变更原因。
/// </summary>
public string? ChangeReason { get; set; }
}

View File

@@ -33,5 +33,40 @@ public enum MerchantAuditAction
/// <summary>
/// 商户审核结果。
/// </summary>
MerchantReviewed = 5
MerchantReviewed = 5,
/// <summary>
/// 领取审核。
/// </summary>
ReviewClaimed = 6,
/// <summary>
/// 释放审核。
/// </summary>
ReviewReleased = 7,
/// <summary>
/// 审核通过。
/// </summary>
ReviewApproved = 8,
/// <summary>
/// 审核驳回。
/// </summary>
ReviewRejected = 9,
/// <summary>
/// 撤销审核。
/// </summary>
ReviewRevoked = 10,
/// <summary>
/// 关键信息变更进入待审核。
/// </summary>
ReviewPendingReApproval = 11,
/// <summary>
/// 强制接管审核。
/// </summary>
ReviewForceClaimed = 12
}

View File

@@ -1,3 +1,4 @@
using TakeoutSaaS.Domain.Common.Enums;
using TakeoutSaaS.Domain.Merchants.Entities;
using TakeoutSaaS.Domain.Merchants.Enums;
@@ -17,6 +18,22 @@ public interface IMerchantRepository
/// <returns>商户实体或 null。</returns>
Task<Merchant?> FindByIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 依据标识获取商户(忽略租户过滤)。
/// </summary>
/// <param name="merchantId">商户 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>商户实体或 null。</returns>
Task<Merchant?> FindByIdAsync(long merchantId, CancellationToken cancellationToken = default);
/// <summary>
/// 依据租户标识获取商户(忽略租户过滤)。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>商户实体或 null。</returns>
Task<Merchant?> FindByTenantIdAsync(long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 按状态筛选商户列表。
/// </summary>
@@ -26,6 +43,22 @@ public interface IMerchantRepository
/// <returns>商户集合。</returns>
Task<IReadOnlyList<Merchant>> SearchAsync(long tenantId, MerchantStatus? status, CancellationToken cancellationToken = default);
/// <summary>
/// 按条件筛选商户列表(支持跨租户)。
/// </summary>
/// <param name="tenantId">租户 ID为 null 时查询全部租户。</param>
/// <param name="status">状态过滤。</param>
/// <param name="operatingMode">经营模式过滤。</param>
/// <param name="keyword">关键词过滤。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>商户集合。</returns>
Task<IReadOnlyList<Merchant>> SearchAsync(
long? tenantId,
MerchantStatus? status,
OperatingMode? operatingMode,
string? keyword,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取指定商户的员工列表。
/// </summary>
@@ -168,6 +201,14 @@ public interface IMerchantRepository
/// <returns>异步任务。</returns>
Task AddAuditLogAsync(MerchantAuditLog log, CancellationToken cancellationToken = default);
/// <summary>
/// 记录变更日志。
/// </summary>
/// <param name="log">变更日志实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task AddChangeLogAsync(MerchantChangeLog log, CancellationToken cancellationToken = default);
/// <summary>
/// 获取审核日志。
/// </summary>
@@ -176,4 +217,18 @@ public interface IMerchantRepository
/// <param name="cancellationToken">取消标记。</param>
/// <returns>审核日志列表。</returns>
Task<IReadOnlyList<MerchantAuditLog>> GetAuditLogsAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 获取变更日志。
/// </summary>
/// <param name="merchantId">商户 ID。</param>
/// <param name="tenantId">租户 ID。</param>
/// <param name="fieldName">字段过滤。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>变更日志列表。</returns>
Task<IReadOnlyList<MerchantChangeLog>> GetChangeLogsAsync(
long merchantId,
long tenantId,
string? fieldName = null,
CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,26 @@
using TakeoutSaaS.Domain.Merchants.Entities;
using TakeoutSaaS.Domain.Stores.Entities;
namespace TakeoutSaaS.Domain.Merchants.Services;
/// <summary>
/// 商户导出服务接口。
/// </summary>
public interface IMerchantExportService
{
/// <summary>
/// 导出为 PDF。
/// </summary>
/// <param name="merchant">商户主体。</param>
/// <param name="tenantName">租户名称。</param>
/// <param name="stores">门店列表。</param>
/// <param name="auditLogs">审核历史。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>PDF 字节数组。</returns>
Task<byte[]> ExportToPdfAsync(
Merchant merchant,
string? tenantName,
IReadOnlyList<Store> stores,
IReadOnlyList<MerchantAuditLog> auditLogs,
CancellationToken cancellationToken = default);
}

View File

@@ -33,6 +33,26 @@ public sealed class Store : MultiTenantEntityBase
/// </summary>
public string? ManagerName { get; set; }
/// <summary>
/// 门店营业执照号(主体不一致模式使用)。
/// </summary>
public string? BusinessLicenseNumber { get; set; }
/// <summary>
/// 门店法人(主体不一致模式使用)。
/// </summary>
public string? LegalRepresentative { get; set; }
/// <summary>
/// 门店注册地址(主体不一致模式使用)。
/// </summary>
public string? RegisteredAddress { get; set; }
/// <summary>
/// 门店营业执照图片地址(主体不一致模式使用)。
/// </summary>
public string? BusinessLicenseImageUrl { get; set; }
/// <summary>
/// 门店当前运营状态。
/// </summary>

View File

@@ -13,11 +13,21 @@ public interface IStoreRepository
/// </summary>
Task<Store?> FindByIdAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 获取指定商户的门店列表。
/// </summary>
Task<IReadOnlyList<Store>> GetByMerchantIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 按租户筛选门店列表。
/// </summary>
Task<IReadOnlyList<Store>> SearchAsync(long tenantId, StoreStatus? status, CancellationToken cancellationToken = default);
/// <summary>
/// 获取指定商户集合的门店数量。
/// </summary>
Task<Dictionary<long, int>> GetStoreCountsAsync(long? tenantId, IReadOnlyCollection<long> merchantIds, CancellationToken cancellationToken = default);
/// <summary>
/// 获取门店营业时段。
/// </summary>

View File

@@ -1,3 +1,4 @@
using TakeoutSaaS.Domain.Common.Enums;
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
@@ -93,6 +94,11 @@ public sealed class Tenant : AuditableEntityBase
/// </summary>
public TenantStatus Status { get; set; } = TenantStatus.PendingReview;
/// <summary>
/// 经营模式(同一主体/不同主体)。
/// </summary>
public OperatingMode? OperatingMode { get; set; }
/// <summary>
/// 服务生效时间UTC
/// </summary>