feat: tenant门店管理首批接口落地
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 30s

This commit is contained in:
2026-02-17 11:10:06 +08:00
parent 992930a821
commit 654b1ae3f7
31 changed files with 2731 additions and 14 deletions

View File

@@ -0,0 +1,71 @@
using MediatR;
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 normalizedCode = request.Code.Trim();
var hasDuplicateCode = existingStores.Any(store => string.Equals(store.Code, normalizedCode, StringComparison.OrdinalIgnoreCase));
if (hasDuplicateCode)
{
throw new BusinessException(ErrorCodes.Conflict, "门店编码已存在");
}
// 3. 组装门店实体
var serviceTypes = request.ServiceTypes?.Count > 0
? request.ServiceTypes
: [ServiceType.Delivery];
var store = new Store
{
TenantId = context.TenantId,
MerchantId = context.MerchantId,
Code = normalizedCode,
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);
}
}