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; /// /// 积分记录管理 /// public class PointsRecordService : BaseService, ITransient { private readonly ISqlSugarRepository _repository; private readonly ISqlSugarRepository _appUserRepository; private readonly ISqlSugarRepository _pointsRuleRepository; private readonly ISqlSugarRepository _projectRepository; private readonly ISqlSugarClient _db; private readonly AuthManager _authManager; public PointsRecordService( ISqlSugarRepository repository, ISqlSugarRepository appUserRepository, ISqlSugarRepository pointsRuleRepository, ISqlSugarRepository projectRepository, ISqlSugarClient db, AuthManager authManager) : base(repository) { _repository = repository; _appUserRepository = appUserRepository; _pointsRuleRepository = pointsRuleRepository; _projectRepository = projectRepository; _db = db; _authManager = authManager; } /// /// 获取用户当前积分余额 (PC用户) /// /// [DisplayName("获取用户当前积分余额")] [HttpGet("balance")] public async Task GetBalance() { var userId = _authManager.UserId; var certification = await _db.Queryable() .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 }; } /// /// 查询积分明细列表 (PC用户) /// /// /// [DisplayName("查询积分明细列表")] [HttpGet("logs")] public async Task> GetLogs([FromQuery] UserPointsQueryInput input) { var userId = _authManager.UserId; var certification = await _db.Queryable() .FirstAsync(c => c.UserId == userId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark); var query = _repository.AsQueryable() .LeftJoin((pr, e) => pr.EventId == e.Id) .LeftJoin((pr, e, ep) => pr.ProjectId == ep.Id) .LeftJoin((pr, e, ep, p) => ep.ProjectId == p.Id) .LeftJoin((pr, e, ep, p, er) => pr.ResultId == er.Id && !er.DeleteMark) .LeftJoin((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; } /// /// 积分规则说明接口 (PC用户) /// /// [DisplayName("积分规则说明接口")] [HttpGet("rules")] [AllowAnonymous] public async Task> GetRules() { // 返回所有启用的积分规则 return await _pointsRuleRepository.AsQueryable() .Where(x => x.Status == AvailabilityStatus.Enable) .Includes(x => x.Details) // 包含明细 .OrderBy(x => x.CreatedTime) .ToListAsync(); } /// /// 积分记录分页查询 /// [DisplayName("积分记录分页查询")] [HttpGet("list")] public async Task> 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() .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() .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() .InnerJoin((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() .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() .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; } } /// /// 积分明细查询 /// [DisplayName("积分明细查询")] [HttpGet("detail/{userId}")] public async Task> Detail([FromQuery] PointsDetailQueryInput input) { if (input.UserType == 0) // 普通用户 { var query = _repository.AsQueryable() .LeftJoin((p, e) => p.EventId == e.Id) .LeftJoin((p, e, ep) => p.ProjectId == ep.Id) .LeftJoin((p, e, ep, pr) => ep.ProjectId == pr.Id) .LeftJoin((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((p, e) => p.EventId == e.Id) .LeftJoin((p, e, ep) => p.ProjectId == ep.Id) .LeftJoin((p, e, ep, pr) => ep.ProjectId == pr.Id) .LeftJoin((p, e, ep, pr, u) => p.UserId == u.Id && !u.DeleteMark) .LeftJoin((p, e, ep, pr, u, er) => p.ResultId == er.Id && !er.DeleteMark) .LeftJoin((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, 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(); if (projectIds.Any()) { var projectList = await _projectRepository.GetListAsync(p => projectIds.Contains(p.Id)); projects = projectList.ToDictionary(p => p.Id); } // 处理数据 var processedData = new List(); var teamRegistrationItemIds = new List(); // 先处理团队项目,收集需要查询的 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 teamPersonDict = new Dictionary(); if (teamRegistrationItemIds.Any()) { var distinctTeamIds = teamRegistrationItemIds.Distinct().ToList(); // 查询同一 RegistrationItemId 的所有 EventRegistrationPerson,获取姓名 var teamPersons = await _db.Queryable() .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 { PageNo = input.PageNo, PageSize = pageSize, Total = total, Pages = (int)Math.Ceiling((double)total / pageSize), Rows = pagedData }; } } }