赛事取消报名逻辑优化

This commit is contained in:
马梓彭 2026-05-09 09:47:41 +08:00
parent e0e4f02f9e
commit 7ccd2e22e5
2 changed files with 167 additions and 194 deletions

View File

@ -2809,52 +2809,16 @@ public class UnifiedRegistrationService : ITransient
// 全部取消
if (input.CancelType == 0)
{
// 对于定价赛事,需要判断是否有团队项目
if (eventInfo.ChargeMode == ChargeMode.FixedPrice)
{
// 检查是否有团队项目(项目类型名称包含"团队")
bool hasTeamProject = items.Any(item => item.ProjectTypeName.Contains("团队"));
_logger.LogInformation("定价赛事,hasTeamProject={HasTeamProject}", hasTeamProject);
// 如果没有团队项目,个人取消不退款
if (!hasTeamProject)
{
_logger.LogInformation("定价赛事且无团队项目,个人取消不退款");
return 0;
}
}
var fullRefundAmount = await CalculateRefundAmountAsync(remainingOriginalAmount, eventInfo.MatchStartTime);
_logger.LogInformation("全部取消,退款金额={Amount}", fullRefundAmount);
return fullRefundAmount;
}
// 部分取消
// 【重要】退款基本原则
// - 付款时已对报名人员进行去重处理,即同一运动员报名多个项目仅收取一份费用
// - 退款时需遵循相同原则:仅当某运动员的所有已报名项目均被取消时,方可进行退款
// - 若运动员报名多个项目但仅取消部分项目,则不进行退款
_logger.LogInformation("开始执行新退款规则:仅当运动员所有项目均被取消时方可退款");
// 收集所有取消的队员ID
var cancelMemberIds = input.PersonIds ?? new List<long>();
var cancelTeams = input.CancelTeams ?? new List<string>();
// 第一步:构建每个运动员的报名项目映射
var athleteProjects = new Dictionary<long, List<long>>();
foreach (var person in persons)
{
if (!athleteProjects.ContainsKey(person.Id))
{
athleteProjects[person.Id] = new List<long>();
}
athleteProjects[person.Id].Add(person.RegistrationItemId);
}
_logger.LogInformation("运动员报名项目统计:共{Count}名运动员", athleteProjects.Count);
// 第二步:收集所有要取消的队员
var allCancelIds = new HashSet<long>(cancelMemberIds);
foreach (var teamKey in cancelTeams)
{
@ -2871,184 +2835,106 @@ public class UnifiedRegistrationService : ITransient
_logger.LogInformation("本次取消的队员数量:{Count}", allCancelIds.Count);
// 第三步:识别符合退款条件的运动员(所有项目均被取消的运动员)
var eligibleAthletes = new List<long>();
// 首先,构建团队取消映射
var canceledItemIds = new HashSet<long>();
foreach (var teamKey in cancelTeams)
{
var parts = teamKey.Split('_');
if (parts.Length == 2 && long.TryParse(parts[0], out var registrationItemId))
{
canceledItemIds.Add(registrationItemId);
}
}
foreach (var kvp in athleteProjects)
{
var athleteId = kvp.Key;
var athleteProjectIds = kvp.Value;
_logger.LogInformation("检查运动员ID={AthleteId}, 报名项目数={Count}", athleteId, athleteProjectIds.Count);
// 检查该运动员的所有项目是否都被取消了
bool allProjectsCanceled = true;
foreach (var projectId in athleteProjectIds)
{
// 检查该项目是否被取消:
// 1. 该项目的团队是否被取消
// 2. 或者该运动员在该项目中的个人报名是否被取消
bool projectCanceled = false;
// 检查团队是否被取消
if (canceledItemIds.Contains(projectId))
{
projectCanceled = true;
_logger.LogInformation("运动员ID={AthleteId}, 项目ID={ProjectId}:团队被取消", athleteId, projectId);
}
else
{
// 检查该运动员在该项目中的个人报名是否被取消
// 获取该运动员在该项目中的队员记录
var memberInProject = persons.FirstOrDefault(p => p.Id == athleteId && p.RegistrationItemId == projectId);
if (memberInProject != null && cancelMemberIds.Contains(memberInProject.Id))
{
projectCanceled = true;
_logger.LogInformation("运动员ID={AthleteId}, 项目ID={ProjectId}:个人报名被取消", athleteId, projectId);
}
}
if (!projectCanceled)
{
allProjectsCanceled = false;
_logger.LogInformation("运动员ID={AthleteId}, 项目ID={ProjectId}:未被取消", athleteId, projectId);
break;
}
}
if (allProjectsCanceled && athleteProjectIds.Count > 0)
{
eligibleAthletes.Add(athleteId);
_logger.LogInformation("运动员ID={AthleteId}:所有项目均被取消,符合退款条件", athleteId);
}
else
{
_logger.LogInformation("运动员ID={AthleteId}:部分项目未取消,不符合退款条件", athleteId);
}
}
_logger.LogInformation("符合退款条件的运动员数量:{Count}", eligibleAthletes.Count);
// 第四步:根据不同收费模式计算退款金额
var baseRefundAmount = await CalculateRefundAmountAsync(remainingOriginalAmount, eventInfo.MatchStartTime);
var refundRatio = remainingOriginalAmount > 0 ? baseRefundAmount / remainingOriginalAmount : 1;
switch (eventInfo.ChargeMode)
{
case ChargeMode.FixedPrice:
// 【定价(赛事)退款规则】
// - 全队取消场景:严格按照既定退款规则执行退款操作
// - 个人取消场景:不进行退款(仅当所有项目均取消时才退款)
// - 全队取消:严格按照预设退款规则执行退款
// - 个人取消:不退款
// - 仅当所有项目取消时才启动退款,单个项目取消不退款
_logger.LogInformation("定价(赛事)退款:开始计算");
// 首先检查是否有团队被取消
bool hasTeamCanceled = cancelTeams.Any();
// 检查是否取消了所有队员(所有项目取消)
var allPersonsCanceled = allCancelIds.Count == persons.Count;
if (!hasTeamCanceled)
if (!allPersonsCanceled)
{
// 没有取消任何团队,可能只是取消了个人
_logger.LogInformation("定价赛事部分取消,未取消团队,检查是否有符合条件的运动员");
// 检查是否有团队项目
bool hasTeamProject = items.Any(item => item.ProjectTypeName.Contains("团队"));
if (!hasTeamProject)
{
// 没有团队项目,个人取消不退款
_logger.LogInformation("定价赛事没有团队项目,不退款");
return 0;
}
}
// 如果有符合退款条件的运动员,按照退款规则计算退款金额
if (eligibleAthletes.Count > 0)
{
// 定价赛事中,符合条件的运动员退款按照退款规则计算
var fixedPriceRefundAmount = await CalculateRefundAmountAsync(remainingOriginalAmount, eventInfo.MatchStartTime);
_logger.LogInformation("定价(赛事)退款:符合条件的运动员数={Count}, 退款金额={Amount}",
eligibleAthletes.Count, fixedPriceRefundAmount);
return fixedPriceRefundAmount;
}
_logger.LogInformation("定价(赛事)退款:没有符合条件的运动员,不退款");
return 0;
case ChargeMode.UnitPriceEvent:
// 【赛事单价退款计算规则】
// - 全队取消:严格按照既定退款规则执行退款操作
// - 个人取消:计算公式为【赛事单价 × 符合退款条件的取消人数】,再根据退款规则计算最终退款金额
_logger.LogInformation("赛事单价退款:开始计算");
if (eligibleAthletes.Count == 0)
{
_logger.LogInformation("赛事单价退款:没有符合条件的运动员,不退款");
// 没有取消所有队员,个人取消不退款
_logger.LogInformation("定价赛事部分取消(未取消所有人员),个人取消不退款");
return 0;
}
var unitPriceEvent = persons.Count > 0 ? remainingOriginalAmount / persons.Count : 0;
var unitPriceEventAmount = unitPriceEvent * eligibleAthletes.Count * refundRatio;
// 取消了所有队员,按退款规则退款
var fixedPriceRefundAmount = await CalculateRefundAmountAsync(remainingOriginalAmount, eventInfo.MatchStartTime);
_logger.LogInformation("定价赛事全部取消,按退款规则退款,金额={Amount}", fixedPriceRefundAmount);
return fixedPriceRefundAmount;
case ChargeMode.UnitPriceEvent:
// 【单价(赛事)退款规则】
// - 全队取消:严格按照退款规则执行退款
// - 个人取消:计算公式为【单价(赛事)× 取消人数】,并正确应用退款规则
// - 全额退款:用户取消全部报名项目时按退款规则全额退还费用
// - 部分退款:用户取消部分项目时按取消人数对应费用进行退款
_logger.LogInformation("赛事单价退款:符合条件的运动员数={Count}, 人均单价={UnitPrice}, 退款比例={Ratio}, 计算金额={Amount}",
eligibleAthletes.Count, unitPriceEvent, refundRatio, unitPriceEventAmount);
_logger.LogInformation("单价(赛事)退款:开始计算");
if (allCancelIds.Count == 0)
{
_logger.LogInformation("单价赛事退款:没有取消任何人,不退款");
return 0;
}
// 获取赛事单价(从费用配置中获取)
var feeConfig = await _feeConfigRepository.GetFirstAsync(x => x.EventId == eventInfo.Id && !x.DeleteMark);
var eventUnitPrice = feeConfig?.Price ?? 0;
_logger.LogInformation("单价(赛事)信息:赛事单价={EventUnitPrice}", eventUnitPrice);
// 计算本次取消的原始金额
var unitPriceEventCancelAmount = eventUnitPrice * allCancelIds.Count;
// 直接对本次取消的原始金额应用退款规则
var unitPriceEventFinalAmount = await CalculateRefundAmountAsync(unitPriceEventCancelAmount, eventInfo.MatchStartTime);
// 确保不超过剩余可退款金额
var finalUnitPriceEventAmount = Math.Min(unitPriceEventAmount, baseRefundAmount);
_logger.LogInformation("赛事单价退款:最终金额={FinalAmount}", finalUnitPriceEventAmount);
var maxUnitPriceEventRefund = await CalculateRefundAmountAsync(remainingOriginalAmount, eventInfo.MatchStartTime);
var finalUnitPriceEventAmount = Math.Min(unitPriceEventFinalAmount, maxUnitPriceEventRefund);
_logger.LogInformation("单价(赛事)退款:取消人数={CancelCount}, 赛事单价={UnitPrice}, 本次取消原始金额={CancelAmount}, 按规则退款金额={RefundAmount}, 最终金额={FinalAmount}",
allCancelIds.Count, eventUnitPrice, unitPriceEventCancelAmount, unitPriceEventFinalAmount, finalUnitPriceEventAmount);
return finalUnitPriceEventAmount;
case ChargeMode.UnitPriceProject:
// 【项目单价退款计算规则】
// - 全队取消:严格按照既定退款规则执行退款操作
// - 个人取消:计算公式为【项目A单价 × 项目A符合退款条件的取消人数 + 项目B单价 × 项目B符合退款条件的取消人数】,再根据退款规则计算最终退款金额
// 【单价(项目)退款规则】
// - 全队取消:严格按照退款规则执行退款
// - 个人取消:计算公式为【单价(项目A)× 项目A取消人数 + 单价(项目B)× 项目B取消人数】,并正确应用退款规则
_logger.LogInformation("项目单价退款:开始计算");
_logger.LogInformation("单价(项目)退款:开始计算");
if (eligibleAthletes.Count == 0)
if (allCancelIds.Count == 0)
{
_logger.LogInformation("项目单价退款:没有符合条件的运动员,不退款");
_logger.LogInformation("单价项目退款:没有取消任何人,不退款");
return 0;
}
decimal totalProjectCancelAmount = 0;
// 按项目分组计算符合条件的运动员
// 按项目分组计算取消的人数和金额
foreach (var item in items)
{
var itemPersons = persons.Where(p => p.RegistrationItemId == item.Id).ToList();
var canceledInItem = itemPersons.Count(p => allCancelIds.Contains(p.Id));
// 统计该项目中符合退款条件的运动员数量
var eligibleInProject = itemPersons.Count(p => eligibleAthletes.Contains(p.Id));
if (eligibleInProject > 0)
if (canceledInItem > 0)
{
var projectEligibleAmount = item.UnitPrice * eligibleInProject;
totalProjectCancelAmount += projectEligibleAmount;
_logger.LogInformation("项目单价退款:项目名称={ProjectName}, 项目单价={UnitPrice}, 符合条件人数={Count}, 本项金额={Amount}",
item.ProjectName, item.UnitPrice, eligibleInProject, projectEligibleAmount);
var projectCancelAmount = item.UnitPrice * canceledInItem;
totalProjectCancelAmount += projectCancelAmount;
_logger.LogInformation("单价(项目)退款:项目={ProjectName}, 单价={UnitPrice}, 取消人数={CancelCount}, 本项原始金额={Amount}",
item.ProjectName, item.UnitPrice, canceledInItem, projectCancelAmount);
}
}
var projectRefundAmount = totalProjectCancelAmount * refundRatio;
_logger.LogInformation("项目单价退款:总符合条件金额={TotalAmount}, 退款比例={Ratio}, 计算金额={Amount}",
totalProjectCancelAmount, refundRatio, projectRefundAmount);
// 直接对本次取消的原始总金额应用退款规则
var projectRefundAmount = await CalculateRefundAmountAsync(totalProjectCancelAmount, eventInfo.MatchStartTime);
// 确保不超过剩余可退款金额
var finalProjectRefundAmount = Math.Min(projectRefundAmount, baseRefundAmount);
_logger.LogInformation("项目单价退款:最终金额={FinalAmount}", finalProjectRefundAmount);
var maxProjectRefund = await CalculateRefundAmountAsync(remainingOriginalAmount, eventInfo.MatchStartTime);
var finalProjectRefundAmount = Math.Min(projectRefundAmount, maxProjectRefund);
_logger.LogInformation("单价(项目)退款:本次取消总原始金额={TotalCancelAmount}, 按规则退款金额={RefundAmount}, 最终金额={FinalAmount}",
totalProjectCancelAmount, projectRefundAmount, finalProjectRefundAmount);
return finalProjectRefundAmount;
default:
@ -3149,18 +3035,19 @@ public class UnifiedRegistrationService : ITransient
_logger.LogInformation("线下支付部分取消:报名ID={RegistrationId}, 选择人数={SelectedCount}, 退款金额={RefundAmount}",
registration.Id, selectedPersons.Count, actualRefundAmount);
await _registrationPersonRepository.DeleteAsync(x => allCancelMemberIds.Contains(x.Id));
_logger.LogInformation("已删除队员:报名ID={RegistrationId}, 队员ID列表={PersonIds}",
// 软删除报名人员
await SoftDeletePersonsAsync(allCancelMemberIds.ToList(), operatorUserId, now);
_logger.LogInformation("已软删除队员:报名ID={RegistrationId}, 队员ID列表={PersonIds}",
registration.Id, string.Join(", ", allCancelMemberIds));
// 检查并删除空的 RegistrationItem(所有成员都被删除的项)
var remainingPersons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registration.Id);
// 检查并软删除空的 RegistrationItem(所有成员都被软删除的项)
var remainingPersons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registration.Id && !x.DeleteMark);
var remainingRegistrationItemIds = new HashSet<long>(remainingPersons.Select(p => p.RegistrationItemId));
var itemsToDelete = items.Where(item => !remainingRegistrationItemIds.Contains(item.Id)).ToList();
if (itemsToDelete.Count > 0)
{
await _registrationItemRepository.DeleteAsync(itemsToDelete);
_logger.LogInformation("已删除空团队:报名ID={RegistrationId}, 团队ID列表={ItemIds}",
await SoftDeleteItemsAsync(itemsToDelete.Select(i => i.Id).ToList(), operatorUserId, now);
_logger.LogInformation("已软删除空团队:报名ID={RegistrationId}, 团队ID列表={ItemIds}",
registration.Id, string.Join(", ", itemsToDelete.Select(i => i.Id)));
}
@ -3191,6 +3078,14 @@ public class UnifiedRegistrationService : ITransient
_logger.LogInformation("线下支付全额取消:报名ID={RegistrationId}, 退款金额={RefundAmount}",
registration.Id, actualRefundAmount);
// 软删除所有报名人员
await SoftDeletePersonsAsync(persons.Select(p => p.Id).ToList(), operatorUserId, now);
_logger.LogInformation("已软删除所有队员:报名ID={RegistrationId}", registration.Id);
// 软删除所有报名项目
await SoftDeleteItemsAsync(items.Select(i => i.Id).ToList(), operatorUserId, now);
_logger.LogInformation("已软删除所有项目:报名ID={RegistrationId}", registration.Id);
registration.PaymentStatus = 4; // 已取消
registration.UpdatedUserId = operatorUserId;
registration.UpdatedTime = now;
@ -3218,18 +3113,19 @@ public class UnifiedRegistrationService : ITransient
_logger.LogInformation("统一部分退款:报名ID={RegistrationId}, 总人数={TotalPersons}, 选择人数={SelectedCount}, 实际退款金额={ActualRefundAmount}",
registration.Id, totalPersons, selectedPersons.Count, actualRefundAmount);
await _registrationPersonRepository.DeleteAsync(x => allCancelMemberIds.Contains(x.Id));
_logger.LogInformation("已删除队员:报名ID={RegistrationId}, 队员ID列表={PersonIds}",
// 软删除报名人员
await SoftDeletePersonsAsync(allCancelMemberIds.ToList(), operatorUserId, now);
_logger.LogInformation("已软删除队员:报名ID={RegistrationId}, 队员ID列表={PersonIds}",
registration.Id, string.Join(", ", allCancelMemberIds));
// 检查并删除空的 RegistrationItem(所有成员都被删除的项)
var remainingPersons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registration.Id);
// 检查并软删除空的 RegistrationItem(所有成员都被软删除的项)
var remainingPersons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registration.Id && !x.DeleteMark);
var remainingRegistrationItemIds = new HashSet<long>(remainingPersons.Select(p => p.RegistrationItemId));
var itemsToDelete = items.Where(item => !remainingRegistrationItemIds.Contains(item.Id)).ToList();
if (itemsToDelete.Count > 0)
{
await _registrationItemRepository.DeleteAsync(itemsToDelete);
_logger.LogInformation("已删除空团队:报名ID={RegistrationId}, 团队ID列表={ItemIds}",
await SoftDeleteItemsAsync(itemsToDelete.Select(i => i.Id).ToList(), operatorUserId, now);
_logger.LogInformation("已软删除空团队:报名ID={RegistrationId}, 团队ID列表={ItemIds}",
registration.Id, string.Join(", ", itemsToDelete.Select(i => i.Id)));
}
@ -3260,6 +3156,14 @@ public class UnifiedRegistrationService : ITransient
_logger.LogInformation("统一全额退款:报名ID={RegistrationId}, 退款金额={ActualRefundAmount}",
registration.Id, actualRefundAmount);
// 软删除所有报名人员
await SoftDeletePersonsAsync(persons.Select(p => p.Id).ToList(), operatorUserId, now);
_logger.LogInformation("已软删除所有队员:报名ID={RegistrationId}", registration.Id);
// 软删除所有报名项目
await SoftDeleteItemsAsync(items.Select(i => i.Id).ToList(), operatorUserId, now);
_logger.LogInformation("已软删除所有项目:报名ID={RegistrationId}", registration.Id);
registration.PaymentStatus = 3;
registration.UpdatedUserId = operatorUserId;
registration.UpdatedTime = now;
@ -3457,6 +3361,36 @@ public class UnifiedRegistrationService : ITransient
refundRecord.Id, outRefundNo);
}
/// <summary>
/// 软删除报名人员
/// </summary>
private async Task SoftDeletePersonsAsync(List<long> personIds, long operatorUserId, DateTime now)
{
var personsToDelete = await _registrationPersonRepository.GetListAsync(x => personIds.Contains(x.Id));
foreach (var person in personsToDelete)
{
person.DeleteMark = true;
person.UpdatedUserId = operatorUserId;
person.UpdatedTime = now;
}
await _registrationPersonRepository.UpdateRangeAsync(personsToDelete);
}
/// <summary>
/// 软删除报名项目
/// </summary>
private async Task SoftDeleteItemsAsync(List<long> itemIds, long operatorUserId, DateTime now)
{
var itemsToDelete = await _registrationItemRepository.GetListAsync(x => itemIds.Contains(x.Id));
foreach (var item in itemsToDelete)
{
item.DeleteMark = true;
item.UpdatedUserId = operatorUserId;
item.UpdatedTime = now;
}
await _registrationItemRepository.UpdateRangeAsync(itemsToDelete);
}
/// <summary>
/// 更新订单状态
/// </summary>
@ -3514,6 +3448,7 @@ public class UnifiedRegistrationService : ITransient
if (isPartialCancel)
{
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == order.RegistrationId);
var items = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == order.RegistrationId);
var totalPersons = persons.Count;
if (totalPersons == 0)
{
@ -3530,11 +3465,23 @@ public class UnifiedRegistrationService : ITransient
_logger.LogInformation("统一后台部分退款:订单ID={OrderId}, 总人数={TotalPersons}, 选择人数={SelectedCount}, 退款金额={RefundAmount}",
order.Id, totalPersons, selectedPersons.Count, input.RefundAmount);
await _registrationPersonRepository.DeleteAsync(x => input.PersonIds.Contains(x.Id));
_logger.LogInformation("已删除队员:订单ID={OrderId}, 队员ID列表={PersonIds}",
// 软删除报名人员
await SoftDeletePersonsAsync(input.PersonIds, currentUserId, now);
_logger.LogInformation("已软删除队员:订单ID={OrderId}, 队员ID列表={PersonIds}",
order.Id, string.Join(", ", input.PersonIds));
// 检查并软删除空的 RegistrationItem(所有成员都被软删除的项)
var remainingPersons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == order.RegistrationId && !x.DeleteMark);
var remainingRegistrationItemIds = new HashSet<long>(remainingPersons.Select(p => p.RegistrationItemId));
var itemsToDelete = items.Where(item => !remainingRegistrationItemIds.Contains(item.Id)).ToList();
if (itemsToDelete.Count > 0)
{
await SoftDeleteItemsAsync(itemsToDelete.Select(i => i.Id).ToList(), currentUserId, now);
_logger.LogInformation("已软删除空团队:订单ID={OrderId}, 团队ID列表={ItemIds}",
order.Id, string.Join(", ", itemsToDelete.Select(i => i.Id)));
}
var remainingPersonsCount = await _registrationPersonRepository.CountAsync(x => x.RegistrationId == order.RegistrationId);
var remainingPersonsCount = remainingPersons.Count;
var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId);
if (reg != null)
{
@ -3579,6 +3526,22 @@ public class UnifiedRegistrationService : ITransient
{
throw Oops.Bah("该订单已存在退款记录");
}
// 软删除所有报名人员
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == order.RegistrationId);
if (persons.Count > 0)
{
await SoftDeletePersonsAsync(persons.Select(p => p.Id).ToList(), currentUserId, now);
_logger.LogInformation("已软删除所有队员:订单ID={OrderId}", order.Id);
}
// 软删除所有报名项目
var items = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == order.RegistrationId);
if (items.Count > 0)
{
await SoftDeleteItemsAsync(items.Select(i => i.Id).ToList(), currentUserId, now);
_logger.LogInformation("已软删除所有项目:订单ID={OrderId}", order.Id);
}
}
var fullRefundOrder = new YD_Event.Core.Entities.Order

View File

@ -8913,6 +8913,16 @@
创建退款记录
</summary>
</member>
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.SoftDeletePersonsAsync(System.Collections.Generic.List{System.Int64},System.Int64,System.DateTime)">
<summary>
软删除报名人员
</summary>
</member>
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.SoftDeleteItemsAsync(System.Collections.Generic.List{System.Int64},System.Int64,System.DateTime)">
<summary>
软删除报名项目
</summary>
</member>
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.UpdateOrderStatusAsync(System.String,YD_Event.Core.Enum.OrderStatus,System.Int64)">
<summary>
更新订单状态