完成门店管理后端接口与任务

This commit is contained in:
2026-01-01 07:26:14 +08:00
parent dc9f6136d6
commit fc55003d3d
131 changed files with 15333 additions and 201 deletions

View File

@@ -53,6 +53,66 @@ public sealed class Store : MultiTenantEntityBase
/// </summary>
public string? BusinessLicenseImageUrl { get; set; }
/// <summary>
/// 门头招牌图 URL。
/// </summary>
public string? SignboardImageUrl { get; set; }
/// <summary>
/// 主体类型。
/// </summary>
public StoreOwnershipType OwnershipType { get; set; } = StoreOwnershipType.SameEntity;
/// <summary>
/// 审核状态。
/// </summary>
public StoreAuditStatus AuditStatus { get; set; } = StoreAuditStatus.Draft;
/// <summary>
/// 经营状态。
/// </summary>
public StoreBusinessStatus BusinessStatus { get; set; } = StoreBusinessStatus.Resting;
/// <summary>
/// 歇业原因。
/// </summary>
public StoreClosureReason? ClosureReason { get; set; }
/// <summary>
/// 歇业原因补充说明。
/// </summary>
public string? ClosureReasonText { get; set; }
/// <summary>
/// 行业类目 ID。
/// </summary>
public long? CategoryId { get; set; }
/// <summary>
/// 审核驳回原因。
/// </summary>
public string? RejectionReason { get; set; }
/// <summary>
/// 提交审核时间。
/// </summary>
public DateTime? SubmittedAt { get; set; }
/// <summary>
/// 审核通过时间。
/// </summary>
public DateTime? ActivatedAt { get; set; }
/// <summary>
/// 强制关闭时间。
/// </summary>
public DateTime? ForceClosedAt { get; set; }
/// <summary>
/// 强制关闭原因。
/// </summary>
public string? ForceCloseReason { get; set; }
/// <summary>
/// 门店当前运营状态。
/// </summary>

View File

@@ -0,0 +1,55 @@
using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Stores.Entities;
/// <summary>
/// 门店审核记录。
/// </summary>
public sealed class StoreAuditRecord : MultiTenantEntityBase
{
/// <summary>
/// 门店标识。
/// </summary>
public long StoreId { get; set; }
/// <summary>
/// 操作类型。
/// </summary>
public StoreAuditAction Action { get; set; }
/// <summary>
/// 操作前状态。
/// </summary>
public StoreAuditStatus? PreviousStatus { get; set; }
/// <summary>
/// 操作后状态。
/// </summary>
public StoreAuditStatus NewStatus { get; set; }
/// <summary>
/// 操作人 ID。
/// </summary>
public long? OperatorId { get; set; }
/// <summary>
/// 操作人名称。
/// </summary>
public string OperatorName { get; set; } = string.Empty;
/// <summary>
/// 驳回理由 ID。
/// </summary>
public long? RejectionReasonId { get; set; }
/// <summary>
/// 驳回理由文本。
/// </summary>
public string? RejectionReason { get; set; }
/// <summary>
/// 备注。
/// </summary>
public string? Remarks { get; set; }
}

View File

@@ -0,0 +1,40 @@
using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Stores.Entities;
/// <summary>
/// 门店费用配置。
/// </summary>
public sealed class StoreFee : MultiTenantEntityBase
{
/// <summary>
/// 门店标识。
/// </summary>
public long StoreId { get; set; }
/// <summary>
/// 起送费(元)。
/// </summary>
public decimal MinimumOrderAmount { get; set; } = 0m;
/// <summary>
/// 基础配送费(元)。
/// </summary>
public decimal BaseDeliveryFee { get; set; } = 0m;
/// <summary>
/// 打包费模式。
/// </summary>
public PackagingFeeMode PackagingFeeMode { get; set; } = PackagingFeeMode.Fixed;
/// <summary>
/// 固定打包费(总计模式有效)。
/// </summary>
public decimal FixedPackagingFee { get; set; } = 0m;
/// <summary>
/// 免配送费门槛。
/// </summary>
public decimal? FreeDeliveryThreshold { get; set; }
}

View File

@@ -0,0 +1,57 @@
using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Stores.Entities;
/// <summary>
/// 门店资质证照。
/// </summary>
public sealed class StoreQualification : MultiTenantEntityBase
{
/// <summary>
/// 门店标识。
/// </summary>
public long StoreId { get; set; }
/// <summary>
/// 资质类型。
/// </summary>
public StoreQualificationType QualificationType { get; set; }
/// <summary>
/// 证照文件 URL。
/// </summary>
public string FileUrl { get; set; } = string.Empty;
/// <summary>
/// 证照编号。
/// </summary>
public string? DocumentNumber { get; set; }
/// <summary>
/// 签发日期。
/// </summary>
public DateTime? IssuedAt { get; set; }
/// <summary>
/// 到期日期。
/// </summary>
public DateTime? ExpiresAt { get; set; }
/// <summary>
/// 是否已过期。
/// </summary>
public bool IsExpired => ExpiresAt.HasValue && ExpiresAt.Value < DateTime.UtcNow;
/// <summary>
/// 是否即将过期30天内
/// </summary>
public bool IsExpiringSoon => ExpiresAt.HasValue
&& ExpiresAt.Value >= DateTime.UtcNow
&& ExpiresAt.Value <= DateTime.UtcNow.AddDays(30);
/// <summary>
/// 排序值。
/// </summary>
public int SortOrder { get; set; } = 100;
}

