admin/src/Oms.Application/Services/Orders/OrderService.BatchWorker.cs
zzh 223d32f62a
All checks were successful
Build and Deploy / deploy (push) Successful in 1m3s
feat: 订单支付时选券,新增支付预览接口并按所选券扣款核销
- 新增 GET /order/pay-preview:返回会员折/券互斥后的应付拆解与本单可选券
- POST /order/pay 支持 couponInstanceId(null 沿用已绑定券,0 不用券,>0 指定券),按试算应付扣款并核销券、释放被替换券的锁定
- 被当前订单锁定的券视为可用,修复入库/编辑重算营销金额时误报券被占用

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 14:01:00 +08:00

174 lines
6.6 KiB
C#

using Fake.Application;
using Newtonsoft.Json;
using Oms.Application.Dtos.Orders;
using Oms.Application.Labeling;
using Oms.Application.Services.Orders.Issues;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Entities;
namespace Oms.Application.Services.Orders;
public partial class OrderService
{
internal Task PayForBatchAsync(int orderId, CancellationToken cancellationToken = default) =>
PayAsync(new OrderPayInput { Id = orderId });
internal Task PreDeclareForBatchAsync(int orderId, CancellationToken cancellationToken = default) =>
PreDeclareAsync(new OrderActionInput { Id = orderId });
internal Task InterceptForBatchAsync(int orderId, CancellationToken cancellationToken = default) =>
InterceptAsync(new OrderActionInput { Id = orderId });
internal Task CancelInterceptForBatchAsync(int orderId, CancellationToken cancellationToken = default) =>
CancelInterceptAsync(new OrderActionInput { Id = orderId });
internal Task DeleteForBatchAsync(int orderId, CancellationToken cancellationToken = default) =>
DeleteAsync(new OrderActionInput { Id = orderId });
internal Task<OrderPrintOutput> PrintForBatchAsync(int orderId, CancellationToken cancellationToken = default) =>
PrintAsync(new OrderActionInput { Id = orderId });
internal async Task<LabelPdfGenerateResult> GenerateLabelPdfForBatchAsync(
int orderId,
CancellationToken cancellationToken = default)
{
var order = await GetOrderOrThrowAsync(orderId);
var mapped = (await MapOrderOutputsAsync([order])).First();
return await LabelPdfGenerationService.GenerateAndPersistAsync(
order,
mapped,
templateIdOverride: null,
operatorName: null,
operatorUserId: 0,
cancellationToken);
}
internal async Task MarkProblematicForBatchAsync(
int orderId,
string? payloadJson,
int operatorUserId,
CancellationToken cancellationToken = default)
{
var input = DeserializeBatchPayload<OrderMarkProblematicInput>(payloadJson)
?? throw new BusinessException("问题件参数无效");
input.OrderIds = [orderId];
var payload = await ResolveProblematicPayloadAsync(input);
var request = new ProblematicMarkRequest
{
IssueType = payload.IssueType,
Title = payload.Title,
Content = payload.Content,
IssueTemplateId = payload.IssueTemplateId,
NotifyMethod = payload.NotifyMethod,
Remark = payload.Remark,
};
var order = await GetOrderOrThrowAsync(orderId);
var sendNotification = !string.IsNullOrWhiteSpace(request.NotifyMethod);
var reason = await ExceptionWorkflowService.TryMarkProblematicAsync(
order,
request,
operatorUserId,
sendNotification);
if (!string.IsNullOrWhiteSpace(reason))
{
throw new BusinessException(reason);
}
}
internal async Task MarkClaimedForBatchAsync(
int orderId,
string? payloadJson,
CancellationToken cancellationToken = default)
{
var input = DeserializeBatchPayload<OrderMarkClaimedInput>(payloadJson)
?? throw new BusinessException("理赔件参数无效");
var payload = NormalizeClaimedPayload(input);
var order = await GetOrderOrThrowAsync(orderId);
var fromStatus = order.OrderStatus;
var reason = TryApplyClaimed(order, payload);
if (!string.IsNullOrWhiteSpace(reason))
{
throw new BusinessException(reason);
}
await OrderRepository.UpdateAsync(order);
await OrderExceptionEventRepository.InsertAsync(new OrderExceptionEvent
{
OrderId = order.Id,
EventType = OrderExceptionEventTypeCodes.MarkClaimed,
FromStatus = fromStatus,
ToStatus = OrderStatusCodes.Claimed,
FromHandleStatus = null,
ToHandleStatus = OrderHandleStatusCodes.Pending,
ClaimType = order.ClaimType,
ClaimAmount = order.ClaimAmount,
ClaimCurrency = order.ClaimCurrency,
ClaimReason = order.ClaimReason,
Remark = payload.Remark,
});
}
internal async Task ReallocateForBatchAsync(
int orderId,
string? payloadJson,
CancellationToken cancellationToken = default)
{
_ = cancellationToken;
var input = DeserializeBatchPayload<OrderReallocateBatchInput>(payloadJson)
?? throw new BusinessException("重新排货参数无效");
var order = await GetOrderOrThrowAsync(orderId);
await ReallocateCoreAsync(order, input.Scheme, input.ServiceChannelId);
}
internal async Task UpdateHandleStatusForBatchAsync(
int orderId,
string? payloadJson,
CancellationToken cancellationToken = default)
{
var input = DeserializeBatchPayload<OrderHandleStatusUpdateInput>(payloadJson)
?? throw new BusinessException("处理状态参数无效");
var targetStatus = input.HandleStatus?.Trim().ToLowerInvariant();
if (!string.Equals(targetStatus, OrderHandleStatusCodes.Processed, StringComparison.Ordinal))
{
throw new BusinessException("仅支持标记为已处理");
}
var order = await GetOrderOrThrowAsync(orderId);
if (order.OrderStatus is not (OrderStatusCodes.Problematic or OrderStatusCodes.Claimed))
{
throw new BusinessException("仅问题件或理赔件可更新处理状态");
}
if (OrderHandleStatusCodes.IsProcessed(order.HandleStatus))
{
throw new BusinessException("订单已处理");
}
order.HandleStatus = OrderHandleStatusCodes.Processed;
await OrderRepository.UpdateAsync(order);
await OrderExceptionEventRepository.InsertAsync(new OrderExceptionEvent
{
OrderId = order.Id,
EventType = OrderExceptionEventTypeCodes.HandleProcessed,
FromStatus = order.OrderStatus,
ToStatus = order.OrderStatus,
FromHandleStatus = OrderHandleStatusCodes.Pending,
ToHandleStatus = OrderHandleStatusCodes.Processed,
Remark = input.Remark?.Trim(),
});
}
private static T? DeserializeBatchPayload<T>(string? payloadJson)
where T : class
{
if (string.IsNullOrWhiteSpace(payloadJson))
{
return null;
}
return JsonConvert.DeserializeObject<T>(payloadJson);
}
}