refactor(commerce): split administration services
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Domain.Commerce;
|
||||
using Tiku.Domain.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed class ActivationCodeAdministrationService(CommerceAdministrationDependencies dependencies)
|
||||
: CommerceAdministrationServiceBase(dependencies), IActivationCodeAdministrationService
|
||||
{
|
||||
public async Task<CodeBatchItem> CreateCodeBatchAsync(
|
||||
CommerceAdminActor actor,
|
||||
CreateCodeBatchCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
if (command.TotalCount is < 1 or > 1000)
|
||||
throw new CommerceException("Code batch total count must be between 1 and 1000.",
|
||||
"invalid_code_batch_count");
|
||||
|
||||
if (command.Days <= 0)
|
||||
throw new CommerceException("Activation code days must be positive.", "invalid_activation_days");
|
||||
|
||||
if (command.RegionId.HasValue)
|
||||
{
|
||||
var regionExists = await dbContext.Regions
|
||||
.AnyAsync(item => item.TenantId == actor.TenantId && item.Id == command.RegionId.Value,
|
||||
cancellationToken);
|
||||
if (!regionExists) throw new CommerceException("Region was not found.", "region_not_found");
|
||||
}
|
||||
|
||||
var batch = new CodeBatch
|
||||
{
|
||||
TenantId = actor.TenantId,
|
||||
RegionId = command.RegionId,
|
||||
CreatedBy = actor.UserId,
|
||||
Name = command.Name.Trim(),
|
||||
SaleType = command.SaleType?.Trim(),
|
||||
Channel = command.Channel?.Trim(),
|
||||
DefaultUnitPriceCents = command.DefaultUnitPriceCents ?? 0,
|
||||
CostPriceCents = command.CostPriceCents ?? 0,
|
||||
TotalCount = command.TotalCount,
|
||||
Days = command.Days,
|
||||
IssuedAt = DateTimeOffset.UtcNow,
|
||||
Remark = command.Remark
|
||||
};
|
||||
dbContext.CodeBatches.Add(batch);
|
||||
for (var index = 0; index < command.TotalCount; index++)
|
||||
dbContext.ActivationCodes.Add(new ActivationCode
|
||||
{
|
||||
TenantId = actor.TenantId,
|
||||
BatchId = batch.Id,
|
||||
Code = GenerateActivationCode(),
|
||||
Days = command.Days,
|
||||
SaleType = batch.SaleType,
|
||||
UnitPriceCents = batch.DefaultUnitPriceCents,
|
||||
Remark = batch.Remark
|
||||
});
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return ToCodeBatchItem(batch);
|
||||
}
|
||||
|
||||
public async Task<ActivationCodeList> GetActivationCodesAsync(
|
||||
CommerceAdminActor actor,
|
||||
CommerceAdminQuery query,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
var codes = dbContext.ActivationCodes.AsNoTracking()
|
||||
.Where(item => item.TenantId == actor.TenantId);
|
||||
if (!string.IsNullOrWhiteSpace(query.Status))
|
||||
{
|
||||
var used = string.Equals(query.Status, "used", StringComparison.OrdinalIgnoreCase);
|
||||
codes = codes.Where(item => item.IsUsed == used);
|
||||
}
|
||||
|
||||
var items = await codes
|
||||
.OrderByDescending(item => item.CreatedAt)
|
||||
.Take(Math.Clamp(query.Limit ?? 50, 1, 200))
|
||||
.ToArrayAsync(cancellationToken);
|
||||
return new ActivationCodeList(items.Select(ToActivationCodeItem).ToArray());
|
||||
}
|
||||
|
||||
public async Task<ActivationCodeItem> RedeemActivationCodeAsync(
|
||||
CommerceAdminActor actor,
|
||||
RedeemActivationCodeCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
var code = await dbContext.ActivationCodes
|
||||
.SingleOrDefaultAsync(item =>
|
||||
item.TenantId == actor.TenantId &&
|
||||
item.Code == command.Code.Trim(),
|
||||
cancellationToken)
|
||||
?? throw new CommerceException("Activation code was not found.", "activation_code_not_found");
|
||||
if (code.IsUsed) throw new CommerceException("Activation code has already been used.", "activation_code_used");
|
||||
|
||||
var userIsMember = await dbContext.TenantMemberships.AnyAsync(item =>
|
||||
item.TenantId == actor.TenantId &&
|
||||
item.UserId == command.UserId &&
|
||||
item.Status == MembershipStatus.Active,
|
||||
cancellationToken);
|
||||
if (!userIsMember)
|
||||
throw new CommerceException("Target user is not a tenant member.", "tenant_member_not_found");
|
||||
|
||||
code.IsUsed = true;
|
||||
code.UsedBy = command.UserId;
|
||||
code.UsedRegionId = command.RegionId;
|
||||
code.UsedAt = DateTimeOffset.UtcNow;
|
||||
dbContext.Entitlements.Add(new Entitlement
|
||||
{
|
||||
TenantId = actor.TenantId,
|
||||
UserId = command.UserId,
|
||||
EntitlementType = "svip",
|
||||
SourceType = "activation_code",
|
||||
SourceId = code.Id,
|
||||
StartsAt = DateTimeOffset.UtcNow,
|
||||
ExpiresAt = DateTimeOffset.UtcNow.AddDays(code.Days),
|
||||
Status = EntitlementStatus.Active,
|
||||
Metadata = JsonSerializer.SerializeToElement(new { code.Code, code.BatchId })
|
||||
});
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return ToActivationCodeItem(code);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user