2026-02-08 18:09:05 +08:00
|
|
|
|
using YD_Event.Application.Event.Dtos;
|
2026-03-01 22:32:44 +08:00
|
|
|
|
using YD_Event.Application.Project.Dtos;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
using YD_Event.Core.Entities;
|
2026-03-01 22:32:44 +08:00
|
|
|
|
using YD_Event.Core.Enum;
|
2026-04-10 10:54:47 +08:00
|
|
|
|
using Easy.Core;
|
2026-03-01 22:32:44 +08:00
|
|
|
|
using SqlSugar;
|
2026-03-08 15:46:19 +08:00
|
|
|
|
using Mapster;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
|
|
|
|
|
namespace YD_Event.Application.Event;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 赛事管理服务
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class EventService : BaseService<EventInfo>, ITransient
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly ISqlSugarRepository<EventInfo> _repository;
|
2026-03-08 15:46:19 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventUnit> _unitRepository;
|
2026-03-14 09:30:01 +08:00
|
|
|
|
private readonly ISqlSugarRepository<SysProvince> _provinceRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<SysCity> _cityRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<SysDistrict> _districtRepository;
|
2026-03-01 22:32:44 +08:00
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
|
2026-03-18 16:25:49 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<EventProjectGroup> _eventProjectGroupRepository;
|
2026-03-01 22:32:44 +08:00
|
|
|
|
private readonly ISqlSugarRepository<ProjectGroup> _projectGroupRepository;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
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;
|
2026-02-28 20:59:18 +08:00
|
|
|
|
private readonly YD_Event.Application.Menu.SysMenuService _sysMenuService;
|
|
|
|
|
|
private readonly YD_Event.Application.Auth.AuthManager _authManager;
|
2026-05-15 13:46:58 +08:00
|
|
|
|
private readonly EventRuleSnapshotService _ruleSnapshotService;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
|
|
|
|
|
public EventService(
|
|
|
|
|
|
ISqlSugarRepository<EventInfo> repository,
|
2026-03-08 15:46:19 +08:00
|
|
|
|
ISqlSugarRepository<EventUnit> unitRepository,
|
2026-03-14 09:30:01 +08:00
|
|
|
|
ISqlSugarRepository<SysProvince> provinceRepository,
|
|
|
|
|
|
ISqlSugarRepository<SysCity> cityRepository,
|
|
|
|
|
|
ISqlSugarRepository<SysDistrict> districtRepository,
|
2026-03-01 22:32:44 +08:00
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
|
2026-03-18 16:25:49 +08:00
|
|
|
|
ISqlSugarRepository<EventProject> eventProjectRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventProjectGroup> eventProjectGroupRepository,
|
2026-03-01 22:32:44 +08:00
|
|
|
|
ISqlSugarRepository<ProjectGroup> projectGroupRepository,
|
2026-02-08 18:09:05 +08:00
|
|
|
|
ISqlSugarRepository<EventRegistrationLimit> registrationLimitRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventProjectRegistrationLimit> projectLimitRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventFeeConfig> feeConfigRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventRegistration> registrationRepository,
|
|
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
|
2026-02-28 20:59:18 +08:00
|
|
|
|
IIdGenerator idGenerator,
|
|
|
|
|
|
YD_Event.Application.Menu.SysMenuService sysMenuService,
|
2026-05-15 13:46:58 +08:00
|
|
|
|
YD_Event.Application.Auth.AuthManager authManager,
|
|
|
|
|
|
EventRuleSnapshotService ruleSnapshotService) : base(repository)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
_repository = repository;
|
2026-03-08 15:46:19 +08:00
|
|
|
|
_unitRepository = unitRepository;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
_provinceRepository = provinceRepository;
|
|
|
|
|
|
_cityRepository = cityRepository;
|
|
|
|
|
|
_districtRepository = districtRepository;
|
|
|
|
|
|
_projectRepository = projectRepository;
|
2026-03-18 16:25:49 +08:00
|
|
|
|
_eventProjectRepository = eventProjectRepository;
|
|
|
|
|
|
_eventProjectGroupRepository = eventProjectGroupRepository;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
_projectGroupRepository = projectGroupRepository;
|
|
|
|
|
|
_registrationLimitRepository = registrationLimitRepository;
|
|
|
|
|
|
_projectLimitRepository = projectLimitRepository;
|
|
|
|
|
|
_feeConfigRepository = feeConfigRepository;
|
|
|
|
|
|
_registrationRepository = registrationRepository;
|
|
|
|
|
|
_merchantRepository = merchantRepository;
|
|
|
|
|
|
_idGenerator = idGenerator;
|
2026-02-28 20:59:18 +08:00
|
|
|
|
_sysMenuService = sysMenuService;
|
|
|
|
|
|
_authManager = authManager;
|
2026-05-15 13:46:58 +08:00
|
|
|
|
_ruleSnapshotService = ruleSnapshotService;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 分页查询赛事
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="input"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[DisplayName("分页查询赛事")]
|
|
|
|
|
|
public async Task<PageResult<EventListOutput>> Page(EventQueryInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var query = _repository.AsQueryable()
|
2026-03-08 15:46:19 +08:00
|
|
|
|
.LeftJoin<EventFeeConfig>((x, f) => x.Id == f.EventId)
|
2026-04-01 13:04:41 +08:00
|
|
|
|
.LeftJoin<YD_Event.Core.Entities.Merchant>((x, f, m) => x.MerchantId == m.Id)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
.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)
|
2026-02-28 20:59:18 +08:00
|
|
|
|
.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)
|
2026-03-08 15:46:19 +08:00
|
|
|
|
.Select((x, f, m) => new EventListOutput
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
Id = x.Id,
|
|
|
|
|
|
EventName = x.EventName,
|
|
|
|
|
|
CoverImage = x.CoverImage,
|
|
|
|
|
|
EventType = x.EventType,
|
|
|
|
|
|
RegistrationType = x.RegistrationType,
|
|
|
|
|
|
ChargeMode = x.ChargeMode,
|
2026-03-08 15:46:19 +08:00
|
|
|
|
MerchantName = m.MerchantName,
|
2026-02-08 18:09:05 +08:00
|
|
|
|
EventStatus = x.EventStatus,
|
|
|
|
|
|
RegistrationStartTime = x.RegistrationStartTime,
|
|
|
|
|
|
RegistrationEndTime = x.RegistrationEndTime,
|
|
|
|
|
|
MatchStartTime = x.MatchStartTime,
|
|
|
|
|
|
MatchEndTime = x.MatchEndTime,
|
2026-03-08 15:46:19 +08:00
|
|
|
|
QrCodeUrl = x.QrCodeUrl
|
2026-02-08 18:09:05 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-28 20:59:18 +08:00
|
|
|
|
return await resultQuery.ToPagedListAsync(input);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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>();
|
|
|
|
|
|
|
2026-06-08 17:57:09 +08:00
|
|
|
|
// 检查是否有报名记录,决定是否可以编辑项目(有报名记录时不能编辑项目和组别)
|
|
|
|
|
|
// 排除已取消(PaymentStatus = 4)和软删除的报名记录
|
2026-06-25 20:51:40 +08:00
|
|
|
|
var hasRegistration = await _registrationRepository.IsAnyAsync(r =>
|
|
|
|
|
|
r.EventId == id && !r.DeleteMark &&
|
|
|
|
|
|
((r.PaymentMethod == 2 && r.PaymentStatus == 1) || (r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1))));
|
2026-06-08 17:35:35 +08:00
|
|
|
|
output.CanEditProject = !hasRegistration;
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
// 1. 获取参与单位
|
2026-03-08 15:46:19 +08:00
|
|
|
|
var units = await _unitRepository.GetListAsync(x => x.EventId == id);
|
|
|
|
|
|
if (units.Any())
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-03-08 15:46:19 +08:00
|
|
|
|
output.Units = units.OrderBy(x => x.Sort).Select(x => x.Adapt<EventUnitDto>()).ToList();
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 09:42:10 +08:00
|
|
|
|
// 3. 获取区域名称和Code
|
2026-02-08 18:09:05 +08:00
|
|
|
|
if (output.ProvinceId > 0)
|
|
|
|
|
|
{
|
2026-03-18 00:05:53 +08:00
|
|
|
|
var p = await _provinceRepository.GetFirstAsync(x => x.Id == output.ProvinceId);
|
2026-04-03 09:42:10 +08:00
|
|
|
|
output.ProvinceCode = p?.ProvinceCode;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
output.ProvinceName = p?.ProvinceName;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (output.CityId > 0)
|
|
|
|
|
|
{
|
2026-03-18 00:05:53 +08:00
|
|
|
|
var c = await _cityRepository.GetFirstAsync(x => x.Id == output.CityId);
|
2026-04-03 09:42:10 +08:00
|
|
|
|
output.CityCode = c?.CityCode;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
output.CityName = c?.CityName;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (output.DistrictId > 0)
|
|
|
|
|
|
{
|
2026-03-18 00:05:53 +08:00
|
|
|
|
var d = await _districtRepository.GetFirstAsync(x => x.Id == output.DistrictId);
|
2026-04-03 09:42:10 +08:00
|
|
|
|
output.DistrictCode = d?.DistrictCode;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
output.DistrictName = d?.DistrictName;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 获取项目列表 (Step 3)
|
2026-03-18 16:25:49 +08:00
|
|
|
|
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == id);
|
|
|
|
|
|
if (eventProjects.Any())
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
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));
|
2026-03-01 22:32:44 +08:00
|
|
|
|
|
2026-03-18 16:25:49 +08:00
|
|
|
|
// 获取关联的原始项目信息
|
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
|
|
|
|
output.Projects = eventProjects.Select(ep =>
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
var baseProj = baseProjects.FirstOrDefault(p => p.Id == ep.ProjectId);
|
|
|
|
|
|
var dto = new EventProjectInput
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = ep.Id,
|
|
|
|
|
|
ProjectId = ep.ProjectId,
|
|
|
|
|
|
GenderType = ep.GenderType,
|
2026-04-10 14:29:41 +08:00
|
|
|
|
Fee = ep.Price,
|
2026-03-18 16:25:49 +08:00
|
|
|
|
Name = baseProj?.Name,
|
2026-04-12 16:11:20 +08:00
|
|
|
|
ProjectTypeId = (long?)baseProj?.ProjectType,
|
2026-03-18 16:25:49 +08:00
|
|
|
|
DurationMinutes = baseProj?.DurationMinutes,
|
2026-06-10 13:25:53 +08:00
|
|
|
|
DurationSeconds = baseProj?.DurationSeconds,
|
|
|
|
|
|
// 填充完成人数相关字段
|
|
|
|
|
|
CompletionCountType = baseProj?.CompletionCountType ?? 0,
|
|
|
|
|
|
CompletionCount = baseProj?.CompletionCount,
|
|
|
|
|
|
CompletionCountMin = baseProj?.CompletionCountMin,
|
|
|
|
|
|
CompletionCountMax = baseProj?.CompletionCountMax
|
2026-03-18 16:25:49 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (baseProj != null)
|
2026-03-01 22:32:44 +08:00
|
|
|
|
{
|
2026-04-12 16:11:20 +08:00
|
|
|
|
dto.ProjectTypeName = baseProj.ProjectType.Description();
|
2026-06-10 13:25:53 +08:00
|
|
|
|
// 生成完成人数显示文本
|
|
|
|
|
|
if (baseProj.CompletionCountType == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
dto.CompletionCountDisplay = baseProj.CompletionCount > 0 ? $"{baseProj.CompletionCount}人" : "0人";
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var min = baseProj.CompletionCountMin ?? 0;
|
|
|
|
|
|
var max = baseProj.CompletionCountMax ?? 0;
|
|
|
|
|
|
dto.CompletionCountDisplay = $"{min}-{max}人";
|
|
|
|
|
|
}
|
2026-03-01 22:32:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 16:25:49 +08:00
|
|
|
|
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,
|
2026-04-04 23:31:38 +08:00
|
|
|
|
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
|
2026-03-18 16:25:49 +08:00
|
|
|
|
};
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var limit = projectLimits.FirstOrDefault(l => l.ProjectId == ep.Id);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
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>();
|
2026-04-07 09:04:38 +08:00
|
|
|
|
// 获取商户详情
|
2026-04-01 13:04:41 +08:00
|
|
|
|
if (eventInfo.MerchantId.HasValue && eventInfo.MerchantId.Value > 0)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-04-01 13:04:41 +08:00
|
|
|
|
var merchant = await _merchantRepository.GetByIdAsync(eventInfo.MerchantId.Value);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
if (merchant != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
output.FeeConfig.MerchantName = merchant.MerchantName;
|
|
|
|
|
|
output.FeeConfig.CreditCode = merchant.CreditCode;
|
|
|
|
|
|
output.FeeConfig.BankName = merchant.BankName;
|
|
|
|
|
|
output.FeeConfig.AccountNumber = merchant.AccountNumber;
|
2026-04-07 09:04:38 +08:00
|
|
|
|
output.FeeConfig.MerchantId = merchant.Id;
|
|
|
|
|
|
output.FeeConfig.BusinessLicenseUrl = merchant.BusinessLicenseUrl;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-07 23:52:35 +08:00
|
|
|
|
|
|
|
|
|
|
// 从 EventProject 表中读取项目价格并填充到 ProjectPrices
|
|
|
|
|
|
if (eventProjects.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
output.FeeConfig.ProjectPrices = new Dictionary<long, decimal>();
|
|
|
|
|
|
foreach (var ep in eventProjects)
|
|
|
|
|
|
{
|
2026-04-10 14:29:41 +08:00
|
|
|
|
output.FeeConfig.ProjectPrices[ep.Id] = ep.Price;
|
2026-04-07 23:52:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-15 13:46:58 +08:00
|
|
|
|
// 7. 获取积分规则和退款规则快照,并赋值到原有的规则DTO中
|
|
|
|
|
|
var pointsRuleSnapshot = await _ruleSnapshotService.GetPointsRuleSnapshot(id);
|
|
|
|
|
|
var refundRuleSnapshots = await _ruleSnapshotService.GetRefundRuleSnapshot(id);
|
|
|
|
|
|
|
|
|
|
|
|
if (pointsRuleSnapshot != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
output.PointsRuleDetail = new PointsRuleDto
|
|
|
|
|
|
{
|
|
|
|
|
|
RuleName = pointsRuleSnapshot.RuleName,
|
|
|
|
|
|
ParticipationPoints = pointsRuleSnapshot.ParticipationPoints,
|
|
|
|
|
|
Remark = pointsRuleSnapshot.Remark,
|
|
|
|
|
|
Details = pointsRuleSnapshot.Details.Select(d => new PointsRuleDetailDto
|
|
|
|
|
|
{
|
|
|
|
|
|
StartRank = d.StartRank,
|
|
|
|
|
|
EndRank = d.EndRank,
|
|
|
|
|
|
Points = d.Points
|
|
|
|
|
|
}).ToList()
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (refundRuleSnapshots != null && refundRuleSnapshots.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
output.RefundRules = refundRuleSnapshots.Select(r => new RefundRuleDto
|
|
|
|
|
|
{
|
|
|
|
|
|
DayStart = r.DayStart,
|
|
|
|
|
|
DayEnd = r.DayEnd,
|
|
|
|
|
|
DeductPercentage = r.DeductPercentage,
|
|
|
|
|
|
Remark = r.Remark
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
return output;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 14:13:43 +08:00
|
|
|
|
/// <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("比赛结束时间必须晚于报名结束时间");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
/// <summary>
|
2026-03-18 00:05:53 +08:00
|
|
|
|
/// 新增赛事
|
2026-02-08 18:09:05 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="input"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[UnitOfWork]
|
|
|
|
|
|
[DisplayName("新增赛事")]
|
|
|
|
|
|
public async Task Add(AddEventInput input)
|
|
|
|
|
|
{
|
2026-04-06 14:13:43 +08:00
|
|
|
|
// 验证赛事时间逻辑
|
|
|
|
|
|
ValidateEventTime(
|
|
|
|
|
|
input.RegistrationStartTime,
|
|
|
|
|
|
input.RegistrationEndTime,
|
|
|
|
|
|
input.MatchStartTime,
|
|
|
|
|
|
input.MatchEndTime
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-04-03 09:42:10 +08:00
|
|
|
|
// 从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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
var eventInfo = input.Adapt<EventInfo>();
|
2026-04-03 09:42:10 +08:00
|
|
|
|
eventInfo.ProvinceId = provinceId;
|
|
|
|
|
|
eventInfo.CityId = cityId;
|
|
|
|
|
|
eventInfo.DistrictId = districtId;
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
eventInfo.Id = _idGenerator.NewLong();
|
|
|
|
|
|
eventInfo.CreatedTime = DateTime.Now;
|
|
|
|
|
|
eventInfo.Status = AvailabilityStatus.Enable;
|
|
|
|
|
|
|
2026-04-03 09:42:10 +08:00
|
|
|
|
// 保存商户ID到EventInfo表
|
2026-04-07 09:04:38 +08:00
|
|
|
|
if (input.FeeConfig != null)
|
2026-04-03 09:42:10 +08:00
|
|
|
|
{
|
2026-04-07 09:04:38 +08:00
|
|
|
|
eventInfo.MerchantId = input.FeeConfig.MerchantId;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 15:46:19 +08:00
|
|
|
|
// 自动生成二维码URL (模拟)
|
|
|
|
|
|
eventInfo.QrCodeUrl = $"/pages/event/detail?id={eventInfo.Id}";
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
var now = DateTime.Now;
|
2026-04-06 14:13:43 +08:00
|
|
|
|
eventInfo.EventStatus = EventStatusUpdateService.CalculateEventStatus(eventInfo, now);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
2026-05-15 13:46:58 +08:00
|
|
|
|
// 保存规则快照
|
|
|
|
|
|
await _ruleSnapshotService.SaveAllRuleSnapshots(eventInfo);
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
await _repository.InsertAsync(eventInfo);
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 保存参与单位
|
2026-03-08 15:46:19 +08:00
|
|
|
|
if (input.Units != null && input.Units.Any())
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-03-08 15:46:19 +08:00
|
|
|
|
var units = input.Units.Select(u => {
|
|
|
|
|
|
var entity = u.Adapt<EventUnit>();
|
|
|
|
|
|
entity.Id = _idGenerator.NewLong();
|
|
|
|
|
|
entity.EventId = eventInfo.Id;
|
|
|
|
|
|
return entity;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}).ToList();
|
2026-03-08 15:46:19 +08:00
|
|
|
|
await _unitRepository.InsertRangeAsync(units);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 15:46:19 +08:00
|
|
|
|
// 2. 自定义字段 (JSON) - Already in eventInfo
|
|
|
|
|
|
|
2026-03-18 16:25:49 +08:00
|
|
|
|
var projectFees = new List<ProjectFeeInput>();
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
// 3. 保存项目信息 (Step 3)
|
|
|
|
|
|
if (input.Projects != null && input.Projects.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var projInput in input.Projects)
|
|
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
var eventProject = new EventProject
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NewLong(),
|
|
|
|
|
|
EventId = eventInfo.Id,
|
|
|
|
|
|
ProjectId = projInput.ProjectId,
|
|
|
|
|
|
GenderType = projInput.GenderType,
|
2026-04-10 14:29:41 +08:00
|
|
|
|
Price = projInput.Fee,
|
2026-06-06 23:34:12 +08:00
|
|
|
|
CreatedUserId = _authManager.UserId,
|
2026-03-18 16:25:49 +08:00
|
|
|
|
CreatedTime = DateTime.Now
|
|
|
|
|
|
};
|
|
|
|
|
|
await _eventProjectRepository.InsertAsync(eventProject);
|
|
|
|
|
|
|
|
|
|
|
|
projectFees.Add(new ProjectFeeInput { ProjectId = eventProject.Id, Fee = projInput.Fee });
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
|
|
|
|
|
// 保存项目分组
|
|
|
|
|
|
if (projInput.Groups != null && projInput.Groups.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var groups = projInput.Groups.Select(g =>
|
|
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
var group = new EventProjectGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NewLong(),
|
|
|
|
|
|
EventId = eventInfo.Id,
|
|
|
|
|
|
EventProjectId = eventProject.Id,
|
|
|
|
|
|
ProjectId = projInput.ProjectId,
|
|
|
|
|
|
ProjectGroupId = g.ProjectGroupId,
|
2026-04-04 23:31:38 +08:00
|
|
|
|
GroupName = g.GroupName ?? "",
|
|
|
|
|
|
BirthDateStart = g.BirthDateStart ?? DateTime.MinValue,
|
|
|
|
|
|
BirthDateEnd = g.BirthDateEnd ?? DateTime.MinValue,
|
|
|
|
|
|
Gender = g.Gender ?? Gender.All,
|
2026-06-06 23:34:12 +08:00
|
|
|
|
CreatedUserId = _authManager.UserId,
|
2026-03-18 16:25:49 +08:00
|
|
|
|
CreatedTime = DateTime.Now
|
|
|
|
|
|
};
|
2026-02-08 18:09:05 +08:00
|
|
|
|
return group;
|
|
|
|
|
|
}).ToList();
|
2026-03-18 16:25:49 +08:00
|
|
|
|
await _eventProjectGroupRepository.InsertRangeAsync(groups);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存项目报名限制 (Step 3 - Project Limit)
|
|
|
|
|
|
if (projInput.LimitCount.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var projLimit = new EventProjectRegistrationLimit
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NewLong(),
|
|
|
|
|
|
EventId = eventInfo.Id,
|
2026-03-18 16:25:49 +08:00
|
|
|
|
ProjectId = eventProject.Id,
|
|
|
|
|
|
LimitCount = projInput.LimitCount.Value,
|
2026-06-06 23:34:12 +08:00
|
|
|
|
CreatedUserId = _authManager.UserId,
|
2026-03-18 16:25:49 +08:00
|
|
|
|
CreatedTime = DateTime.Now
|
2026-02-08 18:09:05 +08:00
|
|
|
|
};
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
// 满足 SaveFeeConfig 的前置状态要求
|
|
|
|
|
|
eventInfo.ProcessStatus = ProcessStatus.ProjectConfigCompleted;
|
|
|
|
|
|
await _repository.UpdateAsync(eventInfo);
|
|
|
|
|
|
|
|
|
|
|
|
var feeConfigInput = new SaveFeeConfigInput
|
|
|
|
|
|
{
|
|
|
|
|
|
EventId = eventInfo.Id,
|
|
|
|
|
|
FeeConfig = input.FeeConfig,
|
|
|
|
|
|
ProjectFees = projectFees
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await SaveFeeConfig(feeConfigInput);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-03-18 00:05:53 +08:00
|
|
|
|
/// 更新赛事
|
2026-02-08 18:09:05 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="input"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[UnitOfWork]
|
|
|
|
|
|
[DisplayName("更新赛事")]
|
|
|
|
|
|
public async Task Update(UpdateEventInput input)
|
|
|
|
|
|
{
|
2026-04-06 14:13:43 +08:00
|
|
|
|
// 验证赛事时间逻辑
|
|
|
|
|
|
ValidateEventTime(
|
|
|
|
|
|
input.RegistrationStartTime,
|
|
|
|
|
|
input.RegistrationEndTime,
|
|
|
|
|
|
input.MatchStartTime,
|
|
|
|
|
|
input.MatchEndTime
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
var eventInfo = await _repository.GetByIdAsync(input.Id);
|
|
|
|
|
|
if (eventInfo == null) throw new InvalidOperationException("赛事不存在");
|
|
|
|
|
|
|
2026-06-08 17:57:09 +08:00
|
|
|
|
// 检查是否有报名记录:如果有人报名,检查是否修改了项目和组别(软删除和已取消的不算)
|
|
|
|
|
|
var hasRegistration = await _registrationRepository.IsAnyAsync(x => x.EventId == input.Id && !x.DeleteMark && x.PaymentStatus != 4);
|
2026-06-08 13:22:58 +08:00
|
|
|
|
if (hasRegistration)
|
2026-06-08 12:19:33 +08:00
|
|
|
|
{
|
|
|
|
|
|
var hasProjectOrGroupChange = await CheckIfProjectsOrGroupsChanged(input, eventInfo.Id);
|
|
|
|
|
|
if (hasProjectOrGroupChange)
|
|
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
throw Oops.Bah("当前赛事已有人员报名,不允许修改项目信息");
|
2026-06-08 12:19:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 09:42:10 +08:00
|
|
|
|
// 将前端传过来的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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
input.Adapt(eventInfo);
|
2026-04-03 09:42:10 +08:00
|
|
|
|
eventInfo.ProvinceId = provinceId;
|
|
|
|
|
|
eventInfo.CityId = cityId;
|
|
|
|
|
|
eventInfo.DistrictId = districtId;
|
|
|
|
|
|
|
|
|
|
|
|
// 保存商户ID到EventInfo表
|
2026-04-07 09:04:38 +08:00
|
|
|
|
if (input.FeeConfig != null)
|
2026-04-03 09:42:10 +08:00
|
|
|
|
{
|
2026-04-07 09:04:38 +08:00
|
|
|
|
eventInfo.MerchantId = input.FeeConfig.MerchantId;
|
2026-04-03 09:42:10 +08:00
|
|
|
|
}
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
|
|
|
|
|
eventInfo.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
|
2026-03-08 15:46:19 +08:00
|
|
|
|
// 更新状态逻辑
|
2026-02-08 18:09:05 +08:00
|
|
|
|
var now = DateTime.Now;
|
2026-04-06 14:13:43 +08:00
|
|
|
|
eventInfo.EventStatus = EventStatusUpdateService.CalculateEventStatus(eventInfo, now);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
2026-05-15 13:46:58 +08:00
|
|
|
|
// 保存规则快照
|
|
|
|
|
|
await _ruleSnapshotService.SaveAllRuleSnapshots(eventInfo);
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
await _repository.UpdateAsync(eventInfo);
|
|
|
|
|
|
|
2026-06-06 23:34:12 +08:00
|
|
|
|
// 1. 更新参与单位 - 软删旧数据避免历史关联丢失
|
|
|
|
|
|
await _unitRepository.SoftDeleteAsync(x => x.EventId == input.Id);
|
2026-03-08 15:46:19 +08:00
|
|
|
|
if (input.Units != null && input.Units.Any())
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-03-08 15:46:19 +08:00
|
|
|
|
var units = input.Units.Select(u => {
|
|
|
|
|
|
var entity = u.Adapt<EventUnit>();
|
|
|
|
|
|
entity.Id = _idGenerator.NewLong();
|
|
|
|
|
|
entity.EventId = eventInfo.Id;
|
|
|
|
|
|
return entity;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}).ToList();
|
2026-03-08 15:46:19 +08:00
|
|
|
|
await _unitRepository.InsertRangeAsync(units);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-08 15:46:19 +08:00
|
|
|
|
// 2. 更新自定义字段 (JSON) - Updated in eventInfo
|
|
|
|
|
|
|
2026-03-18 16:25:49 +08:00
|
|
|
|
var projectFees = new List<ProjectFeeInput>();
|
|
|
|
|
|
|
2026-06-08 12:19:33 +08:00
|
|
|
|
// 3. 更新项目信息
|
2026-06-08 13:22:58 +08:00
|
|
|
|
if (hasRegistration)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
// 有报名记录的情况下,只允许修改项目价格和限报
|
2026-06-08 12:19:33 +08:00
|
|
|
|
var oldEventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.Id && !x.DeleteMark);
|
|
|
|
|
|
var oldProjectDict = oldEventProjects.ToDictionary(x => x.ProjectId);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
2026-06-08 12:19:33 +08:00
|
|
|
|
if (input.Projects != null && input.Projects.Any())
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-06-08 12:19:33 +08:00
|
|
|
|
foreach (var projInput in input.Projects)
|
2026-03-18 16:25:49 +08:00
|
|
|
|
{
|
2026-06-08 12:19:33 +08:00
|
|
|
|
if (oldProjectDict.ContainsKey(projInput.ProjectId))
|
|
|
|
|
|
{
|
|
|
|
|
|
var eventProject = oldProjectDict[projInput.ProjectId];
|
|
|
|
|
|
|
|
|
|
|
|
// 只更新项目价格
|
|
|
|
|
|
eventProject.Price = projInput.Fee;
|
|
|
|
|
|
eventProject.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _eventProjectRepository.UpdateAsync(eventProject);
|
|
|
|
|
|
|
|
|
|
|
|
projectFees.Add(new ProjectFeeInput { ProjectId = eventProject.Id, Fee = projInput.Fee });
|
|
|
|
|
|
|
|
|
|
|
|
// 更新项目限报
|
|
|
|
|
|
var oldLimit = await _projectLimitRepository.GetFirstAsync(x => x.ProjectId == eventProject.Id && !x.DeleteMark);
|
|
|
|
|
|
|
|
|
|
|
|
if (projInput.LimitCount.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (oldLimit != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
oldLimit.LimitCount = projInput.LimitCount.Value;
|
|
|
|
|
|
oldLimit.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _projectLimitRepository.UpdateAsync(oldLimit);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var projLimit = new EventProjectRegistrationLimit
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NewLong(),
|
|
|
|
|
|
EventId = eventInfo.Id,
|
|
|
|
|
|
ProjectId = eventProject.Id,
|
|
|
|
|
|
LimitCount = projInput.LimitCount.Value,
|
|
|
|
|
|
CreatedUserId = _authManager.UserId,
|
|
|
|
|
|
CreatedTime = DateTime.Now
|
|
|
|
|
|
};
|
|
|
|
|
|
await _projectLimitRepository.InsertAsync(projLimit);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (oldLimit != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _projectLimitRepository.SoftDeleteAsync(x => x.Id == oldLimit.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
// 没有报名记录的情况下,全量更新项目信息
|
2026-06-08 12:19:33 +08:00
|
|
|
|
var oldEventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.Id && !x.DeleteMark);
|
|
|
|
|
|
var oldProjectDict = oldEventProjects.ToDictionary(x => x.ProjectId);
|
|
|
|
|
|
var oldEventProjectIds = oldEventProjects.Select(x => x.Id).ToList();
|
2026-03-18 16:25:49 +08:00
|
|
|
|
|
2026-06-08 12:19:33 +08:00
|
|
|
|
if (input.Projects != null && input.Projects.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var newProjectIds = new List<long>();
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
2026-06-08 12:19:33 +08:00
|
|
|
|
foreach (var projInput in input.Projects)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-06-08 12:19:33 +08:00
|
|
|
|
EventProject eventProject;
|
|
|
|
|
|
|
|
|
|
|
|
if (oldProjectDict.ContainsKey(projInput.ProjectId))
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-06-08 12:19:33 +08:00
|
|
|
|
// 更新现有项目
|
|
|
|
|
|
eventProject = oldProjectDict[projInput.ProjectId];
|
|
|
|
|
|
eventProject.GenderType = projInput.GenderType;
|
|
|
|
|
|
eventProject.Price = projInput.Fee;
|
|
|
|
|
|
eventProject.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _eventProjectRepository.UpdateAsync(eventProject);
|
|
|
|
|
|
newProjectIds.Add(eventProject.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 新增项目
|
|
|
|
|
|
eventProject = new EventProject
|
2026-03-18 16:25:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NewLong(),
|
|
|
|
|
|
EventId = eventInfo.Id,
|
|
|
|
|
|
ProjectId = projInput.ProjectId,
|
2026-06-08 12:19:33 +08:00
|
|
|
|
GenderType = projInput.GenderType,
|
|
|
|
|
|
Price = projInput.Fee,
|
2026-06-06 23:34:12 +08:00
|
|
|
|
CreatedUserId = _authManager.UserId,
|
2026-03-18 16:25:49 +08:00
|
|
|
|
CreatedTime = DateTime.Now
|
|
|
|
|
|
};
|
2026-06-08 12:19:33 +08:00
|
|
|
|
await _eventProjectRepository.InsertAsync(eventProject);
|
|
|
|
|
|
newProjectIds.Add(eventProject.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
projectFees.Add(new ProjectFeeInput { ProjectId = eventProject.Id, Fee = projInput.Fee });
|
|
|
|
|
|
|
|
|
|
|
|
// 处理项目分组
|
|
|
|
|
|
var oldGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventProjectId == eventProject.Id && !x.DeleteMark);
|
2026-10-10 09:04:52 +08:00
|
|
|
|
var oldGroupDict = BuildExistingGroupMap(oldGroups);
|
2026-06-08 12:19:33 +08:00
|
|
|
|
var newGroupIds = new List<long>();
|
|
|
|
|
|
|
|
|
|
|
|
if (projInput.Groups != null && projInput.Groups.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var groupInput in projInput.Groups)
|
|
|
|
|
|
{
|
|
|
|
|
|
EventProjectGroup group;
|
|
|
|
|
|
|
|
|
|
|
|
if (oldGroupDict.ContainsKey(groupInput.ProjectGroupId))
|
|
|
|
|
|
{
|
|
|
|
|
|
// 更新现有分组
|
|
|
|
|
|
group = oldGroupDict[groupInput.ProjectGroupId];
|
|
|
|
|
|
group.GroupName = groupInput.GroupName ?? "";
|
|
|
|
|
|
group.BirthDateStart = groupInput.BirthDateStart ?? DateTime.MinValue;
|
|
|
|
|
|
group.BirthDateEnd = groupInput.BirthDateEnd ?? DateTime.MinValue;
|
|
|
|
|
|
group.Gender = groupInput.Gender ?? Gender.All;
|
|
|
|
|
|
group.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _eventProjectGroupRepository.UpdateAsync(group);
|
|
|
|
|
|
newGroupIds.Add(group.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 新增分组
|
|
|
|
|
|
group = new EventProjectGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NewLong(),
|
|
|
|
|
|
EventId = eventInfo.Id,
|
|
|
|
|
|
EventProjectId = eventProject.Id,
|
|
|
|
|
|
ProjectId = projInput.ProjectId,
|
|
|
|
|
|
ProjectGroupId = groupInput.ProjectGroupId,
|
|
|
|
|
|
GroupName = groupInput.GroupName ?? "",
|
|
|
|
|
|
BirthDateStart = groupInput.BirthDateStart ?? DateTime.MinValue,
|
|
|
|
|
|
BirthDateEnd = groupInput.BirthDateEnd ?? DateTime.MinValue,
|
|
|
|
|
|
Gender = groupInput.Gender ?? Gender.All,
|
|
|
|
|
|
CreatedUserId = _authManager.UserId,
|
|
|
|
|
|
CreatedTime = DateTime.Now
|
|
|
|
|
|
};
|
|
|
|
|
|
await _eventProjectGroupRepository.InsertAsync(group);
|
|
|
|
|
|
newGroupIds.Add(group.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 软删除不再存在的分组
|
|
|
|
|
|
var groupsToDelete = oldGroups.Where(g => !newGroupIds.Contains(g.Id)).Select(g => g.Id).ToList();
|
|
|
|
|
|
if (groupsToDelete.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
await _eventProjectGroupRepository.SoftDeleteAsync(x => groupsToDelete.Contains(x.Id));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理项目限制
|
|
|
|
|
|
var oldLimit = await _projectLimitRepository.GetFirstAsync(x => x.ProjectId == eventProject.Id && !x.DeleteMark);
|
|
|
|
|
|
|
|
|
|
|
|
if (projInput.LimitCount.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (oldLimit != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 更新现有限制
|
|
|
|
|
|
oldLimit.LimitCount = projInput.LimitCount.Value;
|
|
|
|
|
|
oldLimit.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _projectLimitRepository.UpdateAsync(oldLimit);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 新增限制
|
|
|
|
|
|
var projLimit = new EventProjectRegistrationLimit
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NewLong(),
|
|
|
|
|
|
EventId = eventInfo.Id,
|
|
|
|
|
|
ProjectId = eventProject.Id,
|
|
|
|
|
|
LimitCount = projInput.LimitCount.Value,
|
|
|
|
|
|
CreatedUserId = _authManager.UserId,
|
|
|
|
|
|
CreatedTime = DateTime.Now
|
|
|
|
|
|
};
|
|
|
|
|
|
await _projectLimitRepository.InsertAsync(projLimit);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (oldLimit != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 删除限制
|
|
|
|
|
|
await _projectLimitRepository.SoftDeleteAsync(x => x.Id == oldLimit.Id);
|
|
|
|
|
|
}
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 12:19:33 +08:00
|
|
|
|
// 软删除不再存在的项目
|
|
|
|
|
|
var projectsToDelete = oldEventProjects.Where(ep => !newProjectIds.Contains(ep.Id)).Select(ep => ep.Id).ToList();
|
|
|
|
|
|
if (projectsToDelete.Any())
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-06-08 12:19:33 +08:00
|
|
|
|
await _eventProjectGroupRepository.SoftDeleteAsync(x => projectsToDelete.Contains(x.EventProjectId));
|
|
|
|
|
|
await _projectLimitRepository.SoftDeleteAsync(x => projectsToDelete.Contains(x.ProjectId));
|
|
|
|
|
|
await _eventProjectRepository.SoftDeleteAsync(x => projectsToDelete.Contains(x.Id));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 删除所有项目
|
|
|
|
|
|
if (oldEventProjectIds.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
await _eventProjectGroupRepository.SoftDeleteAsync(x => oldEventProjectIds.Contains(x.EventProjectId));
|
|
|
|
|
|
await _projectLimitRepository.SoftDeleteAsync(x => oldEventProjectIds.Contains(x.ProjectId));
|
|
|
|
|
|
await _eventProjectRepository.SoftDeleteAsync(x => oldEventProjectIds.Contains(x.Id));
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-06 23:34:12 +08:00
|
|
|
|
// 4. 更新报名限制 - 软删旧数据避免历史关联丢失
|
|
|
|
|
|
await _registrationLimitRepository.SoftDeleteAsync(x => x.EventId == input.Id);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2026-03-18 16:25:49 +08:00
|
|
|
|
// 满足 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);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 12:19:33 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检查项目或组别是否有变化
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task<bool> CheckIfProjectsOrGroupsChanged(UpdateEventInput input, long eventId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var oldEventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId && !x.DeleteMark);
|
|
|
|
|
|
|
|
|
|
|
|
// 检查项目数量是否变化
|
|
|
|
|
|
if ((input.Projects == null || !input.Projects.Any()) && oldEventProjects.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
return true; // 删除了所有项目
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (input.Projects != null && input.Projects.Count() != oldEventProjects.Count)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true; // 项目数量有变化
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果都有项目,继续比较每个项目
|
|
|
|
|
|
if (input.Projects != null && input.Projects.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var oldProjectDict = oldEventProjects.ToDictionary(x => x.ProjectId);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var inputProj in input.Projects)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查是否有新的项目
|
|
|
|
|
|
if (!oldProjectDict.ContainsKey(inputProj.ProjectId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var oldProj = oldProjectDict[inputProj.ProjectId];
|
|
|
|
|
|
|
|
|
|
|
|
// 检查项目的基本信息是否变化
|
|
|
|
|
|
if (oldProj.GenderType != inputProj.GenderType)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查组别是否变化
|
|
|
|
|
|
var oldGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventProjectId == oldProj.Id && !x.DeleteMark);
|
|
|
|
|
|
|
|
|
|
|
|
if ((inputProj.Groups == null || !inputProj.Groups.Any()) && oldGroups.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
return true; // 删除了所有组别
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (inputProj.Groups != null && inputProj.Groups.Count() != oldGroups.Count)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true; // 组别数量有变化
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (inputProj.Groups != null && inputProj.Groups.Any())
|
|
|
|
|
|
{
|
2026-10-10 09:04:52 +08:00
|
|
|
|
var oldGroupDict = BuildExistingGroupMap(oldGroups);
|
2026-06-08 12:19:33 +08:00
|
|
|
|
|
|
|
|
|
|
foreach (var inputGroup in inputProj.Groups)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查是否有新的组别
|
|
|
|
|
|
if (!oldGroupDict.ContainsKey(inputGroup.ProjectGroupId))
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var oldGroup = oldGroupDict[inputGroup.ProjectGroupId];
|
|
|
|
|
|
|
|
|
|
|
|
// 检查组别的基本信息是否变化
|
|
|
|
|
|
if (oldGroup.GroupName != inputGroup.GroupName ||
|
|
|
|
|
|
oldGroup.BirthDateStart != (inputGroup.BirthDateStart ?? DateTime.MinValue) ||
|
|
|
|
|
|
oldGroup.BirthDateEnd != (inputGroup.BirthDateEnd ?? DateTime.MinValue) ||
|
|
|
|
|
|
oldGroup.Gender != (inputGroup.Gender ?? Gender.All))
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 导出赛事
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="input"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[DisplayName("导出赛事")]
|
|
|
|
|
|
public async Task<byte[]> Export(EventExportInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var query = _repository.AsQueryable()
|
2026-03-08 15:46:19 +08:00
|
|
|
|
.LeftJoin<EventFeeConfig>((e, f) => e.Id == f.EventId)
|
2026-04-01 13:04:41 +08:00
|
|
|
|
.LeftJoin<YD_Event.Core.Entities.Merchant>((e, f, m) => e.MerchantId == m.Id)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
.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)
|
2026-04-07 09:04:38 +08:00
|
|
|
|
.Select((e, f, m) => new EventExportData
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-04-07 09:04:38 +08:00
|
|
|
|
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,
|
2026-02-08 18:09:05 +08:00
|
|
|
|
MerchantName = m.MerchantName
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
var list = await query.ToListAsync();
|
2026-03-08 15:46:19 +08:00
|
|
|
|
var eventIds = list.Select(x => x.Id).ToList();
|
|
|
|
|
|
var units = await _unitRepository.GetListAsync(x => eventIds.Contains(x.EventId));
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
2026-04-07 09:04:38 +08:00
|
|
|
|
// 使用 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)>
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-04-07 09:04:38 +08:00
|
|
|
|
("赛事名称", 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];
|
2026-03-08 15:46:19 +08:00
|
|
|
|
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));
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
2026-04-07 09:04:38 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
2026-04-07 09:04:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 自动调整列宽
|
|
|
|
|
|
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();
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 删除赛事
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dto"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[DisplayName("删除赛事")]
|
|
|
|
|
|
public override async Task Delete(KeyDto dto)
|
|
|
|
|
|
{
|
2026-06-08 17:57:09 +08:00
|
|
|
|
// 检查是否有报名记录(排除已取消和软删除的)
|
|
|
|
|
|
if (await _registrationRepository.IsAnyAsync(x => x.EventId == dto.Id && !x.DeleteMark && x.PaymentStatus != 4))
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException("已有人员报名此赛事,暂不支持删除");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 软删除 EventInfo
|
|
|
|
|
|
await base.Delete(dto);
|
|
|
|
|
|
// 关联表物理删除或保留?通常保留。如果需要清理:
|
2026-03-08 15:46:19 +08:00
|
|
|
|
// 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)
|
|
|
|
|
|
{
|
2026-04-06 14:13:43 +08:00
|
|
|
|
// 验证赛事时间逻辑
|
|
|
|
|
|
ValidateEventTime(
|
|
|
|
|
|
input.RegistrationStartTime,
|
|
|
|
|
|
input.RegistrationEndTime,
|
|
|
|
|
|
input.MatchStartTime,
|
|
|
|
|
|
input.MatchEndTime
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-08 15:46:19 +08:00
|
|
|
|
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;
|
2026-05-15 13:46:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 保存规则快照
|
|
|
|
|
|
await _ruleSnapshotService.SaveAllRuleSnapshots(eventInfo);
|
2026-03-08 15:46:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
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;
|
2026-04-06 14:13:43 +08:00
|
|
|
|
eventInfo.EventStatus = EventStatusUpdateService.CalculateEventStatus(eventInfo, now);
|
2026-03-08 15:46:19 +08:00
|
|
|
|
|
2026-05-15 13:46:58 +08:00
|
|
|
|
// 保存规则快照
|
|
|
|
|
|
await _ruleSnapshotService.SaveAllRuleSnapshots(eventInfo);
|
|
|
|
|
|
|
2026-03-08 15:46:19 +08:00
|
|
|
|
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("请先完成基本信息配置");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-06 23:34:12 +08:00
|
|
|
|
// 全量更新单位 - 软删旧数据避免历史关联丢失
|
|
|
|
|
|
await _unitRepository.SoftDeleteAsync(x => x.EventId == input.EventId);
|
2026-03-08 15:46:19 +08:00
|
|
|
|
|
|
|
|
|
|
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("请先完成参与单位配置");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 17:35:35 +08:00
|
|
|
|
// 检查是否有报名记录:如果有人报名,检查是否修改了项目和组别(软删除的不算)
|
|
|
|
|
|
var hasRegistration = await _registrationRepository.IsAnyAsync(x => x.EventId == input.EventId && !x.DeleteMark);
|
2026-06-08 13:22:58 +08:00
|
|
|
|
if (hasRegistration)
|
2026-03-08 15:46:19 +08:00
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
var oldEventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark);
|
|
|
|
|
|
var oldProjectDict = oldEventProjects.ToDictionary(x => x.ProjectId);
|
|
|
|
|
|
var oldProjectIds = oldEventProjects.Select(x => x.ProjectId).ToList();
|
|
|
|
|
|
var newProjectIds = input.Projects?.Select(p => p.ProjectId).ToList() ?? new List<long>();
|
2026-06-08 12:19:33 +08:00
|
|
|
|
|
2026-06-08 13:22:58 +08:00
|
|
|
|
// 检查是否新增或删除了项目
|
|
|
|
|
|
if (newProjectIds.Except(oldProjectIds).Any() || oldProjectIds.Except(newProjectIds).Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("当前赛事已有人员报名,不允许修改项目信息");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查每个项目的组别是否有变化
|
|
|
|
|
|
foreach (var projInput in input.Projects ?? new List<EventProjectInput>())
|
2026-03-08 15:46:19 +08:00
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
if (oldProjectDict.ContainsKey(projInput.ProjectId))
|
|
|
|
|
|
{
|
|
|
|
|
|
var oldProj = oldProjectDict[projInput.ProjectId];
|
|
|
|
|
|
|
|
|
|
|
|
// 检查项目基本信息是否变化
|
|
|
|
|
|
if (oldProj.GenderType != projInput.GenderType)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("当前赛事已有人员报名,不允许修改项目信息");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查组别是否变化
|
|
|
|
|
|
var oldGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventProjectId == oldProj.Id && !x.DeleteMark);
|
|
|
|
|
|
var oldGroupIds = oldGroups.Select(g => g.ProjectGroupId).ToList();
|
|
|
|
|
|
var newGroupIds = projInput.Groups?.Select(g => g.ProjectGroupId).ToList() ?? new List<long>();
|
|
|
|
|
|
|
|
|
|
|
|
if (newGroupIds.Except(oldGroupIds).Any() || oldGroupIds.Except(newGroupIds).Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("当前赛事已有人员报名,不允许修改项目信息");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查组别的基本信息是否变化
|
|
|
|
|
|
foreach (var groupInput in projInput.Groups ?? new List<EventProjectGroupInput>())
|
|
|
|
|
|
{
|
|
|
|
|
|
var oldGroup = oldGroups.FirstOrDefault(g => g.ProjectGroupId == groupInput.ProjectGroupId);
|
|
|
|
|
|
if (oldGroup != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (oldGroup.GroupName != (groupInput.GroupName ?? "") ||
|
|
|
|
|
|
oldGroup.BirthDateStart != (groupInput.BirthDateStart ?? DateTime.MinValue) ||
|
|
|
|
|
|
oldGroup.BirthDateEnd != (groupInput.BirthDateEnd ?? DateTime.MinValue) ||
|
|
|
|
|
|
oldGroup.Gender != (groupInput.Gender ?? Gender.All))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("当前赛事已有人员报名,不允许修改项目信息");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-08 12:19:33 +08:00
|
|
|
|
|
2026-06-08 13:22:58 +08:00
|
|
|
|
// 有报名记录的情况下,只允许修改项目价格和限报
|
|
|
|
|
|
foreach (var projInput in input.Projects ?? new List<EventProjectInput>())
|
|
|
|
|
|
{
|
2026-06-08 12:19:33 +08:00
|
|
|
|
if (oldProjectDict.ContainsKey(projInput.ProjectId))
|
2026-03-18 16:25:49 +08:00
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
var eventProject = oldProjectDict[projInput.ProjectId];
|
|
|
|
|
|
|
|
|
|
|
|
// 只更新项目价格
|
2026-06-08 12:19:33 +08:00
|
|
|
|
eventProject.Price = projInput.Fee;
|
|
|
|
|
|
eventProject.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _eventProjectRepository.UpdateAsync(eventProject);
|
|
|
|
|
|
|
2026-06-08 13:22:58 +08:00
|
|
|
|
// 更新项目限报
|
|
|
|
|
|
var oldLimit = await _projectLimitRepository.GetFirstAsync(x => x.ProjectId == eventProject.Id && !x.DeleteMark);
|
2026-03-08 15:46:19 +08:00
|
|
|
|
|
2026-06-08 13:22:58 +08:00
|
|
|
|
if (projInput.LimitCount.HasValue)
|
2026-03-08 15:46:19 +08:00
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
if (oldLimit != null)
|
2026-06-08 12:19:33 +08:00
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
oldLimit.LimitCount = projInput.LimitCount.Value;
|
|
|
|
|
|
oldLimit.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _projectLimitRepository.UpdateAsync(oldLimit);
|
2026-06-08 12:19:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
var projLimit = new EventProjectRegistrationLimit
|
2026-06-08 12:19:33 +08:00
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NewLong(),
|
|
|
|
|
|
EventId = input.EventId,
|
2026-06-08 13:22:58 +08:00
|
|
|
|
ProjectId = eventProject.Id,
|
|
|
|
|
|
LimitCount = projInput.LimitCount.Value,
|
2026-06-08 12:19:33 +08:00
|
|
|
|
CreatedUserId = _authManager.UserId,
|
|
|
|
|
|
CreatedTime = DateTime.Now
|
|
|
|
|
|
};
|
2026-06-08 13:22:58 +08:00
|
|
|
|
await _projectLimitRepository.InsertAsync(projLimit);
|
2026-06-08 12:19:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-08 13:22:58 +08:00
|
|
|
|
else if (oldLimit != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _projectLimitRepository.SoftDeleteAsync(x => x.Id == oldLimit.Id);
|
|
|
|
|
|
}
|
2026-06-08 12:19:33 +08:00
|
|
|
|
}
|
2026-06-08 13:22:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 没有报名记录的情况下,全量更新项目信息
|
|
|
|
|
|
var oldEventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark);
|
|
|
|
|
|
var oldProjectDict = oldEventProjects.ToDictionary(x => x.ProjectId);
|
|
|
|
|
|
var oldEventProjectIds = oldEventProjects.Select(x => x.Id).ToList();
|
2026-06-08 12:19:33 +08:00
|
|
|
|
|
2026-06-08 13:22:58 +08:00
|
|
|
|
// 处理输入的项目
|
|
|
|
|
|
if (input.Projects != null && input.Projects.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var newProjectIds = new List<long>();
|
2026-06-08 12:19:33 +08:00
|
|
|
|
|
2026-06-08 13:22:58 +08:00
|
|
|
|
foreach (var projInput in input.Projects)
|
2026-06-08 12:19:33 +08:00
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
EventProject eventProject;
|
|
|
|
|
|
|
|
|
|
|
|
if (oldProjectDict.ContainsKey(projInput.ProjectId))
|
2026-06-08 12:19:33 +08:00
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
// 更新现有项目
|
|
|
|
|
|
eventProject = oldProjectDict[projInput.ProjectId];
|
|
|
|
|
|
eventProject.GenderType = projInput.GenderType;
|
|
|
|
|
|
eventProject.Price = projInput.Fee;
|
|
|
|
|
|
eventProject.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _eventProjectRepository.UpdateAsync(eventProject);
|
|
|
|
|
|
newProjectIds.Add(eventProject.Id);
|
2026-06-08 12:19:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
// 新增项目
|
|
|
|
|
|
eventProject = new EventProject
|
2026-03-18 16:25:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NewLong(),
|
|
|
|
|
|
EventId = input.EventId,
|
2026-06-08 13:22:58 +08:00
|
|
|
|
ProjectId = projInput.ProjectId,
|
|
|
|
|
|
GenderType = projInput.GenderType,
|
|
|
|
|
|
Price = projInput.Fee,
|
2026-06-06 23:34:12 +08:00
|
|
|
|
CreatedUserId = _authManager.UserId,
|
2026-03-18 16:25:49 +08:00
|
|
|
|
CreatedTime = DateTime.Now
|
|
|
|
|
|
};
|
2026-06-08 13:22:58 +08:00
|
|
|
|
await _eventProjectRepository.InsertAsync(eventProject);
|
|
|
|
|
|
newProjectIds.Add(eventProject.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理项目分组
|
|
|
|
|
|
var oldGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventProjectId == eventProject.Id && !x.DeleteMark);
|
2026-10-10 09:04:52 +08:00
|
|
|
|
var oldGroupDict = BuildExistingGroupMap(oldGroups);
|
2026-06-08 13:22:58 +08:00
|
|
|
|
var newGroupIds = new List<long>();
|
|
|
|
|
|
|
|
|
|
|
|
if (projInput.Groups != null && projInput.Groups.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var groupInput in projInput.Groups)
|
|
|
|
|
|
{
|
|
|
|
|
|
EventProjectGroup group;
|
|
|
|
|
|
|
|
|
|
|
|
if (oldGroupDict.ContainsKey(groupInput.ProjectGroupId))
|
|
|
|
|
|
{
|
|
|
|
|
|
// 更新现有分组
|
|
|
|
|
|
group = oldGroupDict[groupInput.ProjectGroupId];
|
|
|
|
|
|
group.GroupName = groupInput.GroupName ?? "";
|
|
|
|
|
|
group.BirthDateStart = groupInput.BirthDateStart ?? DateTime.MinValue;
|
|
|
|
|
|
group.BirthDateEnd = groupInput.BirthDateEnd ?? DateTime.MinValue;
|
|
|
|
|
|
group.Gender = groupInput.Gender ?? Gender.All;
|
|
|
|
|
|
group.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _eventProjectGroupRepository.UpdateAsync(group);
|
|
|
|
|
|
newGroupIds.Add(group.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 新增分组
|
|
|
|
|
|
group = new EventProjectGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NewLong(),
|
|
|
|
|
|
EventId = input.EventId,
|
|
|
|
|
|
EventProjectId = eventProject.Id,
|
|
|
|
|
|
ProjectId = projInput.ProjectId,
|
|
|
|
|
|
ProjectGroupId = groupInput.ProjectGroupId,
|
|
|
|
|
|
GroupName = groupInput.GroupName ?? "",
|
|
|
|
|
|
BirthDateStart = groupInput.BirthDateStart ?? DateTime.MinValue,
|
|
|
|
|
|
BirthDateEnd = groupInput.BirthDateEnd ?? DateTime.MinValue,
|
|
|
|
|
|
Gender = groupInput.Gender ?? Gender.All,
|
|
|
|
|
|
CreatedUserId = _authManager.UserId,
|
|
|
|
|
|
CreatedTime = DateTime.Now
|
|
|
|
|
|
};
|
|
|
|
|
|
await _eventProjectGroupRepository.InsertAsync(group);
|
|
|
|
|
|
newGroupIds.Add(group.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 软删除不再存在的分组
|
|
|
|
|
|
var groupsToDelete = oldGroups.Where(g => !newGroupIds.Contains(g.Id)).Select(g => g.Id).ToList();
|
|
|
|
|
|
if (groupsToDelete.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
await _eventProjectGroupRepository.SoftDeleteAsync(x => groupsToDelete.Contains(x.Id));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理项目限制
|
|
|
|
|
|
var oldLimit = await _projectLimitRepository.GetFirstAsync(x => x.ProjectId == eventProject.Id && !x.DeleteMark);
|
|
|
|
|
|
|
|
|
|
|
|
if (projInput.LimitCount.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (oldLimit != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 更新现有限制
|
|
|
|
|
|
oldLimit.LimitCount = projInput.LimitCount.Value;
|
|
|
|
|
|
oldLimit.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _projectLimitRepository.UpdateAsync(oldLimit);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 新增限制
|
|
|
|
|
|
var projLimit = new EventProjectRegistrationLimit
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NewLong(),
|
|
|
|
|
|
EventId = input.EventId,
|
|
|
|
|
|
ProjectId = eventProject.Id,
|
|
|
|
|
|
LimitCount = projInput.LimitCount.Value,
|
|
|
|
|
|
CreatedUserId = _authManager.UserId,
|
|
|
|
|
|
CreatedTime = DateTime.Now
|
|
|
|
|
|
};
|
|
|
|
|
|
await _projectLimitRepository.InsertAsync(projLimit);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (oldLimit != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 删除限制
|
|
|
|
|
|
await _projectLimitRepository.SoftDeleteAsync(x => x.Id == oldLimit.Id);
|
2026-06-08 12:19:33 +08:00
|
|
|
|
}
|
2026-03-08 15:46:19 +08:00
|
|
|
|
}
|
2026-06-08 13:22:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 软删除不再存在的项目
|
|
|
|
|
|
var projectsToDelete = oldEventProjects.Where(ep => !newProjectIds.Contains(ep.Id)).Select(ep => ep.Id).ToList();
|
|
|
|
|
|
if (projectsToDelete.Any())
|
2026-03-08 15:46:19 +08:00
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
await _eventProjectGroupRepository.SoftDeleteAsync(x => projectsToDelete.Contains(x.EventProjectId));
|
|
|
|
|
|
await _projectLimitRepository.SoftDeleteAsync(x => projectsToDelete.Contains(x.ProjectId));
|
|
|
|
|
|
await _eventProjectRepository.SoftDeleteAsync(x => projectsToDelete.Contains(x.Id));
|
2026-03-08 15:46:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-08 13:22:58 +08:00
|
|
|
|
else
|
2026-06-08 12:19:33 +08:00
|
|
|
|
{
|
2026-06-08 13:22:58 +08:00
|
|
|
|
// 删除所有项目
|
|
|
|
|
|
if (oldEventProjectIds.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
await _eventProjectGroupRepository.SoftDeleteAsync(x => oldEventProjectIds.Contains(x.EventProjectId));
|
|
|
|
|
|
await _projectLimitRepository.SoftDeleteAsync(x => oldEventProjectIds.Contains(x.ProjectId));
|
|
|
|
|
|
await _eventProjectRepository.SoftDeleteAsync(x => oldEventProjectIds.Contains(x.Id));
|
|
|
|
|
|
}
|
2026-06-08 12:19:33 +08:00
|
|
|
|
}
|
2026-03-08 15:46:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-06 23:34:12 +08:00
|
|
|
|
// 2. 更新报名限制 - 软删旧数据避免历史关联丢失
|
|
|
|
|
|
await _registrationLimitRepository.SoftDeleteAsync(x => x.EventId == input.EventId);
|
2026-03-08 15:46:19 +08:00
|
|
|
|
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("请先完成项目配置");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-07 09:04:38 +08:00
|
|
|
|
// 验证非免费模式下的必填参数
|
|
|
|
|
|
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");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 格式验证:最多保留两位小数
|
2026-04-09 20:49:36 +08:00
|
|
|
|
if (!AmountHelper.IsValidAmount(input.FeeConfig.Price))
|
2026-04-07 09:04:38 +08:00
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException("价格格式不正确,最多保留两位小数");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 16:25:49 +08:00
|
|
|
|
// 验证单价(项目)模式下的项目单价
|
|
|
|
|
|
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");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 格式验证:最多保留两位小数
|
2026-04-09 20:49:36 +08:00
|
|
|
|
if (!AmountHelper.IsValidAmount(feeInput.Fee))
|
2026-03-18 16:25:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException("项目单价格式不正确,最多保留两位小数");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var proj = eventProjects.FirstOrDefault(p => p.Id == feeInput.ProjectId);
|
|
|
|
|
|
if (proj != null)
|
|
|
|
|
|
{
|
2026-04-10 14:29:41 +08:00
|
|
|
|
proj.Price = feeInput.Fee;
|
2026-03-18 16:25:49 +08:00
|
|
|
|
proj.UpdatedTime = DateTime.Now;
|
|
|
|
|
|
await _eventProjectRepository.UpdateAsync(proj);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-06 23:34:12 +08:00
|
|
|
|
// 更新费用配置 - 软删旧数据避免历史关联丢失
|
|
|
|
|
|
await _feeConfigRepository.SoftDeleteAsync(x => x.EventId == input.EventId);
|
2026-03-08 15:46:19 +08:00
|
|
|
|
if (input.FeeConfig != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var feeConfig = input.FeeConfig.Adapt<EventFeeConfig>();
|
|
|
|
|
|
feeConfig.Id = _idGenerator.NewLong();
|
|
|
|
|
|
feeConfig.EventId = input.EventId;
|
|
|
|
|
|
await _feeConfigRepository.InsertAsync(feeConfig);
|
2026-03-18 16:25:49 +08:00
|
|
|
|
|
|
|
|
|
|
// 更新赛事的 ChargeMode
|
|
|
|
|
|
eventInfo.ChargeMode = feeConfig.ChargeMode;
|
2026-03-08 15:46:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成二维码URL
|
|
|
|
|
|
var qrCodeUrl = $"/pages/event/detail?id={eventInfo.Id}";
|
|
|
|
|
|
eventInfo.QrCodeUrl = qrCodeUrl;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新流程状态和赛事状态
|
|
|
|
|
|
eventInfo.ProcessStatus = ProcessStatus.FeeConfigCompleted;
|
|
|
|
|
|
|
|
|
|
|
|
// 重新计算并确认赛事状态
|
|
|
|
|
|
var now = DateTime.Now;
|
2026-04-06 14:13:43 +08:00
|
|
|
|
eventInfo.EventStatus = EventStatusUpdateService.CalculateEventStatus(eventInfo, now);
|
2026-03-08 15:46:19 +08:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
};
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
2026-10-10 09:04:52 +08:00
|
|
|
|
|
|
|
|
|
|
private static Dictionary<long, EventProjectGroup> BuildExistingGroupMap(List<EventProjectGroup> groups)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (groups.Any(x => x.ProjectGroupId <= 0))
|
|
|
|
|
|
throw Oops.Bah("赛事存在未关联平台组别的历史数据,请先核查修复项目组别配置");
|
|
|
|
|
|
if (groups.GroupBy(x => x.ProjectGroupId).Any(x => x.Count() > 1))
|
|
|
|
|
|
throw Oops.Bah("赛事存在重复的平台组别关联,请先核查修复项目组别配置");
|
|
|
|
|
|
|
|
|
|
|
|
return groups.ToDictionary(x => x.ProjectGroupId);
|
|
|
|
|
|
}
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|