feat(admin): 新增管理员角色、账单、订阅、套餐管理功能
- 新增 AdminRolesController 实现角色 CRUD 和权限管理 - 新增 BillingsController 实现账单查询功能 - 新增 SubscriptionsController 实现订阅管理功能 - 新增 TenantPackagesController 实现套餐管理功能 - 新增租户详情、配额使用、账单列表等查询功能 - 新增 TenantPackage、TenantSubscription 等领域实体 - 新增相关枚举:SubscriptionStatus、TenantPackageType 等 - 更新 appsettings 配置文件 - 更新权限授权策略提供者 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Commands;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Contracts;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 变更套餐命令处理器。
|
||||
/// </summary>
|
||||
public sealed class ChangePlanCommandHandler(ISubscriptionRepository subscriptionRepository)
|
||||
: IRequestHandler<ChangePlanCommand, SubscriptionListDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<SubscriptionListDto?> Handle(
|
||||
ChangePlanCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取订阅(带跟踪)
|
||||
var subscription = await subscriptionRepository.GetByIdForUpdateAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
// 2. 如果不存在,返回 null
|
||||
if (subscription is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 根据是否立即生效处理套餐变更
|
||||
if (request.Immediate)
|
||||
{
|
||||
// 3.1 立即生效:直接更新当前套餐
|
||||
subscription.TenantPackageId = request.TargetPackageId;
|
||||
|
||||
// 3.2 清除排期套餐(如果有)
|
||||
subscription.ScheduledPackageId = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3.3 下周期生效:设置排期套餐
|
||||
subscription.ScheduledPackageId = request.TargetPackageId;
|
||||
}
|
||||
|
||||
// 4. 更新备注(如果提供)
|
||||
if (!string.IsNullOrWhiteSpace(request.Notes))
|
||||
{
|
||||
var existingNotes = subscription.Notes ?? string.Empty;
|
||||
var changeNote = request.Immediate
|
||||
? $"[立即变更套餐] {request.Notes}"
|
||||
: $"[排期变更套餐] {request.Notes}";
|
||||
subscription.Notes = string.IsNullOrWhiteSpace(existingNotes)
|
||||
? changeNote
|
||||
: $"{existingNotes}\n{changeNote}";
|
||||
}
|
||||
|
||||
// 5. 更新时间戳
|
||||
subscription.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// 6. 保存变更
|
||||
await subscriptionRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 7. 获取更新后的订阅信息
|
||||
var result = await subscriptionRepository.GetListResultByIdAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
// 8. 如果不存在,返回 null
|
||||
if (result is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 9. 映射为 DTO 并返回
|
||||
return new SubscriptionListDto
|
||||
{
|
||||
Id = result.Id,
|
||||
TenantId = result.TenantId,
|
||||
TenantName = result.TenantName,
|
||||
TenantCode = result.TenantCode,
|
||||
TenantPackageId = result.TenantPackageId,
|
||||
PackageName = result.PackageName,
|
||||
ScheduledPackageId = result.ScheduledPackageId,
|
||||
ScheduledPackageName = result.ScheduledPackageName,
|
||||
Status = result.Status,
|
||||
EffectiveFrom = result.EffectiveFrom,
|
||||
EffectiveTo = result.EffectiveTo,
|
||||
NextBillingDate = result.NextBillingDate,
|
||||
AutoRenew = result.AutoRenew,
|
||||
Notes = result.Notes,
|
||||
CreatedAt = result.CreatedAt,
|
||||
UpdatedAt = result.UpdatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Commands;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Contracts;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 延期订阅命令处理器。
|
||||
/// </summary>
|
||||
public sealed class ExtendSubscriptionCommandHandler(ISubscriptionRepository subscriptionRepository)
|
||||
: IRequestHandler<ExtendSubscriptionCommand, SubscriptionListDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<SubscriptionListDto?> Handle(
|
||||
ExtendSubscriptionCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取订阅(带跟踪)
|
||||
var subscription = await subscriptionRepository.GetByIdForUpdateAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
// 2. 如果不存在,返回 null
|
||||
if (subscription is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 延期到期时间
|
||||
subscription.EffectiveTo = subscription.EffectiveTo.AddMonths(request.DurationMonths);
|
||||
|
||||
// 4. 更新下次计费时间(如果有)
|
||||
if (subscription.NextBillingDate.HasValue)
|
||||
{
|
||||
subscription.NextBillingDate = subscription.NextBillingDate.Value.AddMonths(request.DurationMonths);
|
||||
}
|
||||
|
||||
// 5. 更新备注(如果提供)
|
||||
if (!string.IsNullOrWhiteSpace(request.Notes))
|
||||
{
|
||||
var existingNotes = subscription.Notes ?? string.Empty;
|
||||
var extendNote = $"[延期 {request.DurationMonths} 个月] {request.Notes}";
|
||||
subscription.Notes = string.IsNullOrWhiteSpace(existingNotes)
|
||||
? extendNote
|
||||
: $"{existingNotes}\n{extendNote}";
|
||||
}
|
||||
|
||||
// 6. 更新时间戳
|
||||
subscription.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// 7. 保存变更
|
||||
await subscriptionRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 8. 获取更新后的订阅信息
|
||||
var result = await subscriptionRepository.GetListResultByIdAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
// 9. 如果不存在,返回 null
|
||||
if (result is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 10. 映射为 DTO 并返回
|
||||
return new SubscriptionListDto
|
||||
{
|
||||
Id = result.Id,
|
||||
TenantId = result.TenantId,
|
||||
TenantName = result.TenantName,
|
||||
TenantCode = result.TenantCode,
|
||||
TenantPackageId = result.TenantPackageId,
|
||||
PackageName = result.PackageName,
|
||||
ScheduledPackageId = result.ScheduledPackageId,
|
||||
ScheduledPackageName = result.ScheduledPackageName,
|
||||
Status = result.Status,
|
||||
EffectiveFrom = result.EffectiveFrom,
|
||||
EffectiveTo = result.EffectiveTo,
|
||||
NextBillingDate = result.NextBillingDate,
|
||||
AutoRenew = result.AutoRenew,
|
||||
Notes = result.Notes,
|
||||
CreatedAt = result.CreatedAt,
|
||||
UpdatedAt = result.UpdatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Contracts;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Queries;
|
||||
using TakeoutSaaS.Application.App.TenantPackages.Contracts;
|
||||
using TakeoutSaaS.Domain.Tenants.Enums;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 获取订阅详情查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetSubscriptionDetailQueryHandler(
|
||||
ISubscriptionRepository subscriptionRepository,
|
||||
ITenantRepository tenantRepository)
|
||||
: IRequestHandler<GetSubscriptionDetailQuery, SubscriptionDetailDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<SubscriptionDetailDto?> Handle(
|
||||
GetSubscriptionDetailQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询订阅详情
|
||||
var detail = await subscriptionRepository.GetDetailAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
// 2. 如果不存在,返回 null
|
||||
if (detail is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 查询变更历史
|
||||
var histories = await subscriptionRepository.GetHistoriesAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
// 4. 查询配额使用情况
|
||||
var quotaUsages = await tenantRepository.GetQuotaUsagesAsync(detail.TenantId, cancellationToken);
|
||||
|
||||
// 5. 映射套餐信息
|
||||
var packageDto = MapToPackageDto(detail.Package);
|
||||
var scheduledPackageDto = detail.ScheduledPackage is not null
|
||||
? MapToPackageDto(detail.ScheduledPackage)
|
||||
: null;
|
||||
|
||||
// 6. 映射配额使用情况
|
||||
var quotaUsageDtos = quotaUsages.Select(u =>
|
||||
{
|
||||
var limit = (int?)u.LimitValue;
|
||||
var used = (int)u.UsedValue;
|
||||
var remaining = limit.HasValue ? Math.Max(0, limit.Value - used) : (int?)null;
|
||||
var usagePercentage = limit.HasValue && limit.Value > 0
|
||||
? Math.Round((decimal)used / limit.Value * 100, 2)
|
||||
: (decimal?)null;
|
||||
|
||||
return new SubscriptionQuotaUsageDto
|
||||
{
|
||||
QuotaType = u.QuotaType,
|
||||
QuotaName = GetQuotaTypeName(u.QuotaType),
|
||||
Limit = limit,
|
||||
Used = used,
|
||||
Remaining = remaining,
|
||||
UsagePercentage = usagePercentage
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
// 7. 映射变更历史
|
||||
var historyDtos = histories.Select(h => new SubscriptionHistoryDto
|
||||
{
|
||||
Id = h.Id,
|
||||
SubscriptionId = h.SubscriptionId,
|
||||
ChangeType = h.ChangeType.ToString(),
|
||||
PreviousPackageId = h.FromPackageId,
|
||||
PreviousPackageName = h.FromPackageName,
|
||||
NewPackageId = h.ToPackageId,
|
||||
NewPackageName = h.ToPackageName,
|
||||
PreviousEffectiveTo = h.EffectiveFrom,
|
||||
NewEffectiveTo = h.EffectiveTo,
|
||||
Notes = h.Notes,
|
||||
CreatedAt = h.CreatedAt,
|
||||
CreatedBy = h.CreatedBy?.ToString()
|
||||
}).ToList();
|
||||
|
||||
// 8. 返回订阅详情 DTO
|
||||
return new SubscriptionDetailDto
|
||||
{
|
||||
Id = detail.Id,
|
||||
TenantId = detail.TenantId,
|
||||
TenantName = detail.TenantName,
|
||||
TenantCode = detail.TenantCode,
|
||||
TenantPackageId = detail.TenantPackageId,
|
||||
PackageName = detail.PackageName,
|
||||
ScheduledPackageId = detail.ScheduledPackageId,
|
||||
ScheduledPackageName = detail.ScheduledPackageName,
|
||||
Status = detail.Status,
|
||||
EffectiveFrom = detail.EffectiveFrom,
|
||||
EffectiveTo = detail.EffectiveTo,
|
||||
NextBillingDate = detail.NextBillingDate,
|
||||
AutoRenew = detail.AutoRenew,
|
||||
Notes = detail.Notes,
|
||||
CreatedAt = detail.CreatedAt,
|
||||
UpdatedAt = detail.UpdatedAt,
|
||||
Package = packageDto,
|
||||
ScheduledPackage = scheduledPackageDto,
|
||||
QuotaUsages = quotaUsageDtos,
|
||||
ChangeHistory = historyDtos
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 映射套餐实体为 DTO。
|
||||
/// </summary>
|
||||
private static TenantPackageListDto MapToPackageDto(TakeoutSaaS.Domain.Tenants.Entities.TenantPackage package)
|
||||
{
|
||||
return new TenantPackageListDto
|
||||
{
|
||||
Id = package.Id,
|
||||
Name = package.Name,
|
||||
Description = package.Description,
|
||||
PackageType = package.PackageType,
|
||||
MonthlyPrice = package.MonthlyPrice,
|
||||
YearlyPrice = package.YearlyPrice,
|
||||
MaxStoreCount = package.MaxStoreCount,
|
||||
MaxAccountCount = package.MaxAccountCount,
|
||||
MaxStorageGb = package.MaxStorageGb,
|
||||
MaxSmsCredits = package.MaxSmsCredits,
|
||||
MaxDeliveryOrders = package.MaxDeliveryOrders,
|
||||
FeaturePoliciesJson = package.FeaturePoliciesJson,
|
||||
IsActive = package.IsActive,
|
||||
IsPublicVisible = package.IsPublicVisible,
|
||||
IsAllowNewTenantPurchase = package.IsAllowNewTenantPurchase,
|
||||
PublishStatus = package.PublishStatus,
|
||||
IsRecommended = package.IsRecommended,
|
||||
Tags = package.Tags ?? [],
|
||||
SortOrder = package.SortOrder
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取配额类型名称。
|
||||
/// </summary>
|
||||
private static string GetQuotaTypeName(TenantQuotaType quotaType)
|
||||
{
|
||||
return quotaType switch
|
||||
{
|
||||
TenantQuotaType.Store => "门店数量",
|
||||
TenantQuotaType.Account => "账号数量",
|
||||
TenantQuotaType.StorageGb => "存储空间(GB)",
|
||||
TenantQuotaType.SmsCredits => "短信额度",
|
||||
TenantQuotaType.DeliveryOrders => "配送订单数",
|
||||
_ => quotaType.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Contracts;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Queries;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 获取订阅列表查询处理器。
|
||||
/// </summary>
|
||||
public sealed class ListSubscriptionsQueryHandler(ISubscriptionRepository subscriptionRepository)
|
||||
: IRequestHandler<ListSubscriptionsQuery, PagedResult<SubscriptionListDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<PagedResult<SubscriptionListDto>> Handle(
|
||||
ListSubscriptionsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询订阅列表
|
||||
var (items, totalCount) = await subscriptionRepository.GetListAsync(
|
||||
request.Status,
|
||||
request.TenantPackageId,
|
||||
request.TenantId,
|
||||
request.TenantKeyword,
|
||||
request.ExpiringWithinDays,
|
||||
request.AutoRenew,
|
||||
request.ExpireFrom,
|
||||
request.ExpireTo,
|
||||
request.Page,
|
||||
request.PageSize,
|
||||
cancellationToken);
|
||||
|
||||
// 2. 映射为 DTO
|
||||
var dtos = items.Select(s => new SubscriptionListDto
|
||||
{
|
||||
Id = s.Id,
|
||||
TenantId = s.TenantId,
|
||||
TenantName = s.TenantName,
|
||||
TenantCode = s.TenantCode,
|
||||
TenantPackageId = s.TenantPackageId,
|
||||
PackageName = s.PackageName,
|
||||
ScheduledPackageId = s.ScheduledPackageId,
|
||||
ScheduledPackageName = s.ScheduledPackageName,
|
||||
Status = s.Status,
|
||||
EffectiveFrom = s.EffectiveFrom,
|
||||
EffectiveTo = s.EffectiveTo,
|
||||
NextBillingDate = s.NextBillingDate,
|
||||
AutoRenew = s.AutoRenew,
|
||||
Notes = s.Notes,
|
||||
CreatedAt = s.CreatedAt,
|
||||
UpdatedAt = s.UpdatedAt
|
||||
}).ToList();
|
||||
|
||||
// 3. 返回分页结果
|
||||
return new PagedResult<SubscriptionListDto>(dtos, totalCount, request.Page, request.PageSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Commands;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Contracts;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 更新订阅状态命令处理器。
|
||||
/// </summary>
|
||||
public sealed class UpdateStatusCommandHandler(ISubscriptionRepository subscriptionRepository)
|
||||
: IRequestHandler<UpdateStatusCommand, SubscriptionListDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<SubscriptionListDto?> Handle(
|
||||
UpdateStatusCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取订阅(带跟踪)
|
||||
var subscription = await subscriptionRepository.GetByIdForUpdateAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
// 2. 如果不存在,返回 null
|
||||
if (subscription is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 更新状态
|
||||
var oldStatus = subscription.Status;
|
||||
subscription.Status = request.Status;
|
||||
|
||||
// 4. 更新备注(如果提供)
|
||||
if (!string.IsNullOrWhiteSpace(request.Notes))
|
||||
{
|
||||
var existingNotes = subscription.Notes ?? string.Empty;
|
||||
var statusNote = $"[状态变更: {oldStatus} -> {request.Status}] {request.Notes}";
|
||||
subscription.Notes = string.IsNullOrWhiteSpace(existingNotes)
|
||||
? statusNote
|
||||
: $"{existingNotes}\n{statusNote}";
|
||||
}
|
||||
|
||||
// 5. 更新时间戳
|
||||
subscription.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// 6. 保存变更
|
||||
await subscriptionRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 7. 获取更新后的订阅信息
|
||||
var result = await subscriptionRepository.GetListResultByIdAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
// 8. 如果不存在,返回 null
|
||||
if (result is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 9. 映射为 DTO 并返回
|
||||
return new SubscriptionListDto
|
||||
{
|
||||
Id = result.Id,
|
||||
TenantId = result.TenantId,
|
||||
TenantName = result.TenantName,
|
||||
TenantCode = result.TenantCode,
|
||||
TenantPackageId = result.TenantPackageId,
|
||||
PackageName = result.PackageName,
|
||||
ScheduledPackageId = result.ScheduledPackageId,
|
||||
ScheduledPackageName = result.ScheduledPackageName,
|
||||
Status = result.Status,
|
||||
EffectiveFrom = result.EffectiveFrom,
|
||||
EffectiveTo = result.EffectiveTo,
|
||||
NextBillingDate = result.NextBillingDate,
|
||||
AutoRenew = result.AutoRenew,
|
||||
Notes = result.Notes,
|
||||
CreatedAt = result.CreatedAt,
|
||||
UpdatedAt = result.UpdatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Commands;
|
||||
using TakeoutSaaS.Application.App.Subscriptions.Contracts;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 更新订阅命令处理器。
|
||||
/// </summary>
|
||||
public sealed class UpdateSubscriptionCommandHandler(ISubscriptionRepository subscriptionRepository)
|
||||
: IRequestHandler<UpdateSubscriptionCommand, SubscriptionListDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<SubscriptionListDto?> Handle(
|
||||
UpdateSubscriptionCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取订阅(带跟踪)
|
||||
var subscription = await subscriptionRepository.GetByIdForUpdateAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
// 2. 如果不存在,返回 null
|
||||
if (subscription is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 更新字段
|
||||
if (request.AutoRenew.HasValue)
|
||||
{
|
||||
subscription.AutoRenew = request.AutoRenew.Value;
|
||||
}
|
||||
|
||||
if (request.Notes is not null)
|
||||
{
|
||||
subscription.Notes = request.Notes;
|
||||
}
|
||||
|
||||
// 4. 更新时间戳
|
||||
subscription.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// 5. 保存变更
|
||||
await subscriptionRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 6. 获取更新后的订阅信息
|
||||
var result = await subscriptionRepository.GetListResultByIdAsync(request.SubscriptionId, cancellationToken);
|
||||
|
||||
// 7. 如果不存在,返回 null
|
||||
if (result is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 8. 映射为 DTO 并返回
|
||||
return new SubscriptionListDto
|
||||
{
|
||||
Id = result.Id,
|
||||
TenantId = result.TenantId,
|
||||
TenantName = result.TenantName,
|
||||
TenantCode = result.TenantCode,
|
||||
TenantPackageId = result.TenantPackageId,
|
||||
PackageName = result.PackageName,
|
||||
ScheduledPackageId = result.ScheduledPackageId,
|
||||
ScheduledPackageName = result.ScheduledPackageName,
|
||||
Status = result.Status,
|
||||
EffectiveFrom = result.EffectiveFrom,
|
||||
EffectiveTo = result.EffectiveTo,
|
||||
NextBillingDate = result.NextBillingDate,
|
||||
AutoRenew = result.AutoRenew,
|
||||
Notes = result.Notes,
|
||||
CreatedAt = result.CreatedAt,
|
||||
UpdatedAt = result.UpdatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user