feat: 后台手动新增租户并直接入驻接口
This commit is contained in:
@@ -35,6 +35,22 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController
|
||||
return ApiResponse<TenantDto>.Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 后台手动新增租户并直接入驻(创建租户 + 认证 + 订阅 + 管理员账号)。
|
||||
/// </summary>
|
||||
/// <returns>新增后的租户详情。</returns>
|
||||
[HttpPost("manual")]
|
||||
[PermissionAuthorize("tenant:create")]
|
||||
[ProducesResponseType(typeof(ApiResponse<TenantDetailDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<TenantDetailDto>> CreateManually([FromBody] CreateTenantManuallyCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 后台手动新增租户(直接可用)
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
// 2. 返回创建结果
|
||||
return ApiResponse<TenantDetailDto>.Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询租户。
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
using MediatR;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 后台手动新增租户并直接入驻(创建租户 + 认证 + 订阅 + 管理员账号)。
|
||||
/// </summary>
|
||||
public sealed record CreateTenantManuallyCommand : IRequest<TenantDetailDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 租户短编码,作为跨系统引用的唯一标识。
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(64)]
|
||||
public string Code { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 租户全称或品牌名称。
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(128)]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 对外展示的简称。
|
||||
/// </summary>
|
||||
public string? ShortName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 法人或公司主体名称。
|
||||
/// </summary>
|
||||
public string? LegalEntityName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属行业,如餐饮、零售等。
|
||||
/// </summary>
|
||||
public string? Industry { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// LOGO 图片地址。
|
||||
/// </summary>
|
||||
public string? LogoUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 品牌海报或封面图。
|
||||
/// </summary>
|
||||
public string? CoverImageUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 官网或主要宣传链接。
|
||||
/// </summary>
|
||||
public string? Website { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 所在国家/地区。
|
||||
/// </summary>
|
||||
public string? Country { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 所在省份或州。
|
||||
/// </summary>
|
||||
public string? Province { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 所在城市。
|
||||
/// </summary>
|
||||
public string? City { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 详细地址信息。
|
||||
/// </summary>
|
||||
public string? Address { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 主联系人姓名。
|
||||
/// </summary>
|
||||
public string? ContactName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 主联系人电话(唯一)。
|
||||
/// </summary>
|
||||
public string? ContactPhone { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 主联系人邮箱。
|
||||
/// </summary>
|
||||
public string? ContactEmail { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务标签集合(逗号分隔)。
|
||||
/// </summary>
|
||||
public string? Tags { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注信息,用于运营记录特殊说明。
|
||||
/// </summary>
|
||||
public string? Remarks { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最近一次暂停服务时间。
|
||||
/// </summary>
|
||||
public DateTime? SuspendedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 暂停或终止的原因说明。
|
||||
/// </summary>
|
||||
public string? SuspensionReason { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户当前状态,默认 Active(直接入驻)。
|
||||
/// </summary>
|
||||
public TenantStatus TenantStatus { get; init; } = TenantStatus.Active;
|
||||
|
||||
/// <summary>
|
||||
/// 购买套餐 ID。
|
||||
/// </summary>
|
||||
[Required]
|
||||
public long TenantPackageId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 订阅时长(月)。
|
||||
/// </summary>
|
||||
[Range(1, int.MaxValue)]
|
||||
public int DurationMonths { get; init; } = 12;
|
||||
|
||||
/// <summary>
|
||||
/// 是否自动续费。
|
||||
/// </summary>
|
||||
public bool AutoRenew { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 订阅生效时间(UTC),为空则立即生效。
|
||||
/// </summary>
|
||||
public DateTime? SubscriptionEffectiveFrom { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 下次计费时间(UTC),为空则默认等于到期时间。
|
||||
/// </summary>
|
||||
public DateTime? NextBillingDate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 订阅状态,默认 Active。
|
||||
/// </summary>
|
||||
public SubscriptionStatus SubscriptionStatus { get; init; } = SubscriptionStatus.Active;
|
||||
|
||||
/// <summary>
|
||||
/// 预定下次切换的套餐 ID。
|
||||
/// </summary>
|
||||
public long? ScheduledPackageId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 订阅备注。
|
||||
/// </summary>
|
||||
public string? SubscriptionNotes { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 实名状态,默认 Approved(直接通过)。
|
||||
/// </summary>
|
||||
public TenantVerificationStatus VerificationStatus { get; init; } = TenantVerificationStatus.Approved;
|
||||
|
||||
/// <summary>
|
||||
/// 营业执照编号。
|
||||
/// </summary>
|
||||
public string? BusinessLicenseNumber { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 营业执照扫描件地址。
|
||||
/// </summary>
|
||||
public string? BusinessLicenseUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 法人姓名。
|
||||
/// </summary>
|
||||
public string? LegalPersonName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 法人身份证号。
|
||||
/// </summary>
|
||||
public string? LegalPersonIdNumber { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 法人身份证人像面图片地址。
|
||||
/// </summary>
|
||||
public string? LegalPersonIdFrontUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 法人身份证国徽面图片地址。
|
||||
/// </summary>
|
||||
public string? LegalPersonIdBackUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 对公账户户名。
|
||||
/// </summary>
|
||||
public string? BankAccountName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 对公银行账号。
|
||||
/// </summary>
|
||||
public string? BankAccountNumber { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 开户行名称。
|
||||
/// </summary>
|
||||
public string? BankName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 其他补充资料 JSON。
|
||||
/// </summary>
|
||||
public string? AdditionalDataJson { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 提交时间(UTC),为空则默认当前时间。
|
||||
/// </summary>
|
||||
public DateTime? SubmittedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 审核时间(UTC),为空则默认当前时间。
|
||||
/// </summary>
|
||||
public DateTime? ReviewedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 审核人姓名(展示用),为空则默认当前用户。
|
||||
/// </summary>
|
||||
public string? ReviewedByName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 审核备注。
|
||||
/// </summary>
|
||||
public string? ReviewRemarks { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户管理员账号。
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(128)]
|
||||
public string AdminAccount { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 租户管理员显示名。
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(128)]
|
||||
public string AdminDisplayName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 管理员初始密码(明文,仅用于创建时生成哈希,不会被持久化回传)。
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(128, MinimumLength = 6)]
|
||||
public string AdminPassword { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 管理员头像。
|
||||
/// </summary>
|
||||
public string? AdminAvatar { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联商户 ID(若有)。
|
||||
/// </summary>
|
||||
public long? AdminMerchantId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TakeoutSaaS.Application.App.Tenants.Commands;
|
||||
using TakeoutSaaS.Application.App.Tenants.Dto;
|
||||
using TakeoutSaaS.Application.Identity.Commands;
|
||||
using TakeoutSaaS.Domain.Identity.Entities;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 后台手动新增租户处理器。
|
||||
/// </summary>
|
||||
public sealed class CreateTenantManuallyCommandHandler(
|
||||
ITenantRepository tenantRepository,
|
||||
ITenantPackageRepository tenantPackageRepository,
|
||||
IIdentityUserRepository identityUserRepository,
|
||||
IRoleRepository roleRepository,
|
||||
IPasswordHasher<IdentityUser> passwordHasher,
|
||||
IIdGenerator idGenerator,
|
||||
IMediator mediator,
|
||||
ITenantContextAccessor tenantContextAccessor,
|
||||
ICurrentUserAccessor currentUserAccessor,
|
||||
ILogger<CreateTenantManuallyCommandHandler> logger)
|
||||
: IRequestHandler<CreateTenantManuallyCommand, TenantDetailDto>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<TenantDetailDto> Handle(CreateTenantManuallyCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 校验订阅时长
|
||||
if (request.DurationMonths <= 0)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.ValidationFailed, "订阅时长必须大于 0");
|
||||
}
|
||||
|
||||
// 2. 校验租户编码唯一性
|
||||
var normalizedCode = request.Code.Trim();
|
||||
if (await tenantRepository.ExistsByCodeAsync(normalizedCode, cancellationToken))
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Conflict, $"租户编码 {normalizedCode} 已存在");
|
||||
}
|
||||
|
||||
// 3. (空行后) 校验联系人手机号唯一性(仅当填写时)
|
||||
if (!string.IsNullOrWhiteSpace(request.ContactPhone))
|
||||
{
|
||||
var normalizedPhone = request.ContactPhone.Trim();
|
||||
if (await tenantRepository.ExistsByContactPhoneAsync(normalizedPhone, cancellationToken))
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Conflict, $"手机号 {normalizedPhone} 已注册");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. (空行后) 校验管理员账号唯一性
|
||||
var normalizedAccount = request.AdminAccount.Trim();
|
||||
if (await identityUserRepository.ExistsByAccountAsync(normalizedAccount, cancellationToken))
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Conflict, $"账号 {normalizedAccount} 已存在");
|
||||
}
|
||||
|
||||
// 5. (空行后) 校验套餐存在且可用
|
||||
var package = await tenantPackageRepository.FindByIdAsync(request.TenantPackageId, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "套餐不存在");
|
||||
if (!package.IsActive)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "套餐未启用,无法绑定订阅");
|
||||
}
|
||||
|
||||
// 6. (空行后) 计算订阅生效与到期时间(UTC)
|
||||
var now = DateTime.UtcNow;
|
||||
var subscriptionEffectiveFrom = request.SubscriptionEffectiveFrom ?? now;
|
||||
var subscriptionEffectiveTo = subscriptionEffectiveFrom.AddMonths(request.DurationMonths);
|
||||
|
||||
// 7. (空行后) 构建租户与订阅
|
||||
var tenantId = idGenerator.NextId();
|
||||
var tenant = new Tenant
|
||||
{
|
||||
Id = tenantId,
|
||||
Code = normalizedCode,
|
||||
Name = request.Name.Trim(),
|
||||
ShortName = request.ShortName,
|
||||
LegalEntityName = request.LegalEntityName,
|
||||
Industry = request.Industry,
|
||||
LogoUrl = request.LogoUrl,
|
||||
CoverImageUrl = request.CoverImageUrl,
|
||||
Website = request.Website,
|
||||
Country = request.Country,
|
||||
Province = request.Province,
|
||||
City = request.City,
|
||||
Address = request.Address,
|
||||
ContactName = request.ContactName,
|
||||
ContactPhone = string.IsNullOrWhiteSpace(request.ContactPhone) ? null : request.ContactPhone.Trim(),
|
||||
ContactEmail = request.ContactEmail,
|
||||
Status = request.TenantStatus,
|
||||
EffectiveFrom = subscriptionEffectiveFrom,
|
||||
EffectiveTo = subscriptionEffectiveTo,
|
||||
SuspendedAt = request.SuspendedAt,
|
||||
SuspensionReason = request.SuspensionReason,
|
||||
Tags = request.Tags,
|
||||
Remarks = request.Remarks
|
||||
};
|
||||
|
||||
// 8. (空行后) 构建订阅实体
|
||||
var subscription = new TenantSubscription
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
TenantId = tenantId,
|
||||
TenantPackageId = request.TenantPackageId,
|
||||
EffectiveFrom = subscriptionEffectiveFrom,
|
||||
EffectiveTo = subscriptionEffectiveTo,
|
||||
NextBillingDate = request.NextBillingDate ?? subscriptionEffectiveTo,
|
||||
Status = request.SubscriptionStatus,
|
||||
AutoRenew = request.AutoRenew,
|
||||
ScheduledPackageId = request.ScheduledPackageId,
|
||||
Notes = request.SubscriptionNotes
|
||||
};
|
||||
|
||||
// 9. (空行后) 构建认证资料(默认直接通过)
|
||||
var actorName = currentUserAccessor.IsAuthenticated
|
||||
? $"user:{currentUserAccessor.UserId}"
|
||||
: "system";
|
||||
|
||||
var verification = new TenantVerificationProfile
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
TenantId = tenantId,
|
||||
Status = request.VerificationStatus,
|
||||
BusinessLicenseNumber = request.BusinessLicenseNumber,
|
||||
BusinessLicenseUrl = request.BusinessLicenseUrl,
|
||||
LegalPersonName = request.LegalPersonName,
|
||||
LegalPersonIdNumber = request.LegalPersonIdNumber,
|
||||
LegalPersonIdFrontUrl = request.LegalPersonIdFrontUrl,
|
||||
LegalPersonIdBackUrl = request.LegalPersonIdBackUrl,
|
||||
BankAccountName = request.BankAccountName,
|
||||
BankAccountNumber = request.BankAccountNumber,
|
||||
BankName = request.BankName,
|
||||
AdditionalDataJson = request.AdditionalDataJson,
|
||||
SubmittedAt = request.SubmittedAt ?? now,
|
||||
ReviewedAt = request.ReviewedAt ?? now,
|
||||
ReviewedBy = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId,
|
||||
ReviewedByName = string.IsNullOrWhiteSpace(request.ReviewedByName) ? actorName : request.ReviewedByName,
|
||||
ReviewRemarks = request.ReviewRemarks
|
||||
};
|
||||
|
||||
// 10. (空行后) 写入审计日志与订阅历史
|
||||
await tenantRepository.AddAuditLogAsync(new TenantAuditLog
|
||||
{
|
||||
TenantId = tenantId,
|
||||
Action = TenantAuditAction.RegistrationSubmitted,
|
||||
Title = "后台手动创建",
|
||||
Description = $"绑定套餐 {request.TenantPackageId},订阅 {request.DurationMonths} 月",
|
||||
OperatorId = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId,
|
||||
OperatorName = actorName,
|
||||
PreviousStatus = null,
|
||||
CurrentStatus = tenant.Status
|
||||
}, cancellationToken);
|
||||
|
||||
await tenantRepository.AddAuditLogAsync(new TenantAuditLog
|
||||
{
|
||||
TenantId = tenantId,
|
||||
Action = TenantAuditAction.SubscriptionUpdated,
|
||||
Title = "订阅初始化",
|
||||
Description = $"生效:{subscription.EffectiveFrom:yyyy-MM-dd HH:mm:ss},到期:{subscription.EffectiveTo:yyyy-MM-dd HH:mm:ss}",
|
||||
OperatorId = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId,
|
||||
OperatorName = actorName,
|
||||
PreviousStatus = null,
|
||||
CurrentStatus = tenant.Status
|
||||
}, cancellationToken);
|
||||
|
||||
await tenantRepository.AddAuditLogAsync(new TenantAuditLog
|
||||
{
|
||||
TenantId = tenantId,
|
||||
Action = TenantAuditAction.VerificationApproved,
|
||||
Title = "认证已通过",
|
||||
Description = request.ReviewRemarks,
|
||||
OperatorId = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId,
|
||||
OperatorName = actorName,
|
||||
PreviousStatus = null,
|
||||
CurrentStatus = tenant.Status
|
||||
}, cancellationToken);
|
||||
|
||||
await tenantRepository.AddSubscriptionHistoryAsync(new TenantSubscriptionHistory
|
||||
{
|
||||
TenantId = tenantId,
|
||||
TenantSubscriptionId = subscription.Id,
|
||||
FromPackageId = request.TenantPackageId,
|
||||
ToPackageId = request.TenantPackageId,
|
||||
ChangeType = SubscriptionChangeType.New,
|
||||
EffectiveFrom = subscription.EffectiveFrom,
|
||||
EffectiveTo = subscription.EffectiveTo,
|
||||
Amount = null,
|
||||
Currency = null,
|
||||
Notes = request.SubscriptionNotes
|
||||
}, cancellationToken);
|
||||
|
||||
// 11. (空行后) 持久化租户、订阅与认证资料
|
||||
await tenantRepository.AddTenantAsync(tenant, cancellationToken);
|
||||
await tenantRepository.AddSubscriptionAsync(subscription, cancellationToken);
|
||||
await tenantRepository.UpsertVerificationProfileAsync(verification, cancellationToken);
|
||||
await tenantRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 12. (空行后) 临时切换租户上下文,保证身份与权限写入正确
|
||||
var previousContext = tenantContextAccessor.Current;
|
||||
tenantContextAccessor.Current = new TenantContext(tenant.Id, tenant.Code, "manual-create");
|
||||
try
|
||||
{
|
||||
// 13. 创建租户管理员账号
|
||||
var adminUser = new IdentityUser
|
||||
{
|
||||
TenantId = tenant.Id,
|
||||
Account = normalizedAccount,
|
||||
DisplayName = request.AdminDisplayName.Trim(),
|
||||
PasswordHash = string.Empty,
|
||||
MerchantId = request.AdminMerchantId,
|
||||
Avatar = request.AdminAvatar
|
||||
};
|
||||
adminUser.PasswordHash = passwordHasher.HashPassword(adminUser, request.AdminPassword);
|
||||
await identityUserRepository.AddAsync(adminUser, cancellationToken);
|
||||
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 14. (空行后) 初始化租户管理员角色模板并绑定角色
|
||||
await mediator.Send(new InitializeRoleTemplatesCommand
|
||||
{
|
||||
TemplateCodes = new[] { "tenant-admin" }
|
||||
}, cancellationToken);
|
||||
|
||||
var tenantAdminRole = await roleRepository.FindByCodeAsync("tenant-admin", tenant.Id, cancellationToken);
|
||||
if (tenantAdminRole != null)
|
||||
{
|
||||
await mediator.Send(new AssignUserRolesCommand
|
||||
{
|
||||
UserId = adminUser.Id,
|
||||
RoleIds = new[] { tenantAdminRole.Id }
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
// 15. (空行后) 回写租户所有者账号
|
||||
tenant.PrimaryOwnerUserId = adminUser.Id;
|
||||
await tenantRepository.UpdateTenantAsync(tenant, cancellationToken);
|
||||
await tenantRepository.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 16. 恢复上下文
|
||||
tenantContextAccessor.Current = previousContext;
|
||||
}
|
||||
|
||||
// 17. (空行后) 返回创建结果
|
||||
logger.LogInformation("已后台手动创建租户 {TenantCode}", tenant.Code);
|
||||
|
||||
return new TenantDetailDto
|
||||
{
|
||||
Tenant = TenantMapping.ToDto(tenant, subscription, verification),
|
||||
Verification = verification.ToVerificationDto(),
|
||||
Subscription = subscription.ToSubscriptionDto(),
|
||||
Package = package.ToDto()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user