View File

@@ -0,0 +1,17 @@
namespace TakeoutSaaS.Domain.Stores.Enums;
/// <summary>
/// 打包费计算模式。
/// </summary>
public enum PackagingFeeMode
{
/// <summary>
/// 总计模式:固定单笔订单打包费。
/// </summary>
Fixed = 0,
/// <summary>
/// 商品计费模式:按商品累计打包费。
/// </summary>
PerItem = 1
}

View File

@@ -0,0 +1,42 @@
namespace TakeoutSaaS.Domain.Stores.Enums;
/// <summary>
/// 门店审核操作类型。
/// </summary>
public enum StoreAuditAction
{
/// <summary>
/// 提交审核。
/// </summary>
Submit = 0,
/// <summary>
/// 重新提交。
/// </summary>
Resubmit = 1,
/// <summary>
/// 审核通过。
/// </summary>
Approve = 2,
/// <summary>
/// 审核驳回。
/// </summary>
Reject = 3,
/// <summary>
/// 强制关闭。
/// </summary>
ForceClose = 4,
/// <summary>
/// 解除关闭。
/// </summary>
Reopen = 5,
/// <summary>
/// 自动激活(同主体门店)。
/// </summary>
AutoActivate = 6
}

View File

@@ -0,0 +1,27 @@
namespace TakeoutSaaS.Domain.Stores.Enums;
/// <summary>
/// 门店审核状态。
/// </summary>
public enum StoreAuditStatus
{
/// <summary>
/// 草稿,未提交审核。
/// </summary>
Draft = 0,
/// <summary>
/// 审核中。
/// </summary>
Pending = 1,
/// <summary>
/// 已激活(审核通过或自动通过)。
/// </summary>
Activated = 2,
/// <summary>
/// 已驳回。
/// </summary>
Rejected = 3
}

View File

@@ -0,0 +1,22 @@
namespace TakeoutSaaS.Domain.Stores.Enums;
/// <summary>
/// 门店经营状态。
/// </summary>
public enum StoreBusinessStatus
{
/// <summary>
/// 营业中。
/// </summary>
Open = 0,
/// <summary>
/// 休息中(手动或自动)。
/// </summary>
Resting = 1,
/// <summary>
/// 强制关闭(平台风控)。
/// </summary>
ForceClosed = 2
}

View File

@@ -0,0 +1,47 @@
namespace TakeoutSaaS.Domain.Stores.Enums;
/// <summary>
/// 门店歇业原因(关联字典表 store_closure_reason
/// </summary>
public enum StoreClosureReason
{
/// <summary>
/// 非营业时间(系统自动)。
/// </summary>
OutOfBusinessHours = 0,
/// <summary>
/// 设备检修。
/// </summary>
EquipmentMaintenance = 1,
/// <summary>
/// 老板休假。
/// </summary>
OwnerVacation = 2,
/// <summary>
/// 食材告罄。
/// </summary>
OutOfStock = 3,
/// <summary>
/// 暂停营业。
/// </summary>
TemporarilyClosed = 4,
/// <summary>
/// 证照过期。
/// </summary>
LicenseExpired = 5,
/// <summary>
/// 平台封禁。
/// </summary>
PlatformSuspended = 6,
/// <summary>
/// 其他原因。
/// </summary>
Other = 99
}

View File

@@ -0,0 +1,17 @@
namespace TakeoutSaaS.Domain.Stores.Enums;
/// <summary>
/// 门店主体类型。
/// </summary>
public enum StoreOwnershipType
{
/// <summary>
/// 同一主体(自营)。
/// </summary>
SameEntity = 0,
/// <summary>
/// 不同主体(外部入驻)。
/// </summary>
DifferentEntity = 1
}

View File

@@ -0,0 +1,27 @@
namespace TakeoutSaaS.Domain.Stores.Enums;
/// <summary>
/// 门店资质类型。
/// </summary>
public enum StoreQualificationType
{
/// <summary>
/// 营业执照。
/// </summary>
BusinessLicense = 0,
/// <summary>
/// 食品经营许可证。
/// </summary>
FoodServiceLicense = 1,
/// <summary>
/// 门头实景照。
/// </summary>
StorefrontPhoto = 2,
/// <summary>
/// 店内环境照。
/// </summary>
InteriorPhoto = 3
}

View File

