2026-04-10 14:29:41 +08:00

1132 lines
47 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 YD_Event.Application.Event.Dtos;
using YD_Event.Application.Project.Dtos;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using Easy.Core;
using MiniExcelLibs;
using SqlSugar;
using Mapster;
namespace YD_Event.Application.Event;
/// <summary>
/// 赛事管理服务
/// </summary>
public class EventService : BaseService<EventInfo>, ITransient
{
private readonly ISqlSugarRepository<EventInfo> _repository;
private readonly ISqlSugarRepository<EventUnit> _unitRepository;
private readonly ISqlSugarRepository<SysProvince> _provinceRepository;
private readonly ISqlSugarRepository<SysCity> _cityRepository;
private readonly ISqlSugarRepository<SysDistrict> _districtRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
private readonly ISqlSugarRepository<EventProjectGroup> _eventProjectGroupRepository;
private readonly ISqlSugarRepository<ProjectGroup> _projectGroupRepository;
private readonly ISqlSugarRepository<ProjectType> _projectTypeRepository;
private readonly ISqlSugarRepository<EventRegistrationLimit> _registrationLimitRepository;
private readonly ISqlSugarRepository<EventProjectRegistrationLimit> _projectLimitRepository;
private readonly ISqlSugarRepository<EventFeeConfig> _feeConfigRepository;
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
private readonly IIdGenerator _idGenerator;
private readonly YD_Event.Application.Menu.SysMenuService _sysMenuService;
private readonly YD_Event.Application.Auth.AuthManager _authManager;
public EventService(
ISqlSugarRepository<EventInfo> repository,
ISqlSugarRepository<EventUnit> unitRepository,
ISqlSugarRepository<SysProvince> provinceRepository,
ISqlSugarRepository<SysCity> cityRepository,
ISqlSugarRepository<SysDistrict> districtRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
ISqlSugarRepository<EventProject> eventProjectRepository,
ISqlSugarRepository<EventProjectGroup> eventProjectGroupRepository,
ISqlSugarRepository<ProjectGroup> projectGroupRepository,
ISqlSugarRepository<ProjectType> projectTypeRepository,
ISqlSugarRepository<EventRegistrationLimit> registrationLimitRepository,
ISqlSugarRepository<EventProjectRegistrationLimit> projectLimitRepository,
ISqlSugarRepository<EventFeeConfig> feeConfigRepository,
ISqlSugarRepository<EventRegistration> registrationRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
IIdGenerator idGenerator,
YD_Event.Application.Menu.SysMenuService sysMenuService,
YD_Event.Application.Auth.AuthManager authManager) : base(repository)
{
_repository = repository;
_unitRepository = unitRepository;
_provinceRepository = provinceRepository;
_cityRepository = cityRepository;
_districtRepository = districtRepository;
_projectRepository = projectRepository;
_eventProjectRepository = eventProjectRepository;
_eventProjectGroupRepository = eventProjectGroupRepository;
_projectGroupRepository = projectGroupRepository;
_projectTypeRepository = projectTypeRepository;
_registrationLimitRepository = registrationLimitRepository;
_projectLimitRepository = projectLimitRepository;
_feeConfigRepository = feeConfigRepository;
_registrationRepository = registrationRepository;
_merchantRepository = merchantRepository;
_idGenerator = idGenerator;
_sysMenuService = sysMenuService;
_authManager = authManager;
}
/// <summary>
/// 分页查询赛事
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("分页查询赛事")]
public async Task<PageResult<EventListOutput>> Page(EventQueryInput input)
{
var query = _repository.AsQueryable()
.LeftJoin<EventFeeConfig>((x, f) => x.Id == f.EventId)
.LeftJoin<YD_Event.Core.Entities.Merchant>((x, f, m) => x.MerchantId == m.Id)
.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);
// 数据权限过滤
if (!_authManager.IsSuperAdmin)
{
// 检查全部数据权限
var hasAllData = await _sysMenuService.CheckPermission("event:data:all");
if (!hasAllData)
{
// 检查个人数据权限
var hasPersonalData = await _sysMenuService.CheckPermission("event:data:personal");
if (hasPersonalData)
{
var userId = _authManager.UserId;
query = query.Where(x => x.CreatedUserId == userId);
}
else
{
// 无权限,返回空
return new PageResult<EventListOutput>();
}
}
}
var resultQuery = query.OrderByDescending(x => x.CreatedTime)
.Select((x, f, m) => new EventListOutput
{
Id = x.Id,
EventName = x.EventName,
CoverImage = x.CoverImage,
EventType = x.EventType,
RegistrationType = x.RegistrationType,
ChargeMode = x.ChargeMode,
MerchantName = m.MerchantName,
EventStatus = x.EventStatus,
RegistrationStartTime = x.RegistrationStartTime,
RegistrationEndTime = x.RegistrationEndTime,
MatchStartTime = x.MatchStartTime,
MatchEndTime = x.MatchEndTime,
QrCodeUrl = x.QrCodeUrl
});
return await resultQuery.ToPagedListAsync(input);
}
/// <summary>
/// 获取赛事详情
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[DisplayName("获取赛事详情")]
public async Task<EventDetailOutput> GetDetail(long id)
{
var eventInfo = await _repository.GetByIdAsync(id);
if (eventInfo == null) throw new InvalidOperationException("赛事不存在");
var output = eventInfo.Adapt<EventDetailOutput>();
// 1. 获取参与单位
var units = await _unitRepository.GetListAsync(x => x.EventId == id);
if (units.Any())
{
output.Units = units.OrderBy(x => x.Sort).Select(x => x.Adapt<EventUnitDto>()).ToList();
}
// 3. 获取区域名称和Code
if (output.ProvinceId > 0)
{
var p = await _provinceRepository.GetFirstAsync(x => x.Id == output.ProvinceId);
output.ProvinceCode = p?.ProvinceCode;
output.ProvinceName = p?.ProvinceName;
}
if (output.CityId > 0)
{
var c = await _cityRepository.GetFirstAsync(x => x.Id == output.CityId);
output.CityCode = c?.CityCode;
output.CityName = c?.CityName;
}
if (output.DistrictId > 0)
{
var d = await _districtRepository.GetFirstAsync(x => x.Id == output.DistrictId);
output.DistrictCode = d?.DistrictCode;
output.DistrictName = d?.DistrictName;
}
// 4. 获取项目列表 (Step 3)
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == id);
if (eventProjects.Any())
{
var eventProjectIds = eventProjects.Select(x => x.Id).ToList();
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => eventProjectIds.Contains(x.EventProjectId));
var projectLimits = await _projectLimitRepository.GetListAsync(x => eventProjectIds.Contains(x.ProjectId));
// 获取关联的原始项目信息
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
var baseProjectGroups = await _projectGroupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
// 获取项目类型信息
var typeIds = baseProjects.Select(x => x.ProjectTypeId).Distinct().ToList();
var types = await _projectTypeRepository.GetListAsync(x => typeIds.Contains(x.Id));
output.Projects = eventProjects.Select(ep =>
{
var baseProj = baseProjects.FirstOrDefault(p => p.Id == ep.ProjectId);
var dto = new EventProjectInput
{
Id = ep.Id,
ProjectId = ep.ProjectId,
GenderType = ep.GenderType,
Fee = ep.Price,
Name = baseProj?.Name,
ProjectTypeId = baseProj?.ProjectTypeId,
DurationMinutes = baseProj?.DurationMinutes,
DurationSeconds = baseProj?.DurationSeconds
};
if (baseProj != null)
{
var type = types.FirstOrDefault(x => x.Id == baseProj.ProjectTypeId);
if (type != null)
{
dto.ProjectTypeName = type.Name;
}
}
var epGroups = eventProjectGroups.Where(g => g.EventProjectId == ep.Id).ToList();
dto.Groups = epGroups.Select(eg =>
{
var baseGroup = baseProjectGroups.FirstOrDefault(bg => bg.Id == eg.ProjectGroupId);
return new EventProjectGroupInput
{
Id = eg.Id,
ProjectGroupId = eg.ProjectGroupId,
GroupName = eg.GroupName ?? baseGroup?.GroupName,
BirthDateStart = eg.BirthDateStart != DateTime.MinValue ? eg.BirthDateStart : baseGroup?.BirthDateStart,
BirthDateEnd = eg.BirthDateEnd != DateTime.MinValue ? eg.BirthDateEnd : baseGroup?.BirthDateEnd,
Gender = eg.Gender
};
}).ToList();
var limit = projectLimits.FirstOrDefault(l => l.ProjectId == ep.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<EventRegistrationLimitInput>();
}
// 6. 获取费用配置 (Step 4)
var feeConfig = await _feeConfigRepository.GetFirstAsync(x => x.EventId == id);
if (feeConfig != null)
{
output.FeeConfig = feeConfig.Adapt<EventFeeConfigInput>();
// 获取商户详情
if (eventInfo.MerchantId.HasValue && eventInfo.MerchantId.Value > 0)
{
var merchant = await _merchantRepository.GetByIdAsync(eventInfo.MerchantId.Value);
if (merchant != null)
{
output.FeeConfig.MerchantName = merchant.MerchantName;
output.FeeConfig.CreditCode = merchant.CreditCode;
output.FeeConfig.BankName = merchant.BankName;
output.FeeConfig.AccountNumber = merchant.AccountNumber;
output.FeeConfig.MerchantId = merchant.Id;
output.FeeConfig.BusinessLicenseUrl = merchant.BusinessLicenseUrl;
}
}
// 从 EventProject 表中读取项目价格并填充到 ProjectPrices
if (eventProjects.Any())
{
output.FeeConfig.ProjectPrices = new Dictionary<long, decimal>();
foreach (var ep in eventProjects)
{
output.FeeConfig.ProjectPrices[ep.Id] = ep.Price;
}
}
}
return output;
}
/// <summary>
/// 验证赛事时间逻辑
/// </summary>
private void ValidateEventTime(DateTime registrationStartTime, DateTime registrationEndTime,
DateTime matchStartTime, DateTime matchEndTime)
{
if (registrationEndTime <= registrationStartTime)
{
throw Oops.Bah("报名结束时间必须晚于报名开始时间");
}
if (matchEndTime <= matchStartTime)
{
throw Oops.Bah("比赛结束时间必须晚于比赛开始时间");
}
if (matchStartTime <= registrationStartTime)
{
throw Oops.Bah("比赛开始时间必须晚于报名开始时间");
}
if (matchStartTime <= registrationEndTime)
{
throw Oops.Bah("比赛开始时间必须晚于报名结束时间");
}
if (matchEndTime <= registrationEndTime)
{
throw Oops.Bah("比赛结束时间必须晚于报名结束时间");
}
}
/// <summary>
/// 新增赛事
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[UnitOfWork]
[DisplayName("新增赛事")]
public async Task Add(AddEventInput input)
{
// 验证赛事时间逻辑
ValidateEventTime(
input.RegistrationStartTime,
input.RegistrationEndTime,
input.MatchStartTime,
input.MatchEndTime
);
// 从EventDetailOutput继承自AddEventInput,所以需要特殊处理一下
// 先获取EventInfo,但先复制除了Id字段
var provinceId = 0;
var cityId = 0;
var districtId = 0;
// 先把输入中的regionIds当作Code处理
if (input.ProvinceId > 0)
{
// 尝试通过Code找到对应的Id
var p = await _provinceRepository.GetFirstAsync(x => x.ProvinceCode == input.ProvinceId.ToString());
if (p != null) provinceId = (int)p.Id;
}
if (input.CityId > 0)
{
var c = await _cityRepository.GetFirstAsync(x => x.CityCode == input.CityId.ToString());
if (c != null) cityId = (int)c.Id;
}
if (input.DistrictId > 0)
{
var d = await _districtRepository.GetFirstAsync(x => x.DistrictCode == input.DistrictId.ToString());
if (d != null) districtId = (int)d.Id;
}
var eventInfo = input.Adapt<EventInfo>();
eventInfo.ProvinceId = provinceId;
eventInfo.CityId = cityId;
eventInfo.DistrictId = districtId;
eventInfo.Id = _idGenerator.NewLong();
eventInfo.CreatedTime = DateTime.Now;
eventInfo.Status = AvailabilityStatus.Enable;
// 保存商户ID到EventInfo表
if (input.FeeConfig != null)
{
eventInfo.MerchantId = input.FeeConfig.MerchantId;
}
// 自动生成二维码URL (模拟)
eventInfo.QrCodeUrl = $"/pages/event/detail?id={eventInfo.Id}";
var now = DateTime.Now;
eventInfo.EventStatus = EventStatusUpdateService.CalculateEventStatus(eventInfo, now);
await _repository.InsertAsync(eventInfo);
// 1. 保存参与单位
if (input.Units != null && input.Units.Any())
{
var units = input.Units.Select(u => {
var entity = u.Adapt<EventUnit>();
entity.Id = _idGenerator.NewLong();
entity.EventId = eventInfo.Id;
return entity;
}).ToList();
await _unitRepository.InsertRangeAsync(units);
}
// 2. 自定义字段 (JSON) - Already in eventInfo
var projectFees = new List<ProjectFeeInput>();
// 3. 保存项目信息 (Step 3)
if (input.Projects != null && input.Projects.Any())
{
foreach (var projInput in input.Projects)
{
var eventProject = new EventProject
{
Id = _idGenerator.NewLong(),
EventId = eventInfo.Id,
ProjectId = projInput.ProjectId,
GenderType = projInput.GenderType,
Price = projInput.Fee,
CreatedTime = DateTime.Now
};
await _eventProjectRepository.InsertAsync(eventProject);
projectFees.Add(new ProjectFeeInput { ProjectId = eventProject.Id, Fee = projInput.Fee });
// 保存项目分组
if (projInput.Groups != null && projInput.Groups.Any())
{
var groups = projInput.Groups.Select(g =>
{
var group = new EventProjectGroup
{
Id = _idGenerator.NewLong(),
EventId = eventInfo.Id,
EventProjectId = eventProject.Id,
ProjectId = projInput.ProjectId,
ProjectGroupId = g.ProjectGroupId,
GroupName = g.GroupName ?? "",
BirthDateStart = g.BirthDateStart ?? DateTime.MinValue,
BirthDateEnd = g.BirthDateEnd ?? DateTime.MinValue,
Gender = g.Gender ?? Gender.All,
CreatedTime = DateTime.Now
};
return group;
}).ToList();
await _eventProjectGroupRepository.InsertRangeAsync(groups);
}
// 保存项目报名限制 (Step 3 - Project Limit)
if (projInput.LimitCount.HasValue)
{
var projLimit = new EventProjectRegistrationLimit
{
Id = _idGenerator.NewLong(),
EventId = eventInfo.Id,
ProjectId = eventProject.Id,
LimitCount = projInput.LimitCount.Value,
CreatedTime = DateTime.Now
};
await _projectLimitRepository.InsertAsync(projLimit);
}
}
}
// 4. 保存报名限制 (Step 3 - Individual/Team Limit)
if (input.RegistrationLimit != null)
{
var limit = input.RegistrationLimit.Adapt<EventRegistrationLimit>();
limit.Id = _idGenerator.NewLong();
limit.EventId = eventInfo.Id;
await _registrationLimitRepository.InsertAsync(limit);
}
// 5. 保存费用配置 (Step 4)
if (input.FeeConfig != null)
{
// 满足 SaveFeeConfig 的前置状态要求
eventInfo.ProcessStatus = ProcessStatus.ProjectConfigCompleted;
await _repository.UpdateAsync(eventInfo);
var feeConfigInput = new SaveFeeConfigInput
{
EventId = eventInfo.Id,
FeeConfig = input.FeeConfig,
ProjectFees = projectFees
};
await SaveFeeConfig(feeConfigInput);
}
}
/// <summary>
/// 更新赛事
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[UnitOfWork]
[DisplayName("更新赛事")]
public async Task Update(UpdateEventInput input)
{
// 验证赛事时间逻辑
ValidateEventTime(
input.RegistrationStartTime,
input.RegistrationEndTime,
input.MatchStartTime,
input.MatchEndTime
);
var eventInfo = await _repository.GetByIdAsync(input.Id);
if (eventInfo == null) throw new InvalidOperationException("赛事不存在");
var oldEventName = eventInfo.EventName;
// 将前端传过来的Code转换为对应的Id
var provinceId = eventInfo.ProvinceId;
var cityId = eventInfo.CityId;
var districtId = eventInfo.DistrictId;
if (input.ProvinceId > 0)
{
var p = await _provinceRepository.GetFirstAsync(x => x.ProvinceCode == input.ProvinceId.ToString());
if (p != null) provinceId = (int)p.Id;
}
if (input.CityId > 0)
{
var c = await _cityRepository.GetFirstAsync(x => x.CityCode == input.CityId.ToString());
if (c != null) cityId = (int)c.Id;
}
if (input.DistrictId > 0)
{
var d = await _districtRepository.GetFirstAsync(x => x.DistrictCode == input.DistrictId.ToString());
if (d != null) districtId = (int)d.Id;
}
input.Adapt(eventInfo);
eventInfo.EventName = oldEventName; // 赛事名称不允许修改
eventInfo.ProvinceId = provinceId;
eventInfo.CityId = cityId;
eventInfo.DistrictId = districtId;
// 保存商户ID到EventInfo表
if (input.FeeConfig != null)
{
eventInfo.MerchantId = input.FeeConfig.MerchantId;
}
eventInfo.UpdatedTime = DateTime.Now;
// 更新状态逻辑
var now = DateTime.Now;
eventInfo.EventStatus = EventStatusUpdateService.CalculateEventStatus(eventInfo, now);
await _repository.UpdateAsync(eventInfo);
// 1. 更新参与单位
await _unitRepository.DeleteAsync(x => x.EventId == input.Id);
if (input.Units != null && input.Units.Any())
{
var units = input.Units.Select(u => {
var entity = u.Adapt<EventUnit>();
entity.Id = _idGenerator.NewLong();
entity.EventId = eventInfo.Id;
return entity;
}).ToList();
await _unitRepository.InsertRangeAsync(units);
}
// 2. 更新自定义字段 (JSON) - Updated in eventInfo
var projectFees = new List<ProjectFeeInput>();
// 3. 更新项目信息
var oldEventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.Id);
var oldEventProjectIds = oldEventProjects.Select(x => x.Id).ToList();
if (oldEventProjectIds.Any())
{
await _eventProjectGroupRepository.DeleteAsync(x => oldEventProjectIds.Contains(x.EventProjectId));
await _projectLimitRepository.DeleteAsync(x => oldEventProjectIds.Contains(x.ProjectId));
await _eventProjectRepository.DeleteAsync(x => x.EventId == input.Id);
}
if (input.Projects != null && input.Projects.Any())
{
foreach (var projInput in input.Projects)
{
var eventProject = new EventProject
{
Id = _idGenerator.NewLong(),
EventId = eventInfo.Id,
ProjectId = projInput.ProjectId,
GenderType = projInput.GenderType,
Price = projInput.Fee,
CreatedTime = DateTime.Now
};
await _eventProjectRepository.InsertAsync(eventProject);
projectFees.Add(new ProjectFeeInput { ProjectId = eventProject.Id, Fee = projInput.Fee });
if (projInput.Groups != null && projInput.Groups.Any())
{
var groups = projInput.Groups.Select(g =>
{
var group = new EventProjectGroup
{
Id = _idGenerator.NewLong(),
EventId = eventInfo.Id,
EventProjectId = eventProject.Id,
ProjectId = projInput.ProjectId,
ProjectGroupId = g.ProjectGroupId,
GroupName = g.GroupName ?? "",
BirthDateStart = g.BirthDateStart ?? DateTime.MinValue,
BirthDateEnd = g.BirthDateEnd ?? DateTime.MinValue,
Gender = g.Gender ?? Gender.All,
CreatedTime = DateTime.Now
};
return group;
}).ToList();
await _eventProjectGroupRepository.InsertRangeAsync(groups);
}
if (projInput.LimitCount.HasValue)
{
var projLimit = new EventProjectRegistrationLimit
{
Id = _idGenerator.NewLong(),
EventId = eventInfo.Id,
ProjectId = eventProject.Id,
LimitCount = projInput.LimitCount.Value,
CreatedTime = DateTime.Now
};
await _projectLimitRepository.InsertAsync(projLimit);
}
}
}
// 4. 更新报名限制
await _registrationLimitRepository.DeleteAsync(x => x.EventId == input.Id);
if (input.RegistrationLimit != null)
{
var limit = input.RegistrationLimit.Adapt<EventRegistrationLimit>();
limit.Id = _idGenerator.NewLong();
limit.EventId = eventInfo.Id;
await _registrationLimitRepository.InsertAsync(limit);
}
// 5. 更新费用配置
if (input.FeeConfig != null)
{
// 满足 SaveFeeConfig 的前置状态要求
if (eventInfo.ProcessStatus < ProcessStatus.ProjectConfigCompleted)
{
eventInfo.ProcessStatus = ProcessStatus.ProjectConfigCompleted;
await _repository.UpdateAsync(eventInfo);
}
var feeConfigInput = new SaveFeeConfigInput
{
EventId = eventInfo.Id,
FeeConfig = input.FeeConfig,
ProjectFees = projectFees
};
await SaveFeeConfig(feeConfigInput);
}
}
/// <summary>
/// 导出赛事
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("导出赛事")]
public async Task<byte[]> Export(EventExportInput input)
{
var query = _repository.AsQueryable()
.LeftJoin<EventFeeConfig>((e, f) => e.Id == f.EventId)
.LeftJoin<YD_Event.Core.Entities.Merchant>((e, f, m) => e.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, f, m) => new EventExportData
{
Id = e.Id,
EventName = e.EventName,
EventType = e.EventType,
ChargeMode = e.ChargeMode,
RegistrationType = e.RegistrationType,
EventStatus = e.EventStatus,
RegistrationStartTime = e.RegistrationStartTime,
RegistrationEndTime = e.RegistrationEndTime,
MaterialUploadEndTime = e.MaterialUploadEndTime,
MatchStartTime = e.MatchStartTime,
MatchEndTime = e.MatchEndTime,
DetailAddress = e.DetailAddress,
MerchantName = m.MerchantName
});
var list = await query.ToListAsync();
var eventIds = list.Select(x => x.Id).ToList();
var units = await _unitRepository.GetListAsync(x => eventIds.Contains(x.EventId));
// 使用 EPPlus 进行导出
OfficeOpenXml.ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
using var package = new OfficeOpenXml.ExcelPackage();
var worksheet = package.Workbook.Worksheets.Add("赛事列表");
// 定义所有可用的列
var allColumns = new List<(string Header, Func<EventExportData, string> GetValue)>
{
("赛事名称", item => item.EventName ?? ""),
("赛事类型", item => GetEnumDescription(item.EventType)),
("收费模式", item => GetEnumDescription(item.ChargeMode)),
("收款商户", item => item.MerchantName ?? ""),
("报名方式", item => GetEnumDescription(item.RegistrationType)),
("赛事状态", item => GetEnumDescription(item.EventStatus)),
("报名起止时间", item => $"{item.RegistrationStartTime:yyyy-MM-dd HH:mm} - {item.RegistrationEndTime:yyyy-MM-dd HH:mm}"),
("上传素材截至时间", item => item.MaterialUploadEndTime.HasValue ? item.MaterialUploadEndTime.Value.ToString("yyyy-MM-dd HH:mm") : ""),
("比赛起止时间", item => $"{item.MatchStartTime:yyyy-MM-dd HH:mm} - {item.MatchEndTime:yyyy-MM-dd HH:mm}"),
("赛事地址", item => item.DetailAddress ?? ""),
("主办单位", item => ""), // 后面填充
("承办单位", item => ""), // 后面填充
("协办单位", item => ""), // 后面填充
("赞助单位", item => "") // 后面填充
};
// 确定要导出的列
var selectedFields = input.ExportFields ?? new List<string>();
bool exportAll = selectedFields.Count == 0;
var columnsToExport = allColumns
.Where(col => exportAll || selectedFields.Contains(col.Header))
.ToList();
if (columnsToExport.Count == 0)
{
columnsToExport = allColumns;
}
// 写入表头
for (int i = 0; i < columnsToExport.Count; i++)
{
worksheet.Cells[1, i + 1].Value = columnsToExport[i].Header;
}
// 设置表头样式
using (var headerRange = worksheet.Cells[1, 1, 1, columnsToExport.Count])
{
headerRange.Style.Font.Bold = true;
headerRange.Style.Font.Size = 11;
headerRange.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
headerRange.Style.VerticalAlignment = OfficeOpenXml.Style.ExcelVerticalAlignment.Center;
headerRange.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
headerRange.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightBlue);
}
// 写入数据行
for (int rowIndex = 0; rowIndex < list.Count; rowIndex++)
{
var item = list[rowIndex];
var eventUnits = units.Where(u => u.EventId == item.Id).ToList();
var hostNames = string.Join(",", eventUnits.Where(u => u.UnitType == UnitType.Host).Select(u => u.UnitName));
var organizerNames = string.Join(",", eventUnits.Where(u => u.UnitType == UnitType.Organizer).Select(u => u.UnitName));
var coOrganizerNames = string.Join(",", eventUnits.Where(u => u.UnitType == UnitType.CoOrganizer).Select(u => u.UnitName));
var sponsorNames = string.Join(",", eventUnits.Where(u => u.UnitType == UnitType.Sponsor).Select(u => u.UnitName));
for (int colIndex = 0; colIndex < columnsToExport.Count; colIndex++)
{
var column = columnsToExport[colIndex];
string cellValue = column.GetValue(item);
// 补充参与单位数据
if (column.Header == "主办单位") cellValue = hostNames;
else if (column.Header == "承办单位") cellValue = organizerNames;
else if (column.Header == "协办单位") cellValue = coOrganizerNames;
else if (column.Header == "赞助单位") cellValue = sponsorNames;
worksheet.Cells[rowIndex + 2, colIndex + 1].Value = cellValue;
}
}
// 自动调整列宽
worksheet.Cells[worksheet.Dimension.Address].AutoFitColumns();
return await package.GetAsByteArrayAsync();
}
/// <summary>
/// 获取枚举描述
/// </summary>
private string GetEnumDescription(object enumValue)
{
if (enumValue == null) return "";
var type = enumValue.GetType();
if (!type.IsEnum) return enumValue.ToString();
var fieldInfo = type.GetField(enumValue.ToString());
if (fieldInfo == null) return enumValue.ToString();
var descriptionAttribute = (System.ComponentModel.DescriptionAttribute)
System.Attribute.GetCustomAttribute(fieldInfo, typeof(System.ComponentModel.DescriptionAttribute));
return descriptionAttribute != null ? descriptionAttribute.Description : enumValue.ToString();
}
/// <summary>
/// 删除赛事
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[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 _unitRepository.DeleteAsync(x => x.EventId == dto.Id);
}
/// <summary>
/// 阶段1:保存基本信息
/// </summary>
/// <param name="input"></param>
/// <returns>EventId</returns>
[UnitOfWork]
[DisplayName("保存基本信息")]
public async Task<long> SaveBasicInfo(SaveBasicInfoInput input)
{
// 验证赛事时间逻辑
ValidateEventTime(
input.RegistrationStartTime,
input.RegistrationEndTime,
input.MatchStartTime,
input.MatchEndTime
);
EventInfo eventInfo;
if (input.EventId.HasValue && input.EventId.Value > 0)
{
eventInfo = await _repository.GetByIdAsync(input.EventId.Value);
if (eventInfo == null) throw new InvalidOperationException("赛事不存在");
input.Adapt(eventInfo);
eventInfo.UpdatedTime = DateTime.Now;
}
else
{
eventInfo = input.Adapt<EventInfo>();
eventInfo.Id = _idGenerator.NewLong();
eventInfo.CreatedTime = DateTime.Now;
eventInfo.Status = AvailabilityStatus.Enable; // 默认启用
eventInfo.ProcessStatus = ProcessStatus.BasicInfoCompleted;
// 初始状态计算
var now = DateTime.Now;
eventInfo.EventStatus = EventStatusUpdateService.CalculateEventStatus(eventInfo, now);
await _repository.InsertAsync(eventInfo);
}
// 更新状态
if (eventInfo.ProcessStatus == ProcessStatus.NotStarted)
{
eventInfo.ProcessStatus = ProcessStatus.BasicInfoCompleted;
}
await _repository.UpdateAsync(eventInfo);
return eventInfo.Id;
}
/// <summary>
/// 阶段2:保存参与单位
/// </summary>
/// <param name="input"></param>
[UnitOfWork]
[DisplayName("保存参与单位")]
public async Task SaveUnits(SaveUnitsInput input)
{
var eventInfo = await _repository.GetByIdAsync(input.EventId);
if (eventInfo == null) throw new InvalidOperationException("赛事不存在");
if (eventInfo.ProcessStatus < ProcessStatus.BasicInfoCompleted)
{
throw new InvalidOperationException("请先完成基本信息配置");
}
// 全量更新单位
await _unitRepository.DeleteAsync(x => x.EventId == input.EventId);
if (input.Units != null && input.Units.Any())
{
var units = input.Units.Select(u => {
var entity = u.Adapt<EventUnit>();
entity.Id = _idGenerator.NewLong();
entity.EventId = input.EventId;
return entity;
}).ToList();
await _unitRepository.InsertRangeAsync(units);
}
// 更新流程状态
if (eventInfo.ProcessStatus < ProcessStatus.ParticipantsCompleted)
{
eventInfo.ProcessStatus = ProcessStatus.ParticipantsCompleted;
await _repository.UpdateAsync(eventInfo);
}
}
/// <summary>
/// 阶段3:保存项目与限报配置
/// </summary>
/// <param name="input"></param>
[UnitOfWork]
[DisplayName("保存项目与限报配置")]
public async Task SaveProjects(SaveProjectsInput input)
{
var eventInfo = await _repository.GetByIdAsync(input.EventId);
if (eventInfo == null) throw new InvalidOperationException("赛事不存在");
if (eventInfo.ProcessStatus < ProcessStatus.ParticipantsCompleted)
{
throw new InvalidOperationException("请先完成参与单位配置");
}
// 1. 更新项目信息 (全量替换)
var oldEventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId);
var oldEventProjectIds = oldEventProjects.Select(x => x.Id).ToList();
if (oldEventProjectIds.Any())
{
await _eventProjectGroupRepository.DeleteAsync(x => oldEventProjectIds.Contains(x.EventProjectId));
await _projectLimitRepository.DeleteAsync(x => oldEventProjectIds.Contains(x.ProjectId));
await _eventProjectRepository.DeleteAsync(x => x.EventId == input.EventId);
}
if (input.Projects != null && input.Projects.Any())
{
foreach (var projInput in input.Projects)
{
var eventProject = new EventProject
{
Id = _idGenerator.NewLong(),
EventId = input.EventId,
ProjectId = projInput.ProjectId,
GenderType = projInput.GenderType,
Price = projInput.Fee,
CreatedTime = DateTime.Now
};
await _eventProjectRepository.InsertAsync(eventProject);
if (projInput.Groups != null && projInput.Groups.Any())
{
var groups = projInput.Groups.Select(g =>
{
var group = new EventProjectGroup
{
Id = _idGenerator.NewLong(),
EventId = input.EventId,
EventProjectId = eventProject.Id,
ProjectId = projInput.ProjectId,
ProjectGroupId = g.ProjectGroupId,
GroupName = g.GroupName ?? "",
BirthDateStart = g.BirthDateStart ?? DateTime.MinValue,
BirthDateEnd = g.BirthDateEnd ?? DateTime.MinValue,
Gender = g.Gender ?? Gender.All,
CreatedTime = DateTime.Now
};
return group;
}).ToList();
await _eventProjectGroupRepository.InsertRangeAsync(groups);
}
if (projInput.LimitCount.HasValue)
{
var projLimit = new EventProjectRegistrationLimit
{
Id = _idGenerator.NewLong(),
EventId = input.EventId,
ProjectId = eventProject.Id,
LimitCount = projInput.LimitCount.Value,
CreatedTime = DateTime.Now
};
await _projectLimitRepository.InsertAsync(projLimit);
}
}
}
// 2. 更新报名限制
await _registrationLimitRepository.DeleteAsync(x => x.EventId == input.EventId);
if (input.RegistrationLimit != null)
{
var limit = input.RegistrationLimit.Adapt<EventRegistrationLimit>();
limit.Id = _idGenerator.NewLong();
limit.EventId = input.EventId;
await _registrationLimitRepository.InsertAsync(limit);
}
// 更新流程状态
if (eventInfo.ProcessStatus < ProcessStatus.ProjectConfigCompleted)
{
eventInfo.ProcessStatus = ProcessStatus.ProjectConfigCompleted;
await _repository.UpdateAsync(eventInfo);
}
}
/// <summary>
/// 阶段4:保存费用与账户配置
/// </summary>
/// <param name="input"></param>
/// <returns>二维码URL</returns>
[UnitOfWork]
[DisplayName("保存费用与账户配置")]
public async Task<string> SaveFeeConfig(SaveFeeConfigInput input)
{
var eventInfo = await _repository.GetByIdAsync(input.EventId);
if (eventInfo == null) throw new InvalidOperationException("赛事不存在");
if (eventInfo.ProcessStatus < ProcessStatus.ProjectConfigCompleted)
{
throw new InvalidOperationException("请先完成项目配置");
}
// 验证非免费模式下的必填参数
if (input.FeeConfig != null && input.FeeConfig.ChargeMode != ChargeMode.Free)
{
// 验证收款商户
if (!input.FeeConfig.MerchantId.HasValue || input.FeeConfig.MerchantId.Value <= 0)
{
throw new InvalidOperationException("收费模式为非免费时,必须选择收款商户");
}
// 验证定价(赛事)和单价(赛事)模式下的价格
if (input.FeeConfig.ChargeMode == ChargeMode.FixedPrice || input.FeeConfig.ChargeMode == ChargeMode.UnitPriceEvent)
{
if (input.FeeConfig.Price <= 0)
{
throw new InvalidOperationException("收费模式为“定价(赛事)”或“单价(赛事)”时,价格必须大于0");
}
// 格式验证:最多保留两位小数
if (!AmountHelper.IsValidAmount(input.FeeConfig.Price))
{
throw new InvalidOperationException("价格格式不正确,最多保留两位小数");
}
}
}
// 验证单价(项目)模式下的项目单价
if (input.FeeConfig != null && input.FeeConfig.ChargeMode == ChargeMode.UnitPriceProject)
{
if (input.ProjectFees == null || !input.ProjectFees.Any())
{
throw new InvalidOperationException("收费模式为“单价(项目)”时,必须提供项目单价信息");
}
var projectIds = input.ProjectFees.Select(x => x.ProjectId).ToList();
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId && projectIds.Contains(x.Id));
foreach (var feeInput in input.ProjectFees)
{
if (feeInput.Fee <= 0)
{
throw new InvalidOperationException("项目单价必须大于0");
}
// 格式验证:最多保留两位小数
if (!AmountHelper.IsValidAmount(feeInput.Fee))
{
throw new InvalidOperationException("项目单价格式不正确,最多保留两位小数");
}
var proj = eventProjects.FirstOrDefault(p => p.Id == feeInput.ProjectId);
if (proj != null)
{
proj.Price = feeInput.Fee;
proj.UpdatedTime = DateTime.Now;
await _eventProjectRepository.UpdateAsync(proj);
}
}
}
// 更新费用配置
await _feeConfigRepository.DeleteAsync(x => x.EventId == input.EventId);
if (input.FeeConfig != null)
{
var feeConfig = input.FeeConfig.Adapt<EventFeeConfig>();
feeConfig.Id = _idGenerator.NewLong();
feeConfig.EventId = input.EventId;
await _feeConfigRepository.InsertAsync(feeConfig);
// 更新赛事的 ChargeMode
eventInfo.ChargeMode = feeConfig.ChargeMode;
}
// 生成二维码URL
var qrCodeUrl = $"/pages/event/detail?id={eventInfo.Id}";
eventInfo.QrCodeUrl = qrCodeUrl;
// 更新流程状态和赛事状态
eventInfo.ProcessStatus = ProcessStatus.FeeConfigCompleted;
// 重新计算并确认赛事状态
var now = DateTime.Now;
eventInfo.EventStatus = EventStatusUpdateService.CalculateEventStatus(eventInfo, now);
await _repository.UpdateAsync(eventInfo);
return qrCodeUrl;
}
/// <summary>
/// 获取赛事填报进度
/// </summary>
/// <param name="eventId"></param>
/// <returns></returns>
[DisplayName("获取赛事填报进度")]
public async Task<EventProgressOutput> GetProgress(long eventId)
{
var eventInfo = await _repository.GetByIdAsync(eventId);
if (eventInfo == null) throw new InvalidOperationException("赛事不存在");
var completedStages = new List<string>();
if (eventInfo.ProcessStatus >= ProcessStatus.BasicInfoCompleted) completedStages.Add("basic");
if (eventInfo.ProcessStatus >= ProcessStatus.ParticipantsCompleted) completedStages.Add("units");
if (eventInfo.ProcessStatus >= ProcessStatus.ProjectConfigCompleted) completedStages.Add("projects");
if (eventInfo.ProcessStatus >= ProcessStatus.FeeConfigCompleted) completedStages.Add("fee");
return new EventProgressOutput
{
EventId = eventInfo.Id,
ProcessStatus = eventInfo.ProcessStatus,
CompletedStages = completedStages
};
}
}