- 后端新增管理员、商品、分类聚合模型 - 实现积分规则、礼品、订单、会员等完整功能 - 添加管理员认证和权限管理 - 完善数据库迁移和实体配置 - 前端管理后台实现登录、仪表盘、积分规则、礼品、订单、会员等页面 - 集成shadcn-vue UI组件库 - 添加前后端功能文档和截图
169 lines
6.1 KiB
C#
169 lines
6.1 KiB
C#
using Fengling.Backend.Domain.AggregatesModel.RedemptionOrderAggregate;
|
|
using Fengling.Backend.Domain.AggregatesModel.GiftAggregate;
|
|
using Fengling.Backend.Domain.AggregatesModel.MemberAggregate;
|
|
using Fengling.Backend.Infrastructure.Repositories;
|
|
|
|
namespace Fengling.Backend.Web.Application.Commands.RedemptionOrders;
|
|
|
|
/// <summary>
|
|
/// 创建兑换订单命令
|
|
/// </summary>
|
|
public record CreateRedemptionOrderCommand(
|
|
MemberId MemberId,
|
|
GiftId GiftId,
|
|
int Quantity,
|
|
CreateRedemptionOrderAddressDto? ShippingAddress = null) : ICommand<RedemptionOrderId>;
|
|
|
|
public record CreateRedemptionOrderAddressDto(
|
|
string ReceiverName,
|
|
string Phone,
|
|
string Province,
|
|
string City,
|
|
string District,
|
|
string DetailAddress);
|
|
|
|
public class CreateRedemptionOrderCommandHandler(
|
|
IRedemptionOrderRepository redemptionOrderRepository,
|
|
IGiftRepository giftRepository,
|
|
IMemberRepository memberRepository) : ICommandHandler<CreateRedemptionOrderCommand, RedemptionOrderId>
|
|
{
|
|
public async Task<RedemptionOrderId> Handle(CreateRedemptionOrderCommand request, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 获取礼品信息
|
|
var gift = await giftRepository.GetByIdAsync(request.GiftId, cancellationToken);
|
|
if (gift == null)
|
|
throw new KnownException("礼品不存在");
|
|
|
|
if (!gift.IsOnShelf)
|
|
throw new KnownException("礼品已下架");
|
|
|
|
// 2. 检查库存
|
|
if (gift.AvailableStock < request.Quantity)
|
|
throw new KnownException($"库存不足,当前可用:{gift.AvailableStock}");
|
|
|
|
// 3. 检查限兑数量
|
|
if (gift.LimitPerMember.HasValue)
|
|
{
|
|
var redeemedCount = await redemptionOrderRepository.GetMemberRedemptionCountAsync(
|
|
request.MemberId.Id, request.GiftId.Id, cancellationToken);
|
|
|
|
if (redeemedCount + request.Quantity > gift.LimitPerMember.Value)
|
|
throw new KnownException($"超出限兑数量,每人限兑{gift.LimitPerMember.Value}个,已兑换{redeemedCount}个");
|
|
}
|
|
|
|
// 4. 获取会员信息并检查积分
|
|
var member = await memberRepository.GetAsync(request.MemberId, cancellationToken);
|
|
if (member == null)
|
|
throw new KnownException("会员不存在");
|
|
|
|
var consumedPoints = gift.RequiredPoints * request.Quantity;
|
|
if (member.AvailablePoints < consumedPoints)
|
|
throw new KnownException($"积分不足,需要{consumedPoints}分,当前{member.AvailablePoints}分");
|
|
|
|
// 5. 扣减积分
|
|
member.ConsumePoints(consumedPoints, "兑换礼品", Guid.NewGuid());
|
|
await memberRepository.UpdateAsync(member, cancellationToken);
|
|
|
|
// 6. 构造收货地址
|
|
Address? shippingAddress = null;
|
|
if (request.ShippingAddress != null)
|
|
{
|
|
shippingAddress = new Address(
|
|
request.ShippingAddress.ReceiverName,
|
|
request.ShippingAddress.Phone,
|
|
request.ShippingAddress.Province,
|
|
request.ShippingAddress.City,
|
|
request.ShippingAddress.District,
|
|
request.ShippingAddress.DetailAddress);
|
|
}
|
|
else if (gift.Type == GiftType.Physical)
|
|
{
|
|
throw new KnownException("实物礼品必须提供收货地址");
|
|
}
|
|
|
|
// 7. 生成订单号
|
|
var orderNo = $"RO{DateTime.UtcNow:yyyyMMddHHmmss}{Random.Shared.Next(1000, 9999)}";
|
|
|
|
// 8. 创建订单
|
|
var order = new RedemptionOrder(
|
|
orderNo,
|
|
request.MemberId.Id,
|
|
request.GiftId.Id,
|
|
gift.Name,
|
|
(int)gift.Type,
|
|
request.Quantity,
|
|
consumedPoints,
|
|
shippingAddress);
|
|
|
|
await redemptionOrderRepository.AddAsync(order, cancellationToken);
|
|
|
|
return order.Id;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 标记订单为已发货命令
|
|
/// </summary>
|
|
public record MarkOrderAsDispatchedCommand(RedemptionOrderId OrderId, string? TrackingNo = null) : ICommand<ResponseData>;
|
|
|
|
public class MarkOrderAsDispatchedCommandHandler(
|
|
IRedemptionOrderRepository redemptionOrderRepository) : ICommandHandler<MarkOrderAsDispatchedCommand, ResponseData>
|
|
{
|
|
public async Task<ResponseData> Handle(MarkOrderAsDispatchedCommand request, CancellationToken cancellationToken)
|
|
{
|
|
var order = await redemptionOrderRepository.GetByIdAsync(request.OrderId, cancellationToken);
|
|
|
|
if (order == null)
|
|
throw new KnownException("订单不存在");
|
|
|
|
order.MarkAsDispatched(request.TrackingNo);
|
|
await redemptionOrderRepository.UpdateAsync(order, cancellationToken);
|
|
|
|
return new ResponseData();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 完成订单命令
|
|
/// </summary>
|
|
public record CompleteOrderCommand(RedemptionOrderId OrderId) : ICommand<ResponseData>;
|
|
|
|
public class CompleteOrderCommandHandler(
|
|
IRedemptionOrderRepository redemptionOrderRepository) : ICommandHandler<CompleteOrderCommand, ResponseData>
|
|
{
|
|
public async Task<ResponseData> Handle(CompleteOrderCommand request, CancellationToken cancellationToken)
|
|
{
|
|
var order = await redemptionOrderRepository.GetByIdAsync(request.OrderId, cancellationToken);
|
|
|
|
if (order == null)
|
|
throw new KnownException("订单不存在");
|
|
|
|
order.Complete();
|
|
await redemptionOrderRepository.UpdateAsync(order, cancellationToken);
|
|
|
|
return new ResponseData();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 取消订单命令
|
|
/// </summary>
|
|
public record CancelOrderCommand(RedemptionOrderId OrderId, string Reason) : ICommand<ResponseData>;
|
|
|
|
public class CancelOrderCommandHandler(
|
|
IRedemptionOrderRepository redemptionOrderRepository) : ICommandHandler<CancelOrderCommand, ResponseData>
|
|
{
|
|
public async Task<ResponseData> Handle(CancelOrderCommand request, CancellationToken cancellationToken)
|
|
{
|
|
var order = await redemptionOrderRepository.GetByIdAsync(request.OrderId, cancellationToken);
|
|
|
|
if (order == null)
|
|
throw new KnownException("订单不存在");
|
|
|
|
order.Cancel(request.Reason);
|
|
await redemptionOrderRepository.UpdateAsync(order, cancellationToken);
|
|
|
|
return new ResponseData();
|
|
}
|
|
}
|