All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 44s
91 lines
3.3 KiB
C#
91 lines
3.3 KiB
C#
using MediatR;
|
|
using System.Security.Cryptography;
|
|
using TakeoutSaaS.Application.App.Stores.Commands;
|
|
using TakeoutSaaS.Application.App.Stores.Enums;
|
|
using TakeoutSaaS.Application.App.Stores.Services;
|
|
using TakeoutSaaS.Domain.Stores.Entities;
|
|
using TakeoutSaaS.Domain.Stores.Enums;
|
|
using TakeoutSaaS.Domain.Stores.Repositories;
|
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
|
|
|
namespace TakeoutSaaS.Application.App.Stores.Handlers;
|
|
|
|
/// <summary>
|
|
/// 租户端创建门店命令处理器。
|
|
/// </summary>
|
|
public sealed class CreateStoreCommandHandler(
|
|
StoreContextService storeContextService,
|
|
IStoreRepository storeRepository)
|
|
: IRequestHandler<CreateStoreCommand>
|
|
{
|
|
/// <inheritdoc />
|
|
public async Task Handle(CreateStoreCommand request, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 解析上下文
|
|
var context = storeContextService.GetRequiredContext();
|
|
|
|
// 2. 生成唯一门店编码
|
|
var existingStores = await storeRepository.SearchAsync(
|
|
context.TenantId,
|
|
context.MerchantId,
|
|
status: null,
|
|
auditStatus: null,
|
|
businessStatus: null,
|
|
ownershipType: null,
|
|
keyword: null,
|
|
includeDeleted: false,
|
|
cancellationToken: cancellationToken);
|
|
var generatedCode = GenerateUniqueStoreCode(existingStores);
|
|
|
|
// 3. 组装门店实体
|
|
var serviceTypes = request.ServiceTypes?.Count > 0
|
|
? request.ServiceTypes
|
|
: [ServiceType.Delivery];
|
|
var store = new Store
|
|
{
|
|
TenantId = context.TenantId,
|
|
MerchantId = context.MerchantId,
|
|
Code = generatedCode,
|
|
Name = request.Name.Trim(),
|
|
Phone = request.ContactPhone.Trim(),
|
|
ManagerName = request.ManagerName.Trim(),
|
|
Address = request.Address.Trim(),
|
|
CoverImageUrl = request.CoverImage?.Trim(),
|
|
SignboardImageUrl = request.CoverImage?.Trim(),
|
|
OwnershipType = StoreOwnershipType.SameEntity,
|
|
AuditStatus = StoreAuditStatus.Draft,
|
|
BusinessStatus = request.BusinessStatus ?? StoreBusinessStatus.Open,
|
|
Status = StoreStatus.Operating
|
|
};
|
|
StoreListMapping.ApplyServiceTypes(store, serviceTypes);
|
|
|
|
// 4. 持久化
|
|
await storeRepository.AddStoreAsync(store, cancellationToken);
|
|
await storeRepository.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 生成当前租户商户内唯一的门店编码。
|
|
/// </summary>
|
|
private static string GenerateUniqueStoreCode(IReadOnlyList<Store> existingStores)
|
|
{
|
|
var existingCodeSet = existingStores
|
|
.Select(store => store.Code.Trim())
|
|
.Where(code => !string.IsNullOrWhiteSpace(code))
|
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
|
|
const int maxRetryCount = 20;
|
|
for (var attempt = 0; attempt < maxRetryCount; attempt++)
|
|
{
|
|
var candidate = $"ST{DateTime.UtcNow:yyMMddHHmmss}{RandomNumberGenerator.GetInt32(100, 1000)}";
|
|
if (existingCodeSet.Add(candidate))
|
|
{
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
throw new BusinessException(ErrorCodes.Conflict, "门店编码生成失败,请稍后重试");
|
|
}
|
|
}
|