feat: add tenant points and coupon operations

This commit is contained in:
xiong
2026-07-26 20:08:23 +08:00
parent 7139da766a
commit 4efccf8355
5 changed files with 780 additions and 0 deletions

View File

@@ -17,6 +17,10 @@ public sealed class TenantCommerceQueryDto
[Range(1, 200)]
public int? Limit { get; set; }
public Guid? UserId { get; set; }
public Guid? RegionId { get; set; }
}
public sealed class UpsertPaymentAccountDto
@@ -134,3 +138,162 @@ public sealed class RedeemActivationCodeDto
return new RedeemActivationCodeCommand(Code, UserId, RegionId);
}
}
public sealed class UpsertPointTaskDto
{
public Guid? Id { get; set; }
[Required]
[StringLength(100)]
public string TaskKey { get; set; } = string.Empty;
[Required]
[StringLength(200)]
public string Title { get; set; } = string.Empty;
[StringLength(1000)]
public string? Description { get; set; }
public PointActivityTaskType TaskType { get; set; } = PointActivityTaskType.Manual;
public PointActivityTaskStatus Status { get; set; } = PointActivityTaskStatus.Active;
[Range(1, int.MaxValue)]
public int Points { get; set; }
[Range(1, 1000)]
public int MaxClaimsPerUser { get; set; } = 1;
public DateTimeOffset? StartsAt { get; set; }
public DateTimeOffset? EndsAt { get; set; }
public int SortOrder { get; set; }
public JsonElement Rules { get; set; } = JsonDefaults.Object();
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public UpsertPointTaskCommand ToCommand()
{
return new UpsertPointTaskCommand(
Id,
TaskKey,
Title,
Description,
TaskType,
Status,
Points,
MaxClaimsPerUser,
StartsAt,
EndsAt,
SortOrder,
Rules,
Metadata);
}
}
public sealed class UpsertPointExchangeItemDto
{
public Guid? Id { get; set; }
public Guid? RegionId { get; set; }
[Required]
[StringLength(100)]
public string ItemKey { get; set; } = string.Empty;
[Required]
[StringLength(200)]
public string Name { get; set; } = string.Empty;
[StringLength(1000)]
public string? Description { get; set; }
public PointExchangeItemType ItemType { get; set; } = PointExchangeItemType.Entitlement;
public PointExchangeItemStatus Status { get; set; } = PointExchangeItemStatus.Active;
[Range(1, int.MaxValue)]
public int PointsCost { get; set; }
[Range(0, int.MaxValue)]
public int? Stock { get; set; }
[Range(0, 3650)]
public int? Days { get; set; }
public int SortOrder { get; set; }
public DateTimeOffset? StartsAt { get; set; }
public DateTimeOffset? EndsAt { get; set; }
public JsonElement FulfillmentPayload { get; set; } = JsonDefaults.Object();
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public UpsertPointExchangeItemCommand ToCommand()
{
return new UpsertPointExchangeItemCommand(
Id,
RegionId,
ItemKey,
Name,
Description,
ItemType,
Status,
PointsCost,
Stock,
Days,
SortOrder,
StartsAt,
EndsAt,
FulfillmentPayload,
Metadata);
}
}
public sealed class UpdatePointExchangeOrderStatusDto
{
[Required]
public Guid OrderId { get; set; }
public PointExchangeOrderStatus Status { get; set; }
public UpdatePointExchangeOrderStatusCommand ToCommand()
{
return new UpdatePointExchangeOrderStatusCommand(OrderId, Status);
}
}
public sealed class UpsertTenantCouponDto
{
public Guid? Id { get; set; }
[Required]
[StringLength(100)]
public string Code { get; set; } = string.Empty;
public Guid? PlanId { get; set; }
public DiscountType DiscountType { get; set; } = DiscountType.Fixed;
[Range(typeof(decimal), "0.01", "999999")]
public decimal DiscountValue { get; set; }
public DateTimeOffset? ValidFrom { get; set; }
public DateTimeOffset? ValidTo { get; set; }
[Range(1, int.MaxValue)]
public int? MaxUses { get; set; }
[StringLength(50)]
public string? Source { get; set; }
[StringLength(1000)]
public string? Remark { get; set; }
public UpsertCouponCommand ToCommand()
{
return new UpsertCouponCommand(
Id,
Code,
PlanId,
DiscountType,
DiscountValue,
ValidFrom,
ValidTo,
MaxUses,
Source,
Remark);
}
}

View File

