YD_Event.Admin/YD_Event.Application/Points/PointsRecordService.cs
2026-05-08 13:36:29 +08:00

404 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<PointsRule> _pointsRuleRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
private readonly ISqlSugarClient _db;
private readonly AuthManager _authManager;
public PointsRecordService(
ISqlSugarRepository<PointsRecord> repository,
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> appUserRepository,
ISqlSugarRepository<PointsRule> pointsRuleRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
ISqlSugarClient db,
AuthManager authManager) : base(repository)
{
_repository = repository;
_appUserRepository = appUserRepository;
_pointsRuleRepository = pointsRuleRepository;
_projectRepository = projectRepository;
_db = db;
_authManager = authManager;
}
/// <summary>
/// 获取用户当前积分余额 (PC用户)
/// </summary>
/// <returns></returns>
[DisplayName("获取用户当前积分余额")]
[HttpGet("balance")]
public async Task<UserPointsOverviewDto> GetBalance()
{
var userId = _authManager.UserId;
var certification = await _db.Queryable<MerchantCertification>()
.FirstAsync(c => c.UserId == userId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark);
decimal balance;
if (certification != null)
{
// 单位认证用户:查询单位认证ID的积分
balance = await _repository.AsQueryable()
.Where(x => x.MerchantCertificationId == certification.Id)
.SumAsync(x => x.Points);
}
else
{
// 个人用户:查询个人积分
balance = await _repository.AsQueryable()
.Where(x => x.UserId == userId && x.MerchantCertificationId == 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 certification = await _db.Queryable<MerchantCertification>()
.FirstAsync(c => c.UserId == userId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark);
var query = _repository.AsQueryable()
.LeftJoin<EventInfo>((pr, e) => pr.EventId == e.Id)
.LeftJoin<YD_Event.Core.Entities.EventProject>((pr, e, ep) => pr.ProjectId == ep.Id)
.LeftJoin<YD_Event.Core.Entities.Project>((pr, e, ep, p) => ep.ProjectId == p.Id)
.LeftJoin<EventResult>((pr, e, ep, p, er) => pr.ResultId == er.Id && !er.DeleteMark)
.LeftJoin<YD_Event.Core.Entities.AppUser>((pr, e, ep, p, er, 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 (certification != null)
{
// 单位认证用户:查询单位认证ID的积分记录
query = query.Where(pr => pr.MerchantCertificationId == certification.Id);
}
else
{
// 个人查询:按 UserId 过滤,且排除单位认证记录
query = query.Where(pr => pr.UserId == userId && pr.MerchantCertificationId == null);
}
var result = await query.Select((pr, e, ep, p, er, u) => new UserPointsLogDto
{
Id = pr.Id,
Type = pr.Type,
Action = e.EventName ?? pr.Remark ?? "未知动作",
ProjectName = p.Name,
GroupName = er.GroupName,
ParticipantName = u.Name, // 显示关联用户的姓名
Rank = er.Rank > 0 ? er.Rank : null, // 使用 EventResult 中的名次
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 && p.MerchantCertificationId == null)
.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 && p.MerchantCertificationId == null)
.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 // 单位用户
{
// 单位用户:查询SysUser,关联MerchantCertification
var query = _db.Queryable<SysUser>()
.InnerJoin<MerchantCertification>((s, m) => s.Id == m.UserId && !m.DeleteMark)
.Where((s, m) => !s.DeleteMark)
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), (s, m) =>
s.Name.Contains(input.Keyword) ||
m.MerchantName.Contains(input.Keyword) ||
m.ContactPhone.Contains(input.Keyword));
var result = await query
.Select((s, m) => new PointsRecordListOutput
{
UserId = m.Id, // 使用MerchantCertificationId作为标识
UserType = 1,
Name = m.MerchantName, // 显示单位名称
Phone = m.ContactPhone,
IdentityInfo = m.CreditCode, // 统一社会信用代码
TotalPoints = SqlFunc.Subqueryable<PointsRecord>()
.Where(p => p.MerchantCertificationId == 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.MerchantCertificationId == 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)
{
if (input.UserType == 0) // 普通用户
{
var query = _repository.AsQueryable()
.LeftJoin<EventInfo>((p, e) => p.EventId == e.Id)
.LeftJoin<EventProject>((p, e, ep) => p.ProjectId == ep.Id)
.LeftJoin<YD_Event.Core.Entities.Project>((p, e, ep, pr) => ep.ProjectId == pr.Id)
.LeftJoin<EventResult>((p, e, ep, pr, er) => p.ResultId == er.Id && !er.DeleteMark)
.Where((p, e, ep, pr, er) => !p.DeleteMark)
.Where((p, e, ep, pr, er) => p.UserId == input.UserId)
.Where((p, e, ep, pr, er) => p.MerchantCertificationId == null)
.WhereIF(input.Type.HasValue, (p, e, ep, pr, er) => p.Type == input.Type)
.OrderByDescending((p, e, ep, pr, er) => p.CreatedTime);
return await query
.Select((p, e, ep, pr, er) => new PointsDetailOutput
{
CreatedTime = p.CreatedTime,
Type = p.Type,
Action = e.EventName,
Item = pr.Name,
Group = er.GroupName,
Points = p.Points,
Remark = p.Remark
}).ToPagedListAsync(input);
}
else // 单位用户
{
// 单位用户:查询该单位认证下所有PointsRecord的积分记录
// input.UserId 在这里是 MerchantCertificationId
var query = _repository.AsQueryable()
.LeftJoin<EventInfo>((p, e) => p.EventId == e.Id)
.LeftJoin<EventProject>((p, e, ep) => p.ProjectId == ep.Id)
.LeftJoin<YD_Event.Core.Entities.Project>((p, e, ep, pr) => ep.ProjectId == pr.Id)
.LeftJoin<YD_Event.Core.Entities.AppUser>((p, e, ep, pr, u) => p.UserId == u.Id && !u.DeleteMark)
.LeftJoin<EventResult>((p, e, ep, pr, u, er) => p.ResultId == er.Id && !er.DeleteMark)
.LeftJoin<EventRegistrationPerson>((p, e, ep, pr, u, er, per) => er.RegistrationPersonId == per.Id && !per.DeleteMark)
.Where((p, e, ep, pr, u, er, per) => !p.DeleteMark)
.Where((p, e, ep, pr, u, er, per) => p.MerchantCertificationId == input.UserId)
.WhereIF(input.Type.HasValue, (p, e, ep, pr, u, er, per) => p.Type == input.Type)
.OrderByDescending((p, e, ep, pr, u, er, per) => p.CreatedTime);
var rawData = await query
.Select((p, e, ep, pr, u, er, per) => new
{
PointRecordId = p.Id,
CreatedTime = p.CreatedTime,
Type = p.Type,
Action = e.EventName,
Item = pr.Name,
ProjectId = pr.Id,
ProjectType = pr.ProjectType,
Group = er.GroupName,
Points = p.Points,
Remark = p.Remark,
ParticipantName = u.Name,
ParticipantId = u.Id,
RegistrationItemId = per.RegistrationItemId
})
.ToListAsync();
// 收集所有项目ID
var projectIds = rawData.Where(d => d.ProjectId > 0).Select(d => d.ProjectId).Distinct().ToList();
// 获取项目信息,判断是否是团队项目
var projects = new Dictionary<long, YD_Event.Core.Entities.Project>();
if (projectIds.Any())
{
var projectList = await _projectRepository.GetListAsync(p => projectIds.Contains(p.Id));
projects = projectList.ToDictionary(p => p.Id);
}
// 处理数据
var processedData = new List<PointsDetailOutput>();
var teamRegistrationItemIds = new List<long>();
// 先处理团队项目,收集需要查询的 RegistrationItemId
foreach (var item in rawData)
{
var isTeamProject = false;
if (item.ProjectId > 0 && projects.TryGetValue(item.ProjectId, out var project))
{
isTeamProject = project.ProjectType == YD_Event.Core.Enum.ProjectTypeEnum.TeamSpeed ||
project.ProjectType == YD_Event.Core.Enum.ProjectTypeEnum.TeamFreestyle;
}
if (isTeamProject)
{
teamRegistrationItemIds.Add(item.RegistrationItemId);
}
}
// 查询团队项目的团队成员
Dictionary<long, string> teamPersonDict = new Dictionary<long, string>();
if (teamRegistrationItemIds.Any())
{
var distinctTeamIds = teamRegistrationItemIds.Distinct().ToList();
// 查询同一 RegistrationItemId 的所有 EventRegistrationPerson,获取姓名
var teamPersons = await _db.Queryable<EventRegistrationPerson>()
.Where(per => distinctTeamIds.Contains(per.RegistrationItemId) && !per.DeleteMark)
.Select(per => new { per.RegistrationItemId, per.Name })
.ToListAsync();
// 按 RegistrationItemId 分组
teamPersonDict = teamPersons
.GroupBy(p => p.RegistrationItemId)
.ToDictionary(
g => g.Key,
g => string.Join(", ", g.Select(p => p.Name).Distinct().OrderBy(n => n))
);
}
// 构建最终输出
foreach (var item in rawData)
{
var isTeamProject = false;
if (item.ProjectId > 0 && projects.TryGetValue(item.ProjectId, out var project))
{
isTeamProject = project.ProjectType == YD_Event.Core.Enum.ProjectTypeEnum.TeamSpeed ||
project.ProjectType == YD_Event.Core.Enum.ProjectTypeEnum.TeamFreestyle;
}
string participantName = item.ParticipantName;
if (isTeamProject)
{
string names;
if (teamPersonDict.TryGetValue(item.RegistrationItemId, out names))
{
participantName = names;
}
}
processedData.Add(new PointsDetailOutput
{
CreatedTime = item.CreatedTime,
Type = item.Type,
Action = item.Action,
Item = item.Item,
Group = item.Group,
Points = item.Points,
Remark = item.Remark,
ParticipantName = participantName,
ParticipantId = item.ParticipantId
});
}
// 重新排序
processedData = processedData.OrderByDescending(x => x.CreatedTime).ToList();
// 手动分页
var total = processedData.Count;
var pageSize = input.PageSize > 0 ? input.PageSize : 10;
var pageNo = input.PageNo > 0 ? input.PageNo - 1 : 0;
var pagedData = processedData.Skip(pageNo * pageSize).Take(pageSize).ToList();
return new PageResult<PointsDetailOutput>
{
PageNo = input.PageNo,
PageSize = pageSize,
Total = total,
Pages = (int)Math.Ceiling((double)total / pageSize),
Rows = pagedData
};
}
}
}