196 lines
8.3 KiB
C#
196 lines
8.3 KiB
C#
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 = pointsPersistence.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 pointsPersistence.PointActivityTasks.SingleOrDefaultAsync(
|
|
item => item.TenantId == actor.TenantId && item.Id == command.Id.Value,
|
|
cancellationToken)
|
|
: await pointsPersistence.PointActivityTasks.SingleOrDefaultAsync(
|
|
item => item.TenantId == actor.TenantId && item.TaskKey == command.TaskKey.Trim(),
|
|
cancellationToken);
|
|
if (task is null)
|
|
{
|
|
task = new PointActivityTask { TenantId = actor.TenantId };
|
|
pointsPersistence.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 unitOfWork.SaveChangesAsync(cancellationToken);
|
|
return task;
|
|
}
|
|
|
|
public async Task<TenantPointClaimList> GetPointClaimsAsync(
|
|
CommerceAdminActor actor,
|
|
TenantPointQuery query,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
await AssertAdminAsync(actor, cancellationToken);
|
|
var claims = pointsPersistence.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 = pointsPersistence.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 pointsPersistence.PointExchangeItems.SingleOrDefaultAsync(
|
|
entry => entry.TenantId == actor.TenantId && entry.Id == command.Id.Value,
|
|
cancellationToken)
|
|
: await pointsPersistence.PointExchangeItems.SingleOrDefaultAsync(
|
|
entry => entry.TenantId == actor.TenantId && entry.ItemKey == command.ItemKey.Trim(),
|
|
cancellationToken);
|
|
if (item is null)
|
|
{
|
|
item = new PointExchangeItem { TenantId = actor.TenantId };
|
|
pointsPersistence.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 unitOfWork.SaveChangesAsync(cancellationToken);
|
|
return item;
|
|
}
|
|
|
|
public async Task<TenantPointExchangeOrderList> GetPointExchangeOrdersAsync(
|
|
CommerceAdminActor actor,
|
|
TenantPointQuery query,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
await AssertAdminAsync(actor, cancellationToken);
|
|
var orders = pointsPersistence.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 pointsPersistence.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 unitOfWork.SaveChangesAsync(cancellationToken);
|
|
return order;
|
|
}
|
|
}
|