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

@@ -1,6 +1,9 @@
using MediatR;
using TakeoutSaaS.Application.App.Tenants.Commands;
using TakeoutSaaS.Application.App.Tenants.Dto;
using TakeoutSaaS.Domain.Merchants.Entities;
using TakeoutSaaS.Domain.Merchants.Enums;
using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Domain.Tenants.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
@@ -14,6 +17,7 @@ namespace TakeoutSaaS.Application.App.Tenants.Handlers;
/// </summary>
public sealed class ReviewTenantCommandHandler(
ITenantRepository tenantRepository,
IMerchantRepository merchantRepository,
ICurrentUserAccessor currentUserAccessor)
: IRequestHandler<ReviewTenantCommand, TenantDto>
{
@@ -53,19 +57,25 @@ public sealed class ReviewTenantCommandHandler(
// 4. 更新租户与订阅状态
if (request.Approve)
{
if (!request.OperatingMode.HasValue)
{
throw new BusinessException(ErrorCodes.ValidationFailed, "审核通过时必须选择经营模式");
}
var renewMonths = request.RenewMonths ?? 0;
if (renewMonths <= 0)
{
throw new BusinessException(ErrorCodes.ValidationFailed, "续费时长必须为正整数(月)");
}
var now = DateTime.UtcNow;
verification.Status = TenantVerificationStatus.Approved;
tenant.Status = TenantStatus.Active;
tenant.OperatingMode = request.OperatingMode;
if (subscription != null)
{
subscription.Status = SubscriptionStatus.Active;
var now = DateTime.UtcNow;
if (subscription.EffectiveFrom == default || subscription.EffectiveFrom > now)
{
subscription.EffectiveFrom = now;
@@ -92,6 +102,69 @@ public sealed class ReviewTenantCommandHandler(
{
throw new BusinessException(ErrorCodes.BadRequest, "订阅不存在,无法续费");
}
var existingMerchant = await merchantRepository.FindByTenantIdAsync(tenant.Id, cancellationToken);
if (existingMerchant == null)
{
var merchant = new Merchant
{
TenantId = tenant.Id,
BrandName = tenant.Name,
BrandAlias = tenant.ShortName,
Category = tenant.Industry,
ContactPhone = tenant.ContactPhone ?? string.Empty,
ContactEmail = tenant.ContactEmail,
BusinessLicenseNumber = verification.BusinessLicenseNumber,
BusinessLicenseImageUrl = verification.BusinessLicenseUrl,
LegalPerson = verification.LegalPersonName,
Province = tenant.Province,
City = tenant.City,
Address = tenant.Address,
Status = MerchantStatus.Approved,
OperatingMode = request.OperatingMode,
ApprovedAt = now,
ApprovedBy = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId,
JoinedAt = now,
LastReviewedAt = now,
LastReviewedBy = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId,
IsFrozen = false
};
await merchantRepository.AddMerchantAsync(merchant, cancellationToken);
await merchantRepository.AddAuditLogAsync(new MerchantAuditLog
{
TenantId = tenant.Id,
MerchantId = merchant.Id,
Action = MerchantAuditAction.ReviewApproved,
Title = "商户审核通过",
Description = request.Reason,
OperatorId = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId,
OperatorName = actorName
}, cancellationToken);
}
else
{
existingMerchant.Status = MerchantStatus.Approved;
existingMerchant.OperatingMode = request.OperatingMode;
existingMerchant.ApprovedAt = now;
existingMerchant.ApprovedBy = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId;
existingMerchant.LastReviewedAt = now;
existingMerchant.LastReviewedBy = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId;
existingMerchant.IsFrozen = false;
existingMerchant.FrozenReason = null;
existingMerchant.FrozenAt = null;
await merchantRepository.UpdateMerchantAsync(existingMerchant, cancellationToken);
await merchantRepository.AddAuditLogAsync(new MerchantAuditLog
{
TenantId = tenant.Id,
MerchantId = existingMerchant.Id,
Action = MerchantAuditAction.ReviewApproved,
Title = "商户审核通过",
Description = request.Reason,
OperatorId = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId,
OperatorName = actorName
}, cancellationToken);
}
}
else
{
@@ -141,6 +214,7 @@ public sealed class ReviewTenantCommandHandler(
// 8. 保存并返回 DTO
await tenantRepository.SaveChangesAsync(cancellationToken);
await merchantRepository.SaveChangesAsync(cancellationToken);
return TenantMapping.ToDto(tenant, subscription, verification);
}