@@ -0,0 +1,22 @@
namespace TakeoutSaaS.Domain.Stores.Events;
/// <summary>
/// 门店审核通过事件。
/// </summary>
public sealed class StoreApprovedEvent
{
/// <summary>
/// 门店 ID。
/// </summary>
public long StoreId { get; init; }
/// <summary>
/// 租户 ID。
/// </summary>
public long TenantId { get; init; }
/// <summary>
/// 审核通过时间UTC
/// </summary>
public DateTime ApprovedAt { get; init; }
}

View File

@@ -0,0 +1,27 @@
namespace TakeoutSaaS.Domain.Stores.Events;
/// <summary>
/// 门店强制关闭事件。
/// </summary>
public sealed class StoreForceClosedEvent
{
/// <summary>
/// 门店 ID。
/// </summary>
public long StoreId { get; init; }
/// <summary>
/// 租户 ID。
/// </summary>
public long TenantId { get; init; }
/// <summary>
/// 强制关闭原因。
/// </summary>
public string? Reason { get; init; }
/// <summary>
/// 强制关闭时间UTC
/// </summary>
public DateTime ForceClosedAt { get; init; }
}

View File

@@ -0,0 +1,32 @@
namespace TakeoutSaaS.Domain.Stores.Events;
/// <summary>
/// 门店审核驳回事件。
/// </summary>
public sealed class StoreRejectedEvent
{
/// <summary>
/// 门店 ID。
/// </summary>
public long StoreId { get; init; }
/// <summary>
/// 租户 ID。
/// </summary>
public long TenantId { get; init; }
/// <summary>
/// 驳回理由 ID。
/// </summary>
public long? RejectionReasonId { get; init; }
/// <summary>
/// 驳回理由文本。
/// </summary>
public string? RejectionReason { get; init; }
/// <summary>
/// 驳回时间UTC
/// </summary>
public DateTime RejectedAt { get; init; }
}

View File

@@ -0,0 +1,22 @@
namespace TakeoutSaaS.Domain.Stores.Events;
/// <summary>
/// 门店提交审核事件。
/// </summary>
public sealed class StoreSubmittedEvent
{
/// <summary>
/// 门店 ID。
/// </summary>
public long StoreId { get; init; }
/// <summary>
/// 租户 ID。
/// </summary>
public long TenantId { get; init; }
/// <summary>
/// 提交时间UTC
/// </summary>
public DateTime SubmittedAt { get; init; }
}

View File

@@ -21,7 +21,26 @@ public interface IStoreRepository
/// <summary>
/// 按租户筛选门店列表。
/// </summary>
Task<IReadOnlyList<Store>> SearchAsync(long tenantId, StoreStatus? status, CancellationToken cancellationToken = default);
Task<IReadOnlyList<Store>> SearchAsync(
long tenantId,
long? merchantId,
StoreStatus? status,
StoreAuditStatus? auditStatus,
StoreBusinessStatus? businessStatus,
StoreOwnershipType? ownershipType,
string? keyword,
CancellationToken cancellationToken = default);
/// <summary>
/// 判断指定坐标是否存在 100 米内门店。
/// </summary>
Task<bool> ExistsStoreWithinDistanceAsync(
long merchantId,
long tenantId,
double longitude,
double latitude,
double distanceMeters,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取指定商户集合的门店数量。
@@ -33,6 +52,56 @@ public interface IStoreRepository
/// </summary>
Task<IReadOnlyList<StoreBusinessHour>> GetBusinessHoursAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 获取门店费用配置。
/// </summary>
Task<StoreFee?> GetStoreFeeAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 新增门店费用配置。
/// </summary>
Task AddStoreFeeAsync(StoreFee storeFee, CancellationToken cancellationToken = default);
/// <summary>
/// 更新门店费用配置。
/// </summary>
Task UpdateStoreFeeAsync(StoreFee storeFee, CancellationToken cancellationToken = default);
/// <summary>
/// 获取门店资质列表。
/// </summary>
Task<IReadOnlyList<StoreQualification>> GetQualificationsAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 依据标识获取门店资质。
/// </summary>
Task<StoreQualification?> FindQualificationByIdAsync(long qualificationId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 新增门店资质。
/// </summary>
Task AddQualificationAsync(StoreQualification qualification, CancellationToken cancellationToken = default);
/// <summary>
/// 更新门店资质。
/// </summary>
Task UpdateQualificationAsync(StoreQualification qualification, CancellationToken cancellationToken = default);
/// <summary>
/// 删除门店资质。
/// </summary>
Task DeleteQualificationAsync(long qualificationId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 新增门店审核记录。
/// </summary>
Task AddAuditRecordAsync(StoreAuditRecord record, CancellationToken cancellationToken = default);
/// <summary>
/// 获取门店审核记录。
/// </summary>
Task<IReadOnlyList<StoreAuditRecord>> GetAuditRecordsAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 依据标识获取营业时段。
/// </summary>