refactor(commerce): split administration services

This commit is contained in:
2026-08-04 09:29:20 +08:00
parent 4d85463a87
commit 0bd075e20d
29 changed files with 1187 additions and 952 deletions

View File

@@ -0,0 +1,195 @@
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Commerce;
using Tiku.Application.Points;
using Tiku.Domain.Commerce;
using Tiku.Infrastructure.Commerce;
namespace Tiku.Infrastructure.Points;
internal sealed class PointAdministrationService(CommerceAdministrationDependencies dependencies)
: CommerceAdministrationServiceBase(dependencies), IPointAdministrationService
{
public async Task<TenantPointTaskList> GetPointTasksAsync(
CommerceAdminActor actor,
TenantPointQuery query,
CancellationToken cancellationToken = default)
{
await AssertAdminAsync(actor, cancellationToken);
var tasks = dbContext.PointActivityTasks.AsNoTracking()
.Where(item => item.TenantId == actor.TenantId);
if (!string.IsNullOrWhiteSpace(query.Status))
tasks = tasks.Where(item => item.Status == ParsePointTaskStatus(query.Status));
var items = await tasks
.OrderBy(item => item.SortOrder)
.ThenByDescending(item => item.CreatedAt)
.Take(Math.Clamp(query.Limit ?? 50, 1, 200))
.ToArrayAsync(cancellationToken);
return new TenantPointTaskList(items);
}
public async Task<PointActivityTask> UpsertPointTaskAsync(
CommerceAdminActor actor,
UpsertPointTaskCommand command,
CancellationToken cancellationToken = default)
{
await AssertAdminAsync(actor, cancellationToken);
if (command.Points <= 0 || command.MaxClaimsPerUser <= 0)
throw new CommerceException("Point task points and claim limit must be positive.", "invalid_point_task");
var task = command.Id.HasValue
? await dbContext.PointActivityTasks.SingleOrDefaultAsync(
item => item.TenantId == actor.TenantId && item.Id == command.Id.Value,
cancellationToken)
: await dbContext.PointActivityTasks.SingleOrDefaultAsync(
item => item.TenantId == actor.TenantId && item.TaskKey == command.TaskKey.Trim(),
cancellationToken);
if (task is null)
{
task = new PointActivityTask { TenantId = actor.TenantId };
dbContext.PointActivityTasks.Add(task);
}
task.TaskKey = command.TaskKey.Trim();
task.Title = command.Title.Trim();
task.Description = command.Description?.Trim();
task.TaskType = command.TaskType;
task.Status = command.Status;
task.Points = command.Points;
task.MaxClaimsPerUser = command.MaxClaimsPerUser;
task.StartsAt = command.StartsAt;
task.EndsAt = command.EndsAt;
task.SortOrder = command.SortOrder;
task.Rules = JsonObjectOrDefault(command.Rules);
task.Metadata = JsonObjectOrDefault(command.Metadata);
await dbContext.SaveChangesAsync(cancellationToken);
return task;
}
public async Task<TenantPointClaimList> GetPointClaimsAsync(
CommerceAdminActor actor,
TenantPointQuery query,
CancellationToken cancellationToken = default)
{
await AssertAdminAsync(actor, cancellationToken);
var claims = dbContext.PointActivityClaims.AsNoTracking()
.Where(item => item.TenantId == actor.TenantId);
if (query.UserId.HasValue) claims = claims.Where(item => item.UserId == query.UserId.Value);
var items = await claims
.OrderByDescending(item => item.CreatedAt)
.Take(Math.Clamp(query.Limit ?? 50, 1, 200))
.ToArrayAsync(cancellationToken);
return new TenantPointClaimList(items);
}
public async Task<TenantPointExchangeItemList> GetPointExchangeItemsAsync(
CommerceAdminActor actor,
TenantPointQuery query,
CancellationToken cancellationToken = default)
{
await AssertAdminAsync(actor, cancellationToken);
var items = dbContext.PointExchangeItems.AsNoTracking()
.Where(item => item.TenantId == actor.TenantId);
if (!string.IsNullOrWhiteSpace(query.Status))
items = items.Where(item => item.Status == ParsePointExchangeItemStatus(query.Status));
if (query.RegionId.HasValue)
items = items.Where(item => item.RegionId == null || item.RegionId == query.RegionId.Value);
var result = await items
.OrderBy(item => item.SortOrder)
.ThenByDescending(item => item.CreatedAt)
.Take(Math.Clamp(query.Limit ?? 50, 1, 200))
.ToArrayAsync(cancellationToken);
return new TenantPointExchangeItemList(result);
}
public async Task<PointExchangeItem> UpsertPointExchangeItemAsync(
CommerceAdminActor actor,
UpsertPointExchangeItemCommand command,
CancellationToken cancellationToken = default)
{
await AssertAdminAsync(actor, cancellationToken);
if (command.PointsCost <= 0)
throw new CommerceException("Point exchange item cost must be positive.", "invalid_point_exchange_item");
var item = command.Id.HasValue
? await dbContext.PointExchangeItems.SingleOrDefaultAsync(
entry => entry.TenantId == actor.TenantId && entry.Id == command.Id.Value,
cancellationToken)
: await dbContext.PointExchangeItems.SingleOrDefaultAsync(
entry => entry.TenantId == actor.TenantId && entry.ItemKey == command.ItemKey.Trim(),
cancellationToken);
if (item is null)
{
item = new PointExchangeItem { TenantId = actor.TenantId };
dbContext.PointExchangeItems.Add(item);
}
item.RegionId = command.RegionId;
item.ItemKey = command.ItemKey.Trim();
item.Name = command.Name.Trim();
item.Description = command.Description?.Trim();
item.ItemType = command.ItemType;
item.Status = command.Status;
item.PointsCost = command.PointsCost;
item.Stock = command.Stock;
item.Days = command.Days;
item.SortOrder = command.SortOrder;
item.StartsAt = command.StartsAt;
item.EndsAt = command.EndsAt;
item.FulfillmentPayload = JsonObjectOrDefault(command.FulfillmentPayload);
item.Metadata = JsonObjectOrDefault(command.Metadata);
await dbContext.SaveChangesAsync(cancellationToken);
return item;
}
public async Task<TenantPointExchangeOrderList> GetPointExchangeOrdersAsync(
CommerceAdminActor actor,
TenantPointQuery query,
CancellationToken cancellationToken = default)
{
await AssertAdminAsync(actor, cancellationToken);
var orders = dbContext.PointExchangeOrders.AsNoTracking()
.Where(item => item.TenantId == actor.TenantId);
if (query.UserId.HasValue) orders = orders.Where(item => item.UserId == query.UserId.Value);
if (!string.IsNullOrWhiteSpace(query.Status))
orders = orders.Where(item => item.Status == ParsePointExchangeOrderStatus(query.Status));
var result = await orders
.OrderByDescending(item => item.CreatedAt)
.Take(Math.Clamp(query.Limit ?? 50, 1, 200))
.ToArrayAsync(cancellationToken);
return new TenantPointExchangeOrderList(result);
}
public async Task<PointExchangeOrder> UpdatePointExchangeOrderStatusAsync(
CommerceAdminActor actor,
UpdatePointExchangeOrderStatusCommand command,
CancellationToken cancellationToken = default)
{
await AssertAdminAsync(actor, cancellationToken);
var order = await dbContext.PointExchangeOrders
.SingleOrDefaultAsync(item => item.TenantId == actor.TenantId && item.Id == command.OrderId,
cancellationToken)
?? throw new CommerceException("Point exchange order was not found.",
"point_exchange_order_not_found");
order.Status = command.Status;
if (command.Status == PointExchangeOrderStatus.Completed)
{
order.CompletedAt ??= DateTimeOffset.UtcNow;
order.CancelledAt = null;
}
else if (command.Status == PointExchangeOrderStatus.Cancelled)
{
order.CancelledAt ??= DateTimeOffset.UtcNow;
}
await dbContext.SaveChangesAsync(cancellationToken);
return order;
}
}