赛事管理bug
This commit is contained in:
parent
2f053f5f11
commit
239fb777f3
@ -61,6 +61,26 @@ public class EventExportInput : EventQueryInput
|
||||
public List<string>? ExportFields { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 赛事导出数据
|
||||
/// </summary>
|
||||
public class EventExportData
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string EventName { get; set; } = "";
|
||||
public EventFormat EventType { get; set; }
|
||||
public ChargeMode ChargeMode { get; set; }
|
||||
public RegistrationType RegistrationType { get; set; }
|
||||
public EventStatus EventStatus { get; set; }
|
||||
public DateTime RegistrationStartTime { get; set; }
|
||||
public DateTime RegistrationEndTime { get; set; }
|
||||
public DateTime? MaterialUploadEndTime { get; set; }
|
||||
public DateTime MatchStartTime { get; set; }
|
||||
public DateTime MatchEndTime { get; set; }
|
||||
public string? DetailAddress { get; set; }
|
||||
public string? MerchantName { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 赛事列表输出
|
||||
/// </summary>
|
||||
@ -448,6 +468,11 @@ public class EventFeeConfigInput
|
||||
/// 银行账号 (显示字段)
|
||||
/// </summary>
|
||||
public string? AccountNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 营业执照图片URL (显示字段)
|
||||
/// </summary>
|
||||
public string? BusinessLicenseUrl { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -57,12 +57,16 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
|
||||
var typeIds = projects.Select(x => x.ProjectTypeId).Distinct().ToList();
|
||||
var types = await _projectTypeRepository.GetListAsync(x => typeIds.Contains(x.Id));
|
||||
|
||||
// 统计各组别报名人数(仅统计已支付或审核通过的)
|
||||
// 统计各组别报名人数(统计所有非取消状态的报名,并且只统计运动员
|
||||
var counts = await _personRepository.AsQueryable()
|
||||
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
||||
.Where((p, r) => r.EventId == eventId && (r.PaymentStatus == 1 || r.RegistrationStatus == 2))
|
||||
.GroupBy(p => new { p.ProjectId, p.GroupName })
|
||||
.Select(p => new { p.ProjectId, p.GroupName, Count = SqlFunc.AggregateCount(p.Id) })
|
||||
.Where((p, r) => r.EventId == eventId
|
||||
&& r.RegistrationStatus != 4 // 排除已取消的报名
|
||||
&& !p.DeleteMark // 排除已删除的人员
|
||||
&& p.PersonType == PersonType.Athlete // 只统计运动员
|
||||
)
|
||||
.GroupBy((p, r) => new { p.ProjectId, p.GroupName })
|
||||
.Select((p, r) => new { p.ProjectId, p.GroupName, Count = SqlFunc.AggregateCount(p.Id) })
|
||||
.ToListAsync();
|
||||
|
||||
var result = new List<ArrangementProjectListOutput>();
|
||||
@ -155,10 +159,13 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
|
||||
int venueCount = proj.VenueCount > 0 ? proj.VenueCount : 1;
|
||||
|
||||
// 获取参赛人员(排序:组别 -> ID)
|
||||
// 放宽查询条件,只要不是取消状态的报名都包含进来
|
||||
// 查询条件:不是取消状态的报名,并且只统计运动员
|
||||
var persons = await _personRepository.AsQueryable()
|
||||
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
||||
.Where((p, r) => p.ProjectId == eventProj.Id && r.RegistrationStatus != 4)
|
||||
.Where((p, r) => p.ProjectId == eventProj.Id
|
||||
&& r.RegistrationStatus != 4
|
||||
&& !p.DeleteMark
|
||||
&& p.PersonType == PersonType.Athlete)
|
||||
.OrderBy(p => p.GroupName)
|
||||
.OrderBy(p => p.Id)
|
||||
.Select((p, r) => new {
|
||||
|
||||
@ -156,9 +156,6 @@ public class EventService : BaseService<EventInfo>, ITransient
|
||||
output.Units = units.OrderBy(x => x.Sort).Select(x => x.Adapt<EventUnitDto>()).ToList();
|
||||
}
|
||||
|
||||
// 2. 自定义字段配置 (JSON)
|
||||
// output.CustomConfigJson 已自动映射
|
||||
|
||||
// 3. 获取区域名称和Code
|
||||
if (output.ProvinceId > 0)
|
||||
{
|
||||
@ -179,24 +176,6 @@ public class EventService : BaseService<EventInfo>, ITransient
|
||||
output.DistrictName = d?.DistrictName;
|
||||
}
|
||||
|
||||
// 4. 获取商户信息并填充到FeeConfig
|
||||
if (eventInfo.MerchantId.HasValue && eventInfo.MerchantId.Value > 0)
|
||||
{
|
||||
var merchant = await _merchantRepository.GetByIdAsync(eventInfo.MerchantId.Value);
|
||||
if (merchant != null)
|
||||
{
|
||||
if (output.FeeConfig == null)
|
||||
{
|
||||
output.FeeConfig = new EventFeeConfigInput();
|
||||
}
|
||||
output.FeeConfig.MerchantId = merchant.Id;
|
||||
output.FeeConfig.MerchantName = merchant.MerchantName;
|
||||
output.FeeConfig.CreditCode = merchant.CreditCode;
|
||||
output.FeeConfig.BankName = merchant.BankName;
|
||||
output.FeeConfig.AccountNumber = merchant.AccountNumber;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 获取项目列表 (Step 3)
|
||||
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == id);
|
||||
if (eventProjects.Any())
|
||||
@ -271,7 +250,7 @@ public class EventService : BaseService<EventInfo>, ITransient
|
||||
if (feeConfig != null)
|
||||
{
|
||||
output.FeeConfig = feeConfig.Adapt<EventFeeConfigInput>();
|
||||
// 获取商户详情(使用 EventInfo.MerchantId)
|
||||
// 获取商户详情
|
||||
if (eventInfo.MerchantId.HasValue && eventInfo.MerchantId.Value > 0)
|
||||
{
|
||||
var merchant = await _merchantRepository.GetByIdAsync(eventInfo.MerchantId.Value);
|
||||
@ -281,6 +260,8 @@ public class EventService : BaseService<EventInfo>, ITransient
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -371,9 +352,9 @@ public class EventService : BaseService<EventInfo>, ITransient
|
||||
eventInfo.Status = AvailabilityStatus.Enable;
|
||||
|
||||
// 保存商户ID到EventInfo表
|
||||
if (input.FeeConfig != null && input.FeeConfig.MerchantId.HasValue)
|
||||
if (input.FeeConfig != null)
|
||||
{
|
||||
eventInfo.MerchantId = input.FeeConfig.MerchantId.Value;
|
||||
eventInfo.MerchantId = input.FeeConfig.MerchantId;
|
||||
}
|
||||
|
||||
// 自动生成二维码URL (模拟)
|
||||
@ -534,9 +515,9 @@ public class EventService : BaseService<EventInfo>, ITransient
|
||||
eventInfo.DistrictId = districtId;
|
||||
|
||||
// 保存商户ID到EventInfo表
|
||||
if (input.FeeConfig != null && input.FeeConfig.MerchantId.HasValue)
|
||||
if (input.FeeConfig != null)
|
||||
{
|
||||
eventInfo.MerchantId = input.FeeConfig.MerchantId.Value;
|
||||
eventInfo.MerchantId = input.FeeConfig.MerchantId;
|
||||
}
|
||||
|
||||
eventInfo.UpdatedTime = DateTime.Now;
|
||||
@ -679,20 +660,20 @@ public class EventService : BaseService<EventInfo>, ITransient
|
||||
.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
|
||||
.Select((e, f, m) => new EventExportData
|
||||
{
|
||||
e.Id,
|
||||
e.EventName,
|
||||
e.EventType,
|
||||
e.ChargeMode,
|
||||
e.RegistrationType,
|
||||
e.EventStatus,
|
||||
e.RegistrationStartTime,
|
||||
e.RegistrationEndTime,
|
||||
e.MaterialUploadEndTime,
|
||||
e.MatchStartTime,
|
||||
e.MatchEndTime,
|
||||
e.DetailAddress,
|
||||
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
|
||||
});
|
||||
|
||||
@ -700,40 +681,107 @@ public class EventService : BaseService<EventInfo>, ITransient
|
||||
var eventIds = list.Select(x => x.Id).ToList();
|
||||
var units = await _unitRepository.GetListAsync(x => eventIds.Contains(x.EventId));
|
||||
|
||||
var exportData = new List<Dictionary<string, object>>();
|
||||
foreach (var item in list)
|
||||
// 使用 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)>
|
||||
{
|
||||
var row = new Dictionary<string, object>();
|
||||
var fields = input.ExportFields ?? new List<string>();
|
||||
bool all = fields.Count == 0;
|
||||
|
||||
("赛事名称", 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));
|
||||
|
||||
if (all || fields.Contains("赛事名称")) row["赛事名称"] = item.EventName;
|
||||
if (all || fields.Contains("赛事类型")) row["赛事类型"] = item.EventType.ToString(); // TODO: Enum Description
|
||||
if (all || fields.Contains("收费模式")) row["收费模式"] = item.ChargeMode.ToString();
|
||||
if (all || fields.Contains("收款商户")) row["收款商户"] = item.MerchantName;
|
||||
if (all || fields.Contains("报名方式")) row["报名方式"] = item.RegistrationType.ToString();
|
||||
if (all || fields.Contains("赛事状态")) row["赛事状态"] = item.EventStatus.ToString();
|
||||
if (all || fields.Contains("报名起止时间")) row["报名起止时间"] = $"{item.RegistrationStartTime:yyyy-MM-dd HH:mm} - {item.RegistrationEndTime:yyyy-MM-dd HH:mm}";
|
||||
if (all || fields.Contains("上传素材截至时间")) row["上传素材截至时间"] = item.MaterialUploadEndTime?.ToString("yyyy-MM-dd HH:mm");
|
||||
if (all || fields.Contains("比赛起止时间")) row["比赛起止时间"] = $"{item.MatchStartTime:yyyy-MM-dd HH:mm} - {item.MatchEndTime:yyyy-MM-dd HH:mm}";
|
||||
if (all || fields.Contains("赛事地址")) row["赛事地址"] = item.DetailAddress;
|
||||
if (all || fields.Contains("主办单位")) row["主办单位"] = hostNames;
|
||||
if (all || fields.Contains("承办单位")) row["承办单位"] = organizerNames;
|
||||
if (all || fields.Contains("协办单位")) row["协办单位"] = coOrganizerNames;
|
||||
if (all || fields.Contains("赞助单位")) row["赞助单位"] = sponsorNames;
|
||||
|
||||
exportData.Add(row);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
stream.SaveAs(exportData);
|
||||
return stream.ToArray();
|
||||
|
||||
// 自动调整列宽
|
||||
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>
|
||||
@ -957,6 +1005,31 @@ public class EventService : BaseService<EventInfo>, ITransient
|
||||
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 (Math.Round(input.FeeConfig.Price, 2) != input.FeeConfig.Price)
|
||||
{
|
||||
throw new InvalidOperationException("价格格式不正确,最多保留两位小数");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 验证单价(项目)模式下的项目单价
|
||||
if (input.FeeConfig != null && input.FeeConfig.ChargeMode == ChargeMode.UnitPriceProject)
|
||||
{
|
||||
|
||||
@ -478,6 +478,20 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
}
|
||||
}
|
||||
|
||||
// 验证3:调用检查报名限制API,确保所有限制都满足
|
||||
var checkInput = new RegistrationLimitCheckInput
|
||||
{
|
||||
EventId = input.EventId,
|
||||
MemberIds = input.MemberIds ?? new List<long>(),
|
||||
GroupIds = input.GroupMembers?.Select(g => g.GroupId).Distinct().ToList() ?? new List<long>()
|
||||
};
|
||||
var checkResult = await CheckRegistrationLimit(checkInput);
|
||||
if (checkResult.HasLimit)
|
||||
{
|
||||
var firstLimit = checkResult.LimitDetails.FirstOrDefault();
|
||||
throw Oops.Bah(firstLimit?.LimitMessage ?? "报名限制检查失败");
|
||||
}
|
||||
|
||||
var registration = await _registrationRepository.GetFirstAsync(x => x.EventId == input.EventId && x.CreatedUserId == userId && !x.DeleteMark);
|
||||
|
||||
if (registration == null)
|
||||
@ -493,7 +507,7 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
ContactName = userName,
|
||||
ContactPhone = userMobile,
|
||||
OrderNo = "",
|
||||
RegistrationMode = 1
|
||||
RegistrationMode = 2 // PC端只做团队报名
|
||||
};
|
||||
await _registrationRepository.InsertAsync(registration);
|
||||
}
|
||||
@ -1222,14 +1236,75 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
};
|
||||
|
||||
var limit = await _limitRepository.GetFirstAsync(x => x.EventId == input.EventId);
|
||||
if (limit != null)
|
||||
|
||||
// 获取该赛事的项目级限报配置
|
||||
var projectLimits = await _projectLimitRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark);
|
||||
|
||||
// 获取所有赛事项目,用于关联
|
||||
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId);
|
||||
|
||||
// 获取所有基础项目信息(用于获取项目名称)
|
||||
var projectIds = eventProjects.Select(ep => ep.ProjectId).Distinct().ToList();
|
||||
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && !x.DeleteMark);
|
||||
|
||||
// 获取所有赛事项目组
|
||||
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark);
|
||||
|
||||
// 检查项目级限报(每个项目下的报名队伍数量)
|
||||
if (input.GroupIds != null && input.GroupIds.Any())
|
||||
{
|
||||
foreach (var groupId in input.GroupIds)
|
||||
{
|
||||
var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == groupId);
|
||||
if (eventProjectGroup == null) continue;
|
||||
|
||||
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId);
|
||||
if (eventProject == null) continue;
|
||||
|
||||
var projectLimit = projectLimits.FirstOrDefault(pl => pl.ProjectId == eventProject.ProjectId);
|
||||
if (projectLimit == null) continue;
|
||||
|
||||
// 统计该项目下已报名的队伍数量
|
||||
var currentTeams = await _registrationItemRepository.AsQueryable()
|
||||
.Where(x => x.EventProjectGroupId == eventProjectGroup.Id && !x.DeleteMark)
|
||||
.Select(x => x.RegistrationId)
|
||||
.Distinct()
|
||||
.CountAsync();
|
||||
|
||||
if (currentTeams >= projectLimit.LimitCount)
|
||||
{
|
||||
var project = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
|
||||
var projectName = project?.Name ?? "未知项目";
|
||||
|
||||
result.HasLimit = true;
|
||||
result.LimitDetails.Add(new RegistrationLimitDetail
|
||||
{
|
||||
MemberId = 0,
|
||||
MemberName = eventProjectGroup.GroupName ?? "未知组别",
|
||||
LimitType = "ProjectLimit",
|
||||
LimitMessage = $"项目\"{projectName}\"的组别\"{eventProjectGroup.GroupName}\"已报名{currentTeams}队,达到限报{projectLimit.LimitCount}队"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查个人项目限报和团队项目限报
|
||||
if (limit != null && input.MemberIds != null && input.MemberIds.Any())
|
||||
{
|
||||
var members = await _userRepository.GetListAsync(x => input.MemberIds.Contains(x.Id));
|
||||
// 先查询该赛事的所有报名记录
|
||||
var eventRegistrations = await _registrationRepository.GetListAsync(x =>
|
||||
x.EventId == input.EventId &&
|
||||
x.RegistrationStatus != 4 &&
|
||||
!x.DeleteMark);
|
||||
var eventRegistrationIds = eventRegistrations.Select(r => r.Id).ToList();
|
||||
|
||||
// 只查询该赛事的报名人员
|
||||
var registrationPersons = await _registrationPersonRepository.GetListAsync(x =>
|
||||
eventRegistrationIds.Contains(x.RegistrationId) && !x.DeleteMark);
|
||||
|
||||
foreach (var member in members)
|
||||
{
|
||||
var registrationPersons = await _registrationPersonRepository.GetListAsync(x =>
|
||||
!x.DeleteMark);
|
||||
|
||||
var matchedPersons = registrationPersons.Where(x => x.IdCard == member.IdCardNumber && !string.IsNullOrEmpty(x.IdCard)).ToList();
|
||||
|
||||
var registrationIds = matchedPersons.Select(x => x.RegistrationId).Distinct().ToList();
|
||||
@ -1239,7 +1314,11 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
x.RegistrationStatus != 4 &&
|
||||
!x.DeleteMark);
|
||||
|
||||
if (registrations.Count() >= limit.IndividualProjectLimit)
|
||||
// 统计该队员已报名的项目数量(不区分个人/团队)
|
||||
var registeredProjectCount = registrations.Count();
|
||||
|
||||
// 检查个人项目限报
|
||||
if (registeredProjectCount >= limit.IndividualProjectLimit)
|
||||
{
|
||||
result.HasLimit = true;
|
||||
result.LimitDetails.Add(new RegistrationLimitDetail
|
||||
@ -1247,7 +1326,20 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
MemberId = member.Id,
|
||||
MemberName = member.Name ?? member.NickName ?? "未知",
|
||||
LimitType = "IndividualLimit",
|
||||
LimitMessage = $"该队员已报名{registrations.Count()}个项目,达到个人报名限制{limit.IndividualProjectLimit}个"
|
||||
LimitMessage = $"该队员已报名{registeredProjectCount}个项目,达到个人报名限制{limit.IndividualProjectLimit}个"
|
||||
});
|
||||
}
|
||||
|
||||
// 检查团队项目限报(PC端只做团队报名)
|
||||
if (registeredProjectCount >= limit.TeamProjectLimit)
|
||||
{
|
||||
result.HasLimit = true;
|
||||
result.LimitDetails.Add(new RegistrationLimitDetail
|
||||
{
|
||||
MemberId = member.Id,
|
||||
MemberName = member.Name ?? member.NickName ?? "未知",
|
||||
LimitType = "TeamLimit",
|
||||
LimitMessage = $"该队员已报名{registeredProjectCount}个团队项目,达到团队报名限制{limit.TeamProjectLimit}个"
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1266,6 +1358,12 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
var result = new List<GroupTeamLimitOutput>();
|
||||
|
||||
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventId == eventId && !x.DeleteMark);
|
||||
|
||||
// 获取该赛事的项目级限报配置
|
||||
var projectLimits = await _projectLimitRepository.GetListAsync(x => x.EventId == eventId && !x.DeleteMark);
|
||||
|
||||
// 获取所有赛事项目,用于关联 ProjectId
|
||||
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId);
|
||||
|
||||
foreach (var eventProjectGroup in eventProjectGroups)
|
||||
{
|
||||
@ -1275,10 +1373,15 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
.Distinct()
|
||||
.CountAsync();
|
||||
|
||||
// 查找该组别的项目限报配置
|
||||
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId);
|
||||
var projectLimit = projectLimits.FirstOrDefault(pl => pl.ProjectId == eventProject?.ProjectId);
|
||||
var maxTeams = projectLimit?.LimitCount ?? 2; // 默认2队
|
||||
|
||||
result.Add(new GroupTeamLimitOutput
|
||||
{
|
||||
GroupId = eventProjectGroup.Id,
|
||||
MaxTeams = 2,
|
||||
MaxTeams = maxTeams,
|
||||
CurrentTeams = currentTeams
|
||||
});
|
||||
}
|
||||
|
||||
@ -61,15 +61,16 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
|
||||
[UnitOfWork]
|
||||
public async Task<string> Create(CreateOrderInput input)
|
||||
{
|
||||
// 1. Create Registration
|
||||
// 1. 创建报名记录
|
||||
var entity = input.Adapt<EventRegistration>();
|
||||
entity.Id = _idGenerator.NextId();
|
||||
entity.OrderNo = DateTime.Now.ToString("yyyyMMdd") + new Random().Next(1000, 9999); // Simple generation
|
||||
entity.RegistrationStatus = 0; // Pending
|
||||
entity.PaymentStatus = 0; // Unpaid
|
||||
entity.OrderNo = DateTime.Now.ToString("yyyyMMdd") + new Random().Next(1000, 9999); // 简单生成
|
||||
entity.RegistrationStatus = 0; // 待提交
|
||||
entity.PaymentStatus = 0; // 未支付
|
||||
entity.RegistrationTime = DateTime.Now;
|
||||
entity.RegistrationMode = 2; // 后台管理页面只做团队报名
|
||||
|
||||
// Calculate Total
|
||||
// 计算总金额
|
||||
decimal totalAmount = 0;
|
||||
int totalPerson = 0;
|
||||
if (input.Items != null)
|
||||
@ -85,7 +86,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
|
||||
|
||||
await _registrationRepository.InsertAsync(entity);
|
||||
|
||||
// 2. Create Items
|
||||
// 2. 创建报名项目
|
||||
var createdItems = new List<EventRegistrationItem>();
|
||||
if (input.Items != null && input.Items.Any())
|
||||
{
|
||||
@ -100,7 +101,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
|
||||
createdItems = items;
|
||||
}
|
||||
|
||||
// 3. Create Persons
|
||||
// 3. 创建报名人员
|
||||
if (input.Persons != null && input.Persons.Any())
|
||||
{
|
||||
var persons = input.Persons.Adapt<List<EventRegistrationPerson>>();
|
||||
@ -109,20 +110,20 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
|
||||
x.Id = _idGenerator.NextId();
|
||||
x.RegistrationId = entity.Id;
|
||||
|
||||
// Link to Item based on ProjectId and GroupName
|
||||
// 根据项目ID和组别名称关联到报名项目
|
||||
var linkedItem = createdItems.FirstOrDefault(i => i.ProjectId == x.ProjectId && i.GroupName == x.GroupName);
|
||||
if (linkedItem != null)
|
||||
{
|
||||
x.RegistrationItemId = linkedItem.Id;
|
||||
}
|
||||
|
||||
// Ensure GroupName is not null (DB Constraint)
|
||||
// 确保组别名称不为空(数据库约束)
|
||||
if (string.IsNullOrEmpty(x.GroupName)) x.GroupName = "";
|
||||
});
|
||||
await _personRepository.InsertRangeAsync(persons);
|
||||
}
|
||||
|
||||
// 4. Create Order (Income)
|
||||
// 4. 创建订单(收入)
|
||||
// 获取赛事关联的商户ID
|
||||
var eventInfo = await _eventRepository.GetByIdAsync(input.EventId);
|
||||
|
||||
@ -134,17 +135,17 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
|
||||
{
|
||||
Id = _idGenerator.NextId(),
|
||||
OrderNo = entity.OrderNo,
|
||||
OrderType = 1, // Income
|
||||
PaymentMethod = 1, // Default Online? Or unknown yet.
|
||||
RegistrationMode = input.RegistrationMode,
|
||||
OrderType = 1, // 收入
|
||||
PaymentMethod = 1, // 默认线上支付
|
||||
RegistrationMode = 2, // 后台管理页面只做团队报名
|
||||
MerchantId = eventInfo?.MerchantId, // 设置商户ID(支付商户)
|
||||
MerchantCertificationId = certification?.Id, // 设置单位认证ID
|
||||
EventId = input.EventId,
|
||||
RegistrationId = entity.Id,
|
||||
OriginalAmount = totalAmount,
|
||||
ActualAmount = totalAmount, // Before discount
|
||||
ActualAmount = totalAmount, // 折扣前金额
|
||||
DiscountAmount = 0,
|
||||
OrderStatus = OrderStatus.Pending, // Pending
|
||||
OrderStatus = OrderStatus.Pending, // 待支付
|
||||
CreatedTime = DateTime.Now,
|
||||
CreatedUserId = entity.CreatedUserId
|
||||
};
|
||||
@ -169,7 +170,7 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
|
||||
.LeftJoin<EventInfo>((o, e) => o.EventId == e.Id)
|
||||
.LeftJoin<YD_Event.Core.Entities.Merchant>((o, e, m) => o.MerchantId == m.Id)
|
||||
.LeftJoin<EventRegistration>((o, e, m, r) => o.RegistrationId == r.Id)
|
||||
.LeftJoin<YD_Event.Core.Entities.Order>((o, e, m, r, ro) => o.RelatedOrderId == ro.Id) // Join self for Related Order No
|
||||
.LeftJoin<YD_Event.Core.Entities.Order>((o, e, m, r, ro) => o.RelatedOrderId == ro.Id) // 自连接获取关联订单编号
|
||||
// 默认只显示已完成支付流程的订单(已支付、已发货、已完成、已退款)
|
||||
.WhereIF(!input.OrderStatus.HasValue, (o, e, m, r, ro) =>
|
||||
o.OrderStatus == OrderStatus.Paid ||
|
||||
@ -246,10 +247,10 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
|
||||
var items = await _itemRepository.AsQueryable().Where(x => x.RegistrationId == registrationId).ToListAsync();
|
||||
var persons = await _personRepository.AsQueryable().Where(x => x.RegistrationId == registrationId).ToListAsync();
|
||||
|
||||
// Convert items and persons to Dto
|
||||
// 将项目和人员转换为DTO
|
||||
var itemDtos = items.Select(x => new OrderItemDto
|
||||
{
|
||||
ProjectName = x.ProjectName, // Assume stored or join
|
||||
ProjectName = x.ProjectName, // 假设已存储或通过关联获取
|
||||
GroupName = x.GroupName,
|
||||
Fee = x.TotalPrice
|
||||
}).ToList();
|
||||
@ -290,15 +291,15 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
|
||||
var order = await _orderRepository.GetByIdAsync(input.Id);
|
||||
if (order == null) throw Oops.Bah("订单不存在");
|
||||
|
||||
// Update Order
|
||||
order.OrderStatus = input.PaymentStatus == 1 ? OrderStatus.Paid : OrderStatus.Pending; // Assuming 1 is paid/success
|
||||
// 更新订单
|
||||
order.OrderStatus = input.PaymentStatus == 1 ? OrderStatus.Paid : OrderStatus.Pending; // 假设1表示支付成功
|
||||
if (input.PaymentStatus == 1)
|
||||
{
|
||||
order.PaymentTime = DateTime.Now;
|
||||
}
|
||||
await _orderRepository.UpdateAsync(order);
|
||||
|
||||
// Update Registration sync
|
||||
// 同步更新报名记录
|
||||
var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId);
|
||||
if (reg != null)
|
||||
{
|
||||
@ -337,34 +338,34 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
|
||||
|
||||
if (order.OrderStatus != OrderStatus.Paid) throw Oops.Bah("订单未支付,无法退款");
|
||||
|
||||
// 1. Create Refund Order
|
||||
// 1. 创建退款订单
|
||||
var refundOrder = new YD_Event.Core.Entities.Order
|
||||
{
|
||||
Id = _idGenerator.NextId(),
|
||||
OrderNo = "REF" + DateTime.Now.ToString("yyyyMMddHHmmss"),
|
||||
OrderType = 2, // Refund
|
||||
OrderType = 2, // 退款
|
||||
PaymentMethod = order.PaymentMethod,
|
||||
RegistrationMode = order.RegistrationMode,
|
||||
MerchantId = order.MerchantId,
|
||||
MerchantCertificationId = order.MerchantCertificationId, // 继承原订单的单位认证ID
|
||||
EventId = order.EventId,
|
||||
RegistrationId = order.RegistrationId,
|
||||
RelatedOrderId = order.Id, // Link to original
|
||||
RelatedOrderId = order.Id, // 关联原订单
|
||||
OriginalAmount = order.ActualAmount,
|
||||
ActualAmount = input.RefundAmount, // Refund Amount
|
||||
OrderStatus = OrderStatus.Refunded, // Success (Refunded)
|
||||
ActualAmount = input.RefundAmount, // 退款金额
|
||||
OrderStatus = OrderStatus.Refunded, // 退款成功
|
||||
PaymentTime = DateTime.Now,
|
||||
Remark = input.Reason,
|
||||
CreatedTime = DateTime.Now,
|
||||
CreatedUserId = _idGenerator.NewLong() // System or current user
|
||||
CreatedUserId = _idGenerator.NewLong() // 系统或当前用户
|
||||
};
|
||||
await _orderRepository.InsertAsync(refundOrder);
|
||||
|
||||
// 2. Update Registration Status
|
||||
// 2. 更新报名状态
|
||||
var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId);
|
||||
if (reg != null)
|
||||
{
|
||||
reg.PaymentStatus = 3; // Refunded
|
||||
reg.PaymentStatus = 3; // 已退款
|
||||
await _registrationRepository.UpdateAsync(reg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3541,6 +3541,11 @@
|
||||
导出字段列表(如:赛事名称,赛事类型...)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.EventExportData">
|
||||
<summary>
|
||||
赛事导出数据
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.EventListOutput">
|
||||
<summary>
|
||||
赛事列表输出
|
||||
@ -3911,6 +3916,11 @@
|
||||
银行账号 (显示字段)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.EventFeeConfigInput.BusinessLicenseUrl">
|
||||
<summary>
|
||||
营业执照图片URL (显示字段)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.UpdateEventInput">
|
||||
<summary>
|
||||
更新赛事输入
|
||||
@ -6272,6 +6282,11 @@
|
||||
<param name="id"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.EventService.ValidateEventTime(System.DateTime,System.DateTime,System.DateTime,System.DateTime)">
|
||||
<summary>
|
||||
验证赛事时间逻辑
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.EventService.Add(YD_Event.Application.Event.Dtos.AddEventInput)">
|
||||
<summary>
|
||||
新增赛事
|
||||
@ -6293,6 +6308,11 @@
|
||||
<param name="input"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.EventService.GetEnumDescription(System.Object)">
|
||||
<summary>
|
||||
获取枚举描述
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.EventService.Delete(YD_Event.Core.Shared.KeyDto)">
|
||||
<summary>
|
||||
删除赛事
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user