using YD_Event.Application.Event.Dtos; using YD_Event.Core.Entities; using MiniExcelLibs; namespace YD_Event.Application.Event; /// /// 赛事管理服务 /// public class EventService : BaseService, ITransient { private readonly ISqlSugarRepository _repository; private readonly ISqlSugarRepository _participantRepository; private readonly ISqlSugarRepository _customFieldRepository; private readonly ISqlSugarRepository _provinceRepository; private readonly ISqlSugarRepository _cityRepository; private readonly ISqlSugarRepository _districtRepository; private readonly ISqlSugarRepository _projectRepository; private readonly ISqlSugarRepository _projectGroupRepository; private readonly ISqlSugarRepository _registrationLimitRepository; private readonly ISqlSugarRepository _projectLimitRepository; private readonly ISqlSugarRepository _feeConfigRepository; private readonly ISqlSugarRepository _registrationRepository; private readonly ISqlSugarRepository _merchantRepository; private readonly IIdGenerator _idGenerator; public EventService( ISqlSugarRepository repository, ISqlSugarRepository participantRepository, ISqlSugarRepository customFieldRepository, ISqlSugarRepository provinceRepository, ISqlSugarRepository cityRepository, ISqlSugarRepository districtRepository, ISqlSugarRepository projectRepository, ISqlSugarRepository projectGroupRepository, ISqlSugarRepository registrationLimitRepository, ISqlSugarRepository projectLimitRepository, ISqlSugarRepository feeConfigRepository, ISqlSugarRepository registrationRepository, ISqlSugarRepository merchantRepository, IIdGenerator idGenerator) : base(repository) { _repository = repository; _participantRepository = participantRepository; _customFieldRepository = customFieldRepository; _provinceRepository = provinceRepository; _cityRepository = cityRepository; _districtRepository = districtRepository; _projectRepository = projectRepository; _projectGroupRepository = projectGroupRepository; _registrationLimitRepository = registrationLimitRepository; _projectLimitRepository = projectLimitRepository; _feeConfigRepository = feeConfigRepository; _registrationRepository = registrationRepository; _merchantRepository = merchantRepository; _idGenerator = idGenerator; } /// /// 分页查询赛事 /// /// /// [DisplayName("分页查询赛事")] public async Task> Page(EventQueryInput input) { var query = _repository.AsQueryable() .WhereIF(!string.IsNullOrWhiteSpace(input.EventName), x => x.EventName.Contains(input.EventName)) .WhereIF(input.EventType.HasValue, x => x.EventType == input.EventType) .WhereIF(input.ChargeMode.HasValue, x => x.ChargeMode == input.ChargeMode) .WhereIF(input.EventStatus.HasValue, x => x.EventStatus == input.EventStatus) .WhereIF(input.RegistrationStartTime.HasValue, x => x.RegistrationStartTime >= input.RegistrationStartTime) .WhereIF(input.RegistrationEndTime.HasValue, x => x.RegistrationEndTime <= input.RegistrationEndTime) .WhereIF(input.MatchStartTime.HasValue, x => x.MatchStartTime >= input.MatchStartTime) .WhereIF(input.MatchEndTime.HasValue, x => x.MatchEndTime <= input.MatchEndTime) .OrderByDescending(x => x.CreatedTime) .Select(x => new EventListOutput { Id = x.Id, EventName = x.EventName, CoverImage = x.CoverImage, EventType = x.EventType, RegistrationType = x.RegistrationType, ChargeMode = x.ChargeMode, EventStatus = x.EventStatus, RegistrationStartTime = x.RegistrationStartTime, RegistrationEndTime = x.RegistrationEndTime, MatchStartTime = x.MatchStartTime, MatchEndTime = x.MatchEndTime, // AccountName 暂未关联 }); return await query.ToPagedListAsync(input); } /// /// 获取赛事详情 /// /// /// [DisplayName("获取赛事详情")] public async Task GetDetail(long id) { var eventInfo = await _repository.GetByIdAsync(id); if (eventInfo == null) throw new InvalidOperationException("赛事不存在"); var output = eventInfo.Adapt(); // 1. 获取参与单位 var participant = await _participantRepository.GetFirstAsync(x => x.EventId == id); if (participant != null) { output.Participant = participant.Adapt(); } // 2. 获取自定义字段 var customFields = await _customFieldRepository.GetListAsync(x => x.EventId == id); output.CustomFields = customFields.OrderBy(x => x.Sort).Select(x => x.FieldName).ToList(); // 3. 获取区域名称 if (output.ProvinceId > 0) { var p = await _provinceRepository.GetFirstAsync(x => x.ProvinceId == output.ProvinceId); output.ProvinceName = p?.ProvinceName; } if (output.CityId > 0) { var c = await _cityRepository.GetFirstAsync(x => x.CityId == output.CityId); output.CityName = c?.CityName; } if (output.DistrictId > 0) { var d = await _districtRepository.GetFirstAsync(x => x.DistrictId == output.DistrictId); output.DistrictName = d?.DistrictName; } // 4. 获取项目列表 (Step 3) var projects = await _projectRepository.GetListAsync(x => x.EventId == id); if (projects.Any()) { var projectIds = projects.Select(x => x.Id).ToList(); var groups = await _projectGroupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId)); var projectLimits = await _projectLimitRepository.GetListAsync(x => projectIds.Contains(x.ProjectId)); output.Projects = projects.Select(p => { var dto = p.Adapt(); dto.Groups = groups.Where(g => g.ProjectId == p.Id).Select(g => g.Adapt()).ToList(); var limit = projectLimits.FirstOrDefault(l => l.ProjectId == p.Id); if (limit != null) dto.LimitCount = limit.LimitCount; return dto; }).ToList(); } // 5. 获取报名限制 (Step 3) var registrationLimit = await _registrationLimitRepository.GetFirstAsync(x => x.EventId == id); if (registrationLimit != null) { output.RegistrationLimit = registrationLimit.Adapt(); } // 6. 获取费用配置 (Step 4) var feeConfig = await _feeConfigRepository.GetFirstAsync(x => x.EventId == id); if (feeConfig != null) { output.FeeConfig = feeConfig.Adapt(); // 获取商户详情 if (feeConfig.MerchantId > 0) { var merchant = await _merchantRepository.GetByIdAsync(feeConfig.MerchantId); if (merchant != null) { output.FeeConfig.MerchantName = merchant.MerchantName; output.FeeConfig.CreditCode = merchant.CreditCode; output.FeeConfig.BankName = merchant.BankName; output.FeeConfig.AccountNumber = merchant.AccountNumber; } } } return output; } /// /// 新增赛事 /// /// /// [UnitOfWork] [DisplayName("新增赛事")] public async Task Add(AddEventInput input) { var eventInfo = input.Adapt(); eventInfo.Id = _idGenerator.NewLong(); eventInfo.CreatedTime = DateTime.Now; eventInfo.Status = AvailabilityStatus.Enable; var now = DateTime.Now; if (now < eventInfo.RegistrationStartTime) eventInfo.EventStatus = EventStatus.Enrolling; else if (now > eventInfo.MatchEndTime) eventInfo.EventStatus = EventStatus.Ended; else if (now >= eventInfo.MatchStartTime) eventInfo.EventStatus = EventStatus.Ongoing; else eventInfo.EventStatus = EventStatus.Enrolling; await _repository.InsertAsync(eventInfo); // 1. 保存参与单位 if (input.Participant != null) { var participant = input.Participant.Adapt(); participant.Id = _idGenerator.NewLong(); participant.EventId = eventInfo.Id; await _participantRepository.InsertAsync(participant); } // 2. 保存自定义字段 if (input.CustomFields != null && input.CustomFields.Any()) { var fields = input.CustomFields.Select((name, index) => new EventCustomField { Id = _idGenerator.NewLong(), EventId = eventInfo.Id, FieldName = name, Sort = index }).ToList(); await _customFieldRepository.InsertRangeAsync(fields); } // 3. 保存项目信息 (Step 3) if (input.Projects != null && input.Projects.Any()) { foreach (var projInput in input.Projects) { var project = projInput.Adapt(); project.Id = _idGenerator.NewLong(); project.EventId = eventInfo.Id; project.Status = AvailabilityStatus.Enable; project.CreatedTime = DateTime.Now; await _projectRepository.InsertAsync(project); // 保存项目分组 if (projInput.Groups != null && projInput.Groups.Any()) { var groups = projInput.Groups.Select(g => { var group = g.Adapt(); group.Id = _idGenerator.NewLong(); group.ProjectId = project.Id; return group; }).ToList(); await _projectGroupRepository.InsertRangeAsync(groups); } // 保存项目报名限制 (Step 3 - Project Limit) if (projInput.LimitCount.HasValue) { var projLimit = new EventProjectRegistrationLimit { Id = _idGenerator.NewLong(), EventId = eventInfo.Id, ProjectId = project.Id, LimitCount = projInput.LimitCount.Value }; await _projectLimitRepository.InsertAsync(projLimit); } } } // 4. 保存报名限制 (Step 3 - Individual/Team Limit) if (input.RegistrationLimit != null) { var limit = input.RegistrationLimit.Adapt(); limit.Id = _idGenerator.NewLong(); limit.EventId = eventInfo.Id; await _registrationLimitRepository.InsertAsync(limit); } // 5. 保存费用配置 (Step 4) if (input.FeeConfig != null) { var feeConfig = input.FeeConfig.Adapt(); feeConfig.Id = _idGenerator.NewLong(); feeConfig.EventId = eventInfo.Id; await _feeConfigRepository.InsertAsync(feeConfig); } } /// /// 更新赛事 /// /// /// [UnitOfWork] [DisplayName("更新赛事")] public async Task Update(UpdateEventInput input) { var eventInfo = await _repository.GetByIdAsync(input.Id); if (eventInfo == null) throw new InvalidOperationException("赛事不存在"); var oldEventName = eventInfo.EventName; input.Adapt(eventInfo); eventInfo.EventName = oldEventName; // 赛事名称不允许修改 eventInfo.UpdatedTime = DateTime.Now; // 更新状态逻辑(可选,每次更新重新计算) var now = DateTime.Now; if (now > eventInfo.MatchEndTime) eventInfo.EventStatus = EventStatus.Ended; else if (now >= eventInfo.MatchStartTime) eventInfo.EventStatus = EventStatus.Ongoing; else eventInfo.EventStatus = EventStatus.Enrolling; await _repository.UpdateAsync(eventInfo); // 1. 更新参与单位 await _participantRepository.DeleteAsync(x => x.EventId == input.Id); if (input.Participant != null) { var participant = input.Participant.Adapt(); participant.Id = _idGenerator.NewLong(); participant.EventId = eventInfo.Id; await _participantRepository.InsertAsync(participant); } // 2. 更新自定义字段 await _customFieldRepository.DeleteAsync(x => x.EventId == input.Id); if (input.CustomFields != null && input.CustomFields.Any()) { var fields = input.CustomFields.Select((name, index) => new EventCustomField { Id = _idGenerator.NewLong(), EventId = eventInfo.Id, FieldName = name, Sort = index }).ToList(); await _customFieldRepository.InsertRangeAsync(fields); } // 3. 更新项目信息 // 策略:全量删除旧的,插入新的。简单可靠。 // 获取旧项目ID列表,用于级联删除分组和限制 var oldProjects = await _projectRepository.GetListAsync(x => x.EventId == input.Id); var oldProjectIds = oldProjects.Select(x => x.Id).ToList(); if (oldProjectIds.Any()) { await _projectGroupRepository.DeleteAsync(x => oldProjectIds.Contains(x.ProjectId)); await _projectLimitRepository.DeleteAsync(x => oldProjectIds.Contains(x.ProjectId)); await _projectRepository.DeleteAsync(x => x.EventId == input.Id); } if (input.Projects != null && input.Projects.Any()) { foreach (var projInput in input.Projects) { var project = projInput.Adapt(); project.Id = _idGenerator.NewLong(); project.EventId = eventInfo.Id; project.Status = AvailabilityStatus.Enable; project.CreatedTime = DateTime.Now; await _projectRepository.InsertAsync(project); if (projInput.Groups != null && projInput.Groups.Any()) { var groups = projInput.Groups.Select(g => { var group = g.Adapt(); group.Id = _idGenerator.NewLong(); group.ProjectId = project.Id; return group; }).ToList(); await _projectGroupRepository.InsertRangeAsync(groups); } if (projInput.LimitCount.HasValue) { var projLimit = new EventProjectRegistrationLimit { Id = _idGenerator.NewLong(), EventId = eventInfo.Id, ProjectId = project.Id, LimitCount = projInput.LimitCount.Value }; await _projectLimitRepository.InsertAsync(projLimit); } } } // 4. 更新报名限制 await _registrationLimitRepository.DeleteAsync(x => x.EventId == input.Id); if (input.RegistrationLimit != null) { var limit = input.RegistrationLimit.Adapt(); limit.Id = _idGenerator.NewLong(); limit.EventId = eventInfo.Id; await _registrationLimitRepository.InsertAsync(limit); } // 5. 更新费用配置 await _feeConfigRepository.DeleteAsync(x => x.EventId == input.Id); if (input.FeeConfig != null) { var feeConfig = input.FeeConfig.Adapt(); feeConfig.Id = _idGenerator.NewLong(); feeConfig.EventId = eventInfo.Id; await _feeConfigRepository.InsertAsync(feeConfig); } } /// /// 导出赛事 /// /// /// [DisplayName("导出赛事")] public async Task Export(EventExportInput input) { var query = _repository.AsQueryable() .LeftJoin((e, p) => e.Id == p.EventId) .LeftJoin((e, p, f) => e.Id == f.EventId) .LeftJoin((e, p, f, m) => f.MerchantId == m.Id) .WhereIF(!string.IsNullOrWhiteSpace(input.EventName), e => e.EventName.Contains(input.EventName)) .WhereIF(input.EventType.HasValue, e => e.EventType == input.EventType) .WhereIF(input.ChargeMode.HasValue, e => e.ChargeMode == input.ChargeMode) .WhereIF(input.EventStatus.HasValue, e => e.EventStatus == input.EventStatus) .WhereIF(input.RegistrationStartTime.HasValue, e => e.RegistrationStartTime >= input.RegistrationStartTime) .WhereIF(input.RegistrationEndTime.HasValue, e => e.RegistrationEndTime <= input.RegistrationEndTime) .WhereIF(input.MatchStartTime.HasValue, e => e.MatchStartTime >= input.MatchStartTime) .WhereIF(input.MatchEndTime.HasValue, e => e.MatchEndTime <= input.MatchEndTime) .Select((e, p, f, m) => new { e.EventName, e.EventType, e.ChargeMode, e.RegistrationType, e.EventStatus, e.RegistrationStartTime, e.RegistrationEndTime, e.MaterialUploadEndTime, e.MatchStartTime, e.MatchEndTime, e.DetailAddress, p.HostName, p.OrganizerName, p.CoOrganizerName, p.SponsorName, MerchantName = m.MerchantName }); var list = await query.ToListAsync(); var exportData = new List>(); foreach (var item in list) { var row = new Dictionary(); var fields = input.ExportFields ?? new List(); bool all = fields.Count == 0; if (all || fields.Contains("赛事名称")) row["赛事名称"] = item.EventName; if (all || fields.Contains("赛事类型")) row["赛事类型"] = item.EventType.ToString(); // TODO: Enum Description if (all || fields.Contains("收费模式")) row["收费模式"] = item.ChargeMode.ToString(); if (all || fields.Contains("收款商户")) row["收款商户"] = item.MerchantName; if (all || fields.Contains("报名方式")) row["报名方式"] = item.RegistrationType.ToString(); if (all || fields.Contains("赛事状态")) row["赛事状态"] = item.EventStatus.ToString(); if (all || fields.Contains("报名起止时间")) row["报名起止时间"] = $"{item.RegistrationStartTime:yyyy-MM-dd HH:mm} - {item.RegistrationEndTime:yyyy-MM-dd HH:mm}"; if (all || fields.Contains("上传素材截至时间")) row["上传素材截至时间"] = item.MaterialUploadEndTime?.ToString("yyyy-MM-dd HH:mm"); if (all || fields.Contains("比赛起止时间")) row["比赛起止时间"] = $"{item.MatchStartTime:yyyy-MM-dd HH:mm} - {item.MatchEndTime:yyyy-MM-dd HH:mm}"; if (all || fields.Contains("赛事地址")) row["赛事地址"] = item.DetailAddress; if (all || fields.Contains("主办单位")) row["主办单位"] = item.HostName; if (all || fields.Contains("承办单位")) row["承办单位"] = item.OrganizerName; if (all || fields.Contains("协办单位")) row["协办单位"] = item.CoOrganizerName; if (all || fields.Contains("赞助单位")) row["赞助单位"] = item.SponsorName; exportData.Add(row); } using var stream = new MemoryStream(); stream.SaveAs(exportData); return stream.ToArray(); } /// /// 删除赛事 /// /// /// [DisplayName("删除赛事")] public override async Task Delete(KeyDto dto) { if (await _registrationRepository.IsAnyAsync(x => x.EventId == dto.Id)) { throw new InvalidOperationException("已有人员报名此赛事,暂不支持删除"); } // 软删除 EventInfo await base.Delete(dto); // 关联表物理删除或保留?通常保留。如果需要清理: // await _participantRepository.DeleteAsync(x => x.EventId == dto.Id); // await _customFieldRepository.DeleteAsync(x => x.EventId == dto.Id); } }