379 lines
12 KiB
C#
379 lines
12 KiB
C#
using YD_Event.Application.Coupon.Dtos;
|
||
using YD_Event.Core.Entities;
|
||
|
||
namespace YD_Event.Application.Coupon;
|
||
|
||
/// <summary>
|
||
/// 优惠券服务
|
||
/// </summary>
|
||
public class CouponService : BaseService<YD_Event.Core.Entities.Coupon>, ITransient
|
||
{
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Coupon> _repository;
|
||
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
|
||
private readonly ISqlSugarRepository<CouponRedemptionCode> _redemptionCodeRepository;
|
||
private readonly ISqlSugarRepository<UserCoupon> _userCouponRepository;
|
||
private readonly IIdGenerator _idGenerator;
|
||
|
||
public CouponService(
|
||
ISqlSugarRepository<YD_Event.Core.Entities.Coupon> repository,
|
||
ISqlSugarRepository<EventInfo> eventRepository,
|
||
ISqlSugarRepository<CouponRedemptionCode> redemptionCodeRepository,
|
||
ISqlSugarRepository<UserCoupon> userCouponRepository,
|
||
IIdGenerator idGenerator) : base(repository)
|
||
{
|
||
_repository = repository;
|
||
_eventRepository = eventRepository;
|
||
_redemptionCodeRepository = redemptionCodeRepository;
|
||
_userCouponRepository = userCouponRepository;
|
||
_idGenerator = idGenerator;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 优惠券分页查询
|
||
/// </summary>
|
||
[DisplayName("优惠券分页查询")]
|
||
public async Task<CouponPageResult> Page(CouponQueryInput input)
|
||
{
|
||
var query = _repository.AsQueryable()
|
||
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), x => x.Name.Contains(input.Name))
|
||
.WhereIF(input.Type.HasValue, x => x.Type == input.Type.Value)
|
||
.OrderByDescending(x => x.CreatedTime);
|
||
|
||
// 先获取实体列表
|
||
var entityList = await query.ToPagedListAsync(input);
|
||
|
||
// 获取所有相关的赛事ID
|
||
var allEventIds = entityList.Rows
|
||
.Where(x => !string.IsNullOrEmpty(x.EventIds))
|
||
.SelectMany(x => x.EventIds.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||
.Select(long.Parse)
|
||
.Distinct()
|
||
.ToList();
|
||
|
||
var eventMap = new Dictionary<long, string>();
|
||
if (allEventIds.Any())
|
||
{
|
||
var events = await _eventRepository.AsQueryable()
|
||
.Where(x => allEventIds.Contains(x.Id))
|
||
.ToListAsync();
|
||
eventMap = events.ToDictionary(x => x.Id, x => x.EventName);
|
||
}
|
||
|
||
// 转换并填充赛事名称
|
||
var resultList = entityList.Rows.Select(item =>
|
||
{
|
||
var eventIdList = string.IsNullOrEmpty(item.EventIds)
|
||
? new List<long>()
|
||
: item.EventIds.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(long.Parse).ToList();
|
||
|
||
var eventNames = "通用";
|
||
if (eventIdList.Any())
|
||
{
|
||
var names = eventIdList
|
||
.Where(id => eventMap.ContainsKey(id))
|
||
.Select(id => eventMap[id]);
|
||
eventNames = string.Join(",", names);
|
||
}
|
||
|
||
return new CouponPageOutput
|
||
{
|
||
Id = item.Id,
|
||
Name = item.Name,
|
||
Type = item.Type,
|
||
Value = item.Value,
|
||
StartTime = item.StartTime,
|
||
EndTime = item.EndTime,
|
||
TotalQuantity = item.TotalQuantity,
|
||
ClaimedQuantity = item.ClaimedQuantity,
|
||
UsedQuantity = item.UsedQuantity,
|
||
CreatedTime = item.CreatedTime,
|
||
EventIds = eventIdList,
|
||
EventNames = eventNames
|
||
};
|
||
}).ToList();
|
||
|
||
return new CouponPageResult
|
||
{
|
||
PageNo = entityList.PageNo,
|
||
PageSize = entityList.PageSize,
|
||
Total = entityList.Total,
|
||
Rows = resultList,
|
||
TotalPage = (int)Math.Ceiling((double)entityList.Total / entityList.PageSize)
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加优惠券
|
||
/// </summary>
|
||
[DisplayName("添加优惠券")]
|
||
public async Task Add(AddCouponInput input)
|
||
{
|
||
var entity = input.Adapt<YD_Event.Core.Entities.Coupon>();
|
||
entity.Id = _idGenerator.NextId();
|
||
|
||
// 处理赛事ID集合
|
||
if (input.EventIds != null && input.EventIds.Any())
|
||
{
|
||
entity.EventIds = string.Join(",", input.EventIds);
|
||
}
|
||
|
||
await _repository.InsertAsync(entity);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新优惠券
|
||
/// </summary>
|
||
[DisplayName("更新优惠券")]
|
||
public async Task Update(UpdateCouponInput input)
|
||
{
|
||
var entity = await _repository.GetByIdAsync(input.Id);
|
||
if (entity == null) throw Oops.Bah("优惠券不存在");
|
||
|
||
// 使用Mapster进行属性映射
|
||
input.Adapt(entity);
|
||
|
||
// 处理赛事ID集合
|
||
if (input.EventIds != null && input.EventIds.Any())
|
||
{
|
||
entity.EventIds = string.Join(",", input.EventIds);
|
||
}
|
||
else
|
||
{
|
||
entity.EventIds = null;
|
||
}
|
||
|
||
await _repository.UpdateAsync(entity);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除优惠券
|
||
/// </summary>
|
||
[DisplayName("删除优惠券")]
|
||
public async Task Delete(List<long> ids)
|
||
{
|
||
if (ids == null || !ids.Any()) return;
|
||
await _repository.DeleteAsync(x => ids.Contains(x.Id));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成单个兑换码(兼容旧接口)
|
||
/// </summary>
|
||
[DisplayName("生成兑换码")]
|
||
[UnitOfWork]
|
||
[Obsolete("请使用 GenerateRedemptionCodes 方法")]
|
||
public async Task<string> GenerateCode(long id)
|
||
{
|
||
var result = await GenerateRedemptionCodes(new GenerateRedemptionCodesInput { CouponId = id, Count = 1 });
|
||
return result.FirstOrDefault()?.Code ?? string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量生成兑换码
|
||
/// </summary>
|
||
[DisplayName("批量生成兑换码")]
|
||
[UnitOfWork]
|
||
public async Task<List<RedemptionCodeOutput>> GenerateRedemptionCodes(GenerateRedemptionCodesInput input)
|
||
{
|
||
// 验证优惠券是否存在
|
||
var coupon = await _repository.GetByIdAsync(input.CouponId);
|
||
if (coupon == null) throw Oops.Bah("优惠券不存在");
|
||
|
||
var generatedCodes = new List<CouponRedemptionCode>();
|
||
const int maxRetryPerCode = 5;
|
||
|
||
for (int i = 0; i < input.Count; i++)
|
||
{
|
||
string newCode;
|
||
int retryCount = 0;
|
||
bool codeIsUnique = false;
|
||
|
||
do
|
||
{
|
||
// 生成12位大写字母数字组合的兑换码
|
||
newCode = GenerateRandomCode(12);
|
||
retryCount++;
|
||
|
||
// 检查是否已存在
|
||
var exists = await _redemptionCodeRepository.AsQueryable()
|
||
.Where(x => x.Code == newCode)
|
||
.AnyAsync();
|
||
|
||
if (!exists)
|
||
{
|
||
codeIsUnique = true;
|
||
}
|
||
} while (!codeIsUnique && retryCount < maxRetryPerCode);
|
||
|
||
if (!codeIsUnique)
|
||
{
|
||
throw Oops.Bah($"生成第 {i + 1} 个兑换码时失败,请重试");
|
||
}
|
||
|
||
var redemptionCode = new CouponRedemptionCode
|
||
{
|
||
Id = _idGenerator.NextId(),
|
||
CouponId = input.CouponId,
|
||
Code = newCode,
|
||
Status = 0,
|
||
DeleteMark = false
|
||
};
|
||
|
||
generatedCodes.Add(redemptionCode);
|
||
}
|
||
|
||
// 批量插入
|
||
await _redemptionCodeRepository.InsertRangeAsync(generatedCodes);
|
||
|
||
// 返回结果
|
||
return generatedCodes.Select(x => new RedemptionCodeOutput
|
||
{
|
||
Id = x.Id,
|
||
CouponId = x.CouponId,
|
||
Code = x.Code,
|
||
Status = x.Status,
|
||
CreatedTime = x.CreatedTime
|
||
}).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 兑换码分页查询
|
||
/// </summary>
|
||
[DisplayName("兑换码分页查询")]
|
||
public async Task<PageResult<RedemptionCodeOutput>> GetRedemptionCodePage(RedemptionCodeQueryInput input)
|
||
{
|
||
var query = _redemptionCodeRepository.AsQueryable()
|
||
.WhereIF(input.CouponId.HasValue, x => x.CouponId == input.CouponId.Value)
|
||
.WhereIF(input.Status.HasValue, x => x.Status == input.Status.Value)
|
||
.WhereIF(!string.IsNullOrWhiteSpace(input.Code), x => x.Code.Contains(input.Code))
|
||
.OrderByDescending(x => x.CreatedTime);
|
||
|
||
var pagedList = await query.ToPagedListAsync(input);
|
||
|
||
return new PageResult<RedemptionCodeOutput>
|
||
{
|
||
PageNo = pagedList.PageNo,
|
||
PageSize = pagedList.PageSize,
|
||
Total = pagedList.Total,
|
||
Rows = pagedList.Rows.Select(x => new RedemptionCodeOutput
|
||
{
|
||
Id = x.Id,
|
||
CouponId = x.CouponId,
|
||
Code = x.Code,
|
||
Status = x.Status,
|
||
UsedTime = x.UsedTime,
|
||
UserCouponId = x.UserCouponId,
|
||
CreatedTime = x.CreatedTime
|
||
}).ToList()
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用兑换码
|
||
/// </summary>
|
||
[DisplayName("使用兑换码")]
|
||
[UnitOfWork]
|
||
public async Task<long> UseRedemptionCode(UseRedemptionCodeInput input)
|
||
{
|
||
// 查找兑换码
|
||
var redemptionCode = await _redemptionCodeRepository.AsQueryable()
|
||
.Where(x => x.Code == input.Code)
|
||
.FirstAsync();
|
||
|
||
if (redemptionCode == null)
|
||
{
|
||
throw Oops.Bah("兑换码不存在");
|
||
}
|
||
|
||
// 检查是否已使用
|
||
if (redemptionCode.Status == 1)
|
||
{
|
||
throw Oops.Bah("该兑换码已被使用");
|
||
}
|
||
|
||
// 检查优惠券是否有效
|
||
var coupon = await _repository.GetByIdAsync(redemptionCode.CouponId);
|
||
if (coupon == null)
|
||
{
|
||
throw Oops.Bah("关联优惠券不存在");
|
||
}
|
||
|
||
if (coupon.Status != AvailabilityStatus.Enable)
|
||
{
|
||
throw Oops.Bah("优惠券已停用");
|
||
}
|
||
|
||
// 检查优惠券有效期
|
||
var now = DateTime.Now;
|
||
if (coupon.StartTime.HasValue && now < coupon.StartTime.Value)
|
||
{
|
||
throw Oops.Bah("优惠券尚未到使用时间");
|
||
}
|
||
if (coupon.EndTime.HasValue && now > coupon.EndTime.Value)
|
||
{
|
||
throw Oops.Bah("优惠券已过期");
|
||
}
|
||
|
||
// 检查库存
|
||
if (coupon.TotalQuantity > 0 && coupon.ClaimedQuantity >= coupon.TotalQuantity)
|
||
{
|
||
throw Oops.Bah("优惠券已领完");
|
||
}
|
||
|
||
// 创建用户优惠券记录
|
||
var userCoupon = new UserCoupon
|
||
{
|
||
Id = _idGenerator.NextId(),
|
||
UserId = input.UserId ?? 0,
|
||
CouponId = coupon.Id,
|
||
ClaimTime = now,
|
||
Status = 0,
|
||
RedemptionCodeId = redemptionCode.Id,
|
||
RedemptionCode = redemptionCode.Code,
|
||
DeleteMark = false
|
||
};
|
||
|
||
await _userCouponRepository.InsertAsync(userCoupon);
|
||
|
||
// 更新兑换码状态
|
||
redemptionCode.Status = 1;
|
||
redemptionCode.UsedTime = now;
|
||
redemptionCode.UserCouponId = userCoupon.Id;
|
||
await _redemptionCodeRepository.UpdateAsync(redemptionCode);
|
||
|
||
// 更新优惠券领取数量
|
||
coupon.ClaimedQuantity++;
|
||
await _repository.UpdateAsync(coupon);
|
||
|
||
return userCoupon.Id;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成随机兑换码
|
||
/// </summary>
|
||
private string GenerateRandomCode(int length)
|
||
{
|
||
const string chars = "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
|
||
var random = new Random();
|
||
return new string(Enumerable.Repeat(chars, length)
|
||
.Select(s => s[random.Next(s.Length)]).ToArray());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取赛事列表(下拉选择用)
|
||
/// </summary>
|
||
[DisplayName("获取赛事列表")]
|
||
public async Task<List<EventSelectOutput>> GetEventList()
|
||
{
|
||
// 这里可以根据需要过滤赛事状态
|
||
return await _eventRepository.AsQueryable()
|
||
.Where(x => x.DeleteMark == false)
|
||
.OrderByDescending(x => x.CreatedTime)
|
||
.Select(x => new EventSelectOutput
|
||
{
|
||
Id = x.Id,
|
||
Name = x.EventName
|
||
})
|
||
.ToListAsync();
|
||
}
|
||
}
|