276 lines
12 KiB
C#
276 lines
12 KiB
C#
using SqlSugar;
|
||
using YD_Event.Application.Auth;
|
||
using YD_Event.Application.Points.Dtos;
|
||
using YD_Event.Core.Entities;
|
||
using YD_Event.Core.Enum;
|
||
|
||
namespace YD_Event.Application.Points;
|
||
|
||
/// <summary>
|
||
/// 积分记录管理
|
||
/// </summary>
|
||
public class PointsRecordService : BaseService<PointsRecord>, ITransient
|
||
{
|
||
private readonly ISqlSugarRepository<PointsRecord> _repository;
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _appUserRepository;
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
|
||
private readonly ISqlSugarRepository<PointsRule> _pointsRuleRepository;
|
||
private readonly AuthManager _authManager;
|
||
|
||
public PointsRecordService(
|
||
ISqlSugarRepository<PointsRecord> repository,
|
||
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> appUserRepository,
|
||
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
|
||
ISqlSugarRepository<PointsRule> pointsRuleRepository,
|
||
AuthManager authManager) : base(repository)
|
||
{
|
||
_repository = repository;
|
||
_appUserRepository = appUserRepository;
|
||
_merchantRepository = merchantRepository;
|
||
_pointsRuleRepository = pointsRuleRepository;
|
||
_authManager = authManager;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取用户当前积分余额 (PC端)
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[DisplayName("获取用户当前积分余额")]
|
||
[HttpGet("balance")]
|
||
public async Task<UserPointsOverviewDto> GetBalance()
|
||
{
|
||
var userId = _authManager.UserId;
|
||
var merchant = await _merchantRepository.GetFirstAsync(m => m.CreatedUserId == userId);
|
||
|
||
// 如果是商户/团队账号,查询商户积分;否则查询个人积分
|
||
decimal balance;
|
||
if (merchant != null)
|
||
{
|
||
balance = await _repository.AsQueryable()
|
||
.Where(x => x.MerchantId == merchant.Id)
|
||
.SumAsync(x => x.Points);
|
||
}
|
||
else
|
||
{
|
||
balance = await _repository.AsQueryable()
|
||
.Where(x => x.UserId == userId && x.MerchantId == null) // 个人积分需排除作为商户成员的记录(如果有)
|
||
.SumAsync(x => x.Points);
|
||
}
|
||
|
||
return new UserPointsOverviewDto { CurrentBalance = balance };
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询积分明细列表 (PC端)
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[DisplayName("查询积分明细列表")]
|
||
[HttpGet("logs")]
|
||
public async Task<PageResult<UserPointsLogDto>> GetLogs([FromQuery] UserPointsQueryInput input)
|
||
{
|
||
var userId = _authManager.UserId;
|
||
var merchant = await _merchantRepository.GetFirstAsync(m => m.CreatedUserId == userId);
|
||
|
||
var query = _repository.AsQueryable()
|
||
.LeftJoin<EventInfo>((pr, e) => pr.EventId == e.Id)
|
||
.LeftJoin<YD_Event.Core.Entities.Project>((pr, e, p) => pr.ProjectId == p.Id)
|
||
.LeftJoin<ProjectGroup>((pr, e, p, pg) => pr.ProjectGroupId == pg.Id)
|
||
.LeftJoin<YD_Event.Core.Entities.AppUser>((pr, e, p, pg, u) => pr.UserId == u.Id)
|
||
.WhereIF(input.Type.HasValue, pr => pr.Type == input.Type)
|
||
.WhereIF(!string.IsNullOrWhiteSpace(input.ActionKeyword), (pr, e) => e.EventName.Contains(input.ActionKeyword) || pr.Remark.Contains(input.ActionKeyword))
|
||
.WhereIF(input.StartTime.HasValue, pr => pr.CreatedTime >= input.StartTime)
|
||
.WhereIF(input.EndTime.HasValue, pr => pr.CreatedTime <= input.EndTime)
|
||
.OrderByDescending(pr => pr.CreatedTime);
|
||
|
||
if (merchant != null)
|
||
{
|
||
// 商户查询:按 MerchantId 过滤
|
||
query = query.Where(pr => pr.MerchantId == merchant.Id);
|
||
}
|
||
else
|
||
{
|
||
// 个人查询:按 UserId 过滤,且排除商户记录
|
||
query = query.Where(pr => pr.UserId == userId && pr.MerchantId == null);
|
||
}
|
||
|
||
var result = await query.Select((pr, e, p, pg, u) => new UserPointsLogDto
|
||
{
|
||
Id = pr.Id,
|
||
Type = pr.Type,
|
||
Action = e.EventName ?? pr.Remark ?? "未知动作",
|
||
ProjectName = p.Name,
|
||
GroupName = pg.GroupName,
|
||
ParticipantName = u.Name, // 显示关联用户的姓名
|
||
Rank = 0, // 暂无法直接关联到名次,需业务侧在写入PointsRecord时记录
|
||
Points = pr.Points,
|
||
CreatedTime = pr.CreatedTime
|
||
})
|
||
.ToPagedListAsync(input);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 积分规则说明接口 (PC端)
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[DisplayName("积分规则说明接口")]
|
||
[HttpGet("rules")]
|
||
[AllowAnonymous]
|
||
public async Task<List<PointsRule>> GetRules()
|
||
{
|
||
// 返回所有启用的积分规则
|
||
return await _pointsRuleRepository.AsQueryable()
|
||
.Where(x => x.Status == AvailabilityStatus.Enable)
|
||
.Includes(x => x.Details) // 包含明细
|
||
.OrderBy(x => x.CreatedTime)
|
||
.ToListAsync();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 积分记录分页查询
|
||
/// </summary>
|
||
[DisplayName("积分记录分页查询")]
|
||
[HttpGet("list")]
|
||
public async Task<PageResult<PointsRecordListOutput>> List([FromQuery] PointsRecordQueryInput input)
|
||
{
|
||
if (input.UserType == 0) // 普通用户
|
||
{
|
||
var query = _appUserRepository.AsQueryable()
|
||
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u =>
|
||
u.Name.Contains(input.Keyword) ||
|
||
u.Mobile.Contains(input.Keyword));
|
||
|
||
// 关联查询积分
|
||
// 注意:这里需要根据时间范围筛选积分记录,还是筛选最后有积分变动的用户?
|
||
// 需求:时间范围筛选。通常指筛选在该时间范围内有积分变动的记录,或者只统计该时间范围内的积分。
|
||
// 但“积分额度”通常指总积分。
|
||
// 如果时间范围筛选是指“只看某段时间的积分情况”,那么积分额度应该是该段时间内的总和。
|
||
// 如果是指“筛选某段时间有活动的用户”,积分额度可能是总积分。
|
||
// 考虑到“积分额度排序”,通常是总积分。
|
||
// 让我们假设:时间范围筛选是指筛选在该时间范围内有积分变动的用户,积分额度为当前总积分。
|
||
// 或者:积分额度 = 该时间范围内的积分总和。
|
||
// 根据管理后台惯例,如果有时间筛选,通常列表显示的是该时间段的数据统计。
|
||
// 让我们采用:积分额度 = 该时间范围内的积分总和(如果没有时间筛选,就是所有时间)。
|
||
|
||
// 使用子查询或 LeftJoin 计算积分
|
||
var result = await query
|
||
.Select(u => new PointsRecordListOutput
|
||
{
|
||
UserId = u.Id,
|
||
UserType = 0,
|
||
Name = u.Name,
|
||
Phone = u.Mobile,
|
||
IdentityInfo = u.IdCardImageUrl, // 身份证图片
|
||
TotalPoints = SqlFunc.Subqueryable<PointsRecord>()
|
||
.Where(p => p.UserId == u.Id && !p.DeleteMark)
|
||
.WhereIF(input.StartTime.HasValue, p => p.CreatedTime >= input.StartTime)
|
||
.WhereIF(input.EndTime.HasValue, p => p.CreatedTime <= input.EndTime)
|
||
.Sum(p => p.Points), // 实时计算
|
||
LastUpdateTime = SqlFunc.Subqueryable<PointsRecord>()
|
||
.Where(p => p.UserId == u.Id && !p.DeleteMark)
|
||
.WhereIF(input.StartTime.HasValue, p => p.CreatedTime >= input.StartTime)
|
||
.WhereIF(input.EndTime.HasValue, p => p.CreatedTime <= input.EndTime)
|
||
.Max(p => p.CreatedTime)
|
||
})
|
||
.MergeTable() // 将上面的 Select 结果作为临时表,以便进行后续的 Where 和 OrderBy
|
||
.WhereIF(input.StartTime.HasValue || input.EndTime.HasValue, x => x.LastUpdateTime != null) // 如果有时间筛选,只显示有记录的用户
|
||
.OrderByIF(!string.IsNullOrWhiteSpace(input.SortPoints), x => x.TotalPoints, input.SortPoints == "asc" ? OrderByType.Asc : OrderByType.Desc)
|
||
.OrderByDescending(x => x.LastUpdateTime) // 默认按更新时间倒序
|
||
.ToPagedListAsync(input);
|
||
|
||
return result;
|
||
}
|
||
else // 单位用户
|
||
{
|
||
var query = _merchantRepository.AsQueryable()
|
||
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), m =>
|
||
m.MerchantName.Contains(input.Keyword) ||
|
||
m.ContactPhone.Contains(input.Keyword));
|
||
|
||
var result = await query
|
||
.Select(m => new PointsRecordListOutput
|
||
{
|
||
UserId = m.Id, // 商户ID
|
||
UserType = 1,
|
||
Name = m.MerchantName,
|
||
Phone = m.ContactPhone,
|
||
IdentityInfo = m.CreditCode, // 统一社会信用代码
|
||
TotalPoints = SqlFunc.Subqueryable<PointsRecord>()
|
||
.Where(p => p.MerchantId == m.Id && !p.DeleteMark)
|
||
.WhereIF(input.StartTime.HasValue, p => p.CreatedTime >= input.StartTime)
|
||
.WhereIF(input.EndTime.HasValue, p => p.CreatedTime <= input.EndTime)
|
||
.Sum(p => p.Points),
|
||
LastUpdateTime = SqlFunc.Subqueryable<PointsRecord>()
|
||
.Where(p => p.MerchantId == m.Id && !p.DeleteMark)
|
||
.WhereIF(input.StartTime.HasValue, p => p.CreatedTime >= input.StartTime)
|
||
.WhereIF(input.EndTime.HasValue, p => p.CreatedTime <= input.EndTime)
|
||
.Max(p => p.CreatedTime)
|
||
})
|
||
.MergeTable()
|
||
.WhereIF(input.StartTime.HasValue || input.EndTime.HasValue, x => x.LastUpdateTime != null)
|
||
.OrderByIF(!string.IsNullOrWhiteSpace(input.SortPoints), x => x.TotalPoints, input.SortPoints == "asc" ? OrderByType.Asc : OrderByType.Desc)
|
||
.OrderByDescending(x => x.LastUpdateTime)
|
||
.ToPagedListAsync(input);
|
||
|
||
return result;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 积分明细查询
|
||
/// </summary>
|
||
[DisplayName("积分明细查询")]
|
||
[HttpGet("detail/{userId}")]
|
||
public async Task<PageResult<PointsDetailOutput>> Detail([FromQuery] PointsDetailQueryInput input)
|
||
{
|
||
var query = _repository.AsQueryable()
|
||
.LeftJoin<EventInfo>((p, e) => p.EventId == e.Id)
|
||
.LeftJoin<YD_Event.Core.Entities.Project>((p, e, pr) => p.ProjectId == pr.Id)
|
||
.LeftJoin<ProjectGroup>((p, e, pr, pg) => p.ProjectGroupId == pg.Id)
|
||
.Where((p, e, pr, pg) => !p.DeleteMark)
|
||
.WhereIF(input.Type.HasValue, (p, e, pr, pg) => p.Type == input.Type)
|
||
.OrderByDescending((p, e, pr, pg) => p.CreatedTime);
|
||
|
||
if (input.UserType == 0) // 普通用户
|
||
{
|
||
query = query.Where((p, e, pr, pg) => p.UserId == input.UserId);
|
||
|
||
return await query
|
||
.Select((p, e, pr, pg) => new PointsDetailOutput
|
||
{
|
||
CreatedTime = p.CreatedTime,
|
||
Type = p.Type,
|
||
Action = e.EventName,
|
||
Item = pr.Name,
|
||
Group = pg.GroupName,
|
||
Points = p.Points,
|
||
Remark = p.Remark
|
||
}).ToPagedListAsync(input);
|
||
|
||
}
|
||
else // 单位用户
|
||
{
|
||
// 单位用户:查询 MerchantId = userId 的记录
|
||
query = query.Where((p, e, pr, pg) => p.MerchantId == input.UserId);
|
||
|
||
return await query
|
||
.LeftJoin<YD_Event.Core.Entities.AppUser>((p, e, pr, pg, u) => p.UserId == u.Id)
|
||
.Select((p, e, pr, pg, u) => new PointsDetailOutput
|
||
{
|
||
CreatedTime = p.CreatedTime,
|
||
Type = p.Type,
|
||
Action = e.EventName,
|
||
Item = pr.Name,
|
||
Group = pg.GroupName,
|
||
Points = p.Points,
|
||
Remark = p.Remark,
|
||
ParticipantName = u.Name, // 参赛人姓名
|
||
ParticipantId = u.Id // 参赛人ID
|
||
}).ToPagedListAsync(input);
|
||
|
||
}
|
||
}
|
||
}
|