@@ -119,6 +119,149 @@ public sealed class TenantCommerceController(
cancellationToken));
}
[HttpGet("point-activity-tasks")]
[EndpointSummary("查询积分活动任务")]
[ProducesResponseType<TenantPointTaskList>(StatusCodes.Status200OK)]
public async Task<ActionResult<TenantPointTaskList>> PointTasks(
[FromQuery] TenantCommerceQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await commerceAdminService.GetPointTasksAsync(
ResolveActor(),
ToPointQuery(query),
cancellationToken));
}
[HttpPut("point-activity-tasks")]
[EndpointSummary("新增或更新积分活动任务")]
[ProducesResponseType<object>(StatusCodes.Status200OK)]
public async Task<ActionResult<object>> UpsertPointTask(
UpsertPointTaskDto request,
CancellationToken cancellationToken)
{
return Ok(await commerceAdminService.UpsertPointTaskAsync(
ResolveActor(),
request.ToCommand(),
cancellationToken));
}
[HttpGet("point-activity-claims")]
[EndpointSummary("查询积分任务领取记录")]
[ProducesResponseType<TenantPointClaimList>(StatusCodes.Status200OK)]
public async Task<ActionResult<TenantPointClaimList>> PointClaims(
[FromQuery] TenantCommerceQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await commerceAdminService.GetPointClaimsAsync(
ResolveActor(),
ToPointQuery(query),
cancellationToken));
}
[HttpGet("point-exchange-items")]
[EndpointSummary("查询积分兑换项")]
[ProducesResponseType<TenantPointExchangeItemList>(StatusCodes.Status200OK)]
public async Task<ActionResult<TenantPointExchangeItemList>> PointExchangeItems(
[FromQuery] TenantCommerceQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await commerceAdminService.GetPointExchangeItemsAsync(
ResolveActor(),
ToPointQuery(query),
cancellationToken));
}
[HttpPut("point-exchange-items")]
[EndpointSummary("新增或更新积分兑换项")]
[ProducesResponseType<object>(StatusCodes.Status200OK)]
public async Task<ActionResult<object>> UpsertPointExchangeItem(
UpsertPointExchangeItemDto request,
CancellationToken cancellationToken)
{
return Ok(await commerceAdminService.UpsertPointExchangeItemAsync(
ResolveActor(),
request.ToCommand(),
cancellationToken));
}
[HttpGet("point-exchange-orders")]
[EndpointSummary("查询积分兑换订单")]
[ProducesResponseType<TenantPointExchangeOrderList>(StatusCodes.Status200OK)]
public async Task<ActionResult<TenantPointExchangeOrderList>> PointExchangeOrders(
[FromQuery] TenantCommerceQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await commerceAdminService.GetPointExchangeOrdersAsync(
ResolveActor(),
ToPointQuery(query),
cancellationToken));
}
[HttpPost("point-exchange-orders/status")]
[EndpointSummary("更新积分兑换订单状态")]
[ProducesResponseType<object>(StatusCodes.Status200OK)]
public async Task<ActionResult<object>> UpdatePointExchangeOrderStatus(
UpdatePointExchangeOrderStatusDto request,
CancellationToken cancellationToken)
{
return Ok(await commerceAdminService.UpdatePointExchangeOrderStatusAsync(
ResolveActor(),
request.ToCommand(),
cancellationToken));
}
[HttpGet("coupons")]
[EndpointSummary("查询租户优惠券")]
[ProducesResponseType<TenantCouponList>(StatusCodes.Status200OK)]
public async Task<ActionResult<TenantCouponList>> Coupons(
[FromQuery] TenantCommerceQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await commerceAdminService.GetCouponsAsync(
ResolveActor(),
ToQuery(query),
cancellationToken));
}
[HttpPut("coupons")]
[EndpointSummary("新增或更新租户优惠券")]
[ProducesResponseType<object>(StatusCodes.Status200OK)]
public async Task<ActionResult<object>> UpsertCoupon(
UpsertTenantCouponDto request,
CancellationToken cancellationToken)
{
return Ok(await commerceAdminService.UpsertCouponAsync(
ResolveActor(),
request.ToCommand(),
cancellationToken));
}
[HttpGet("coupons/redemptions")]
[EndpointSummary("查询优惠券领取和核销记录")]
[ProducesResponseType<TenantCouponRedemptionList>(StatusCodes.Status200OK)]
public async Task<ActionResult<TenantCouponRedemptionList>> CouponRedemptions(
[FromQuery] TenantCommerceQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await commerceAdminService.GetCouponRedemptionsAsync(
ResolveActor(),
ToQuery(query),
cancellationToken));
}
[HttpGet("coupons/report")]
[EndpointSummary("查询优惠券基础报表")]
[ProducesResponseType<TenantCouponReport>(StatusCodes.Status200OK)]
public async Task<ActionResult<TenantCouponReport>> CouponReport(
[FromQuery] TenantCommerceQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await commerceAdminService.GetCouponReportAsync(
ResolveActor(),
ToQuery(query),
cancellationToken));
}
private CommerceAdminActor ResolveActor()
{
if (currentTenant.TenantId is null || currentUser.UserId is null)
@@ -133,4 +276,9 @@ public sealed class TenantCommerceController(
{
return new CommerceAdminQuery(query.Provider, query.Status, query.Limit);
}
private static TenantPointQuery ToPointQuery(TenantCommerceQueryDto query)
{
return new TenantPointQuery(query.Status, query.Limit, query.UserId, query.RegionId);
}
}