订单批量审核提交
This commit is contained in:
@@ -3,7 +3,9 @@ package com.baiyue.order.controller;
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.order.dto.BatchOrderReviewResponse;
|
||||
import com.baiyue.order.dto.OrderDetailResponse;
|
||||
import com.baiyue.order.dto.OrderRequests;
|
||||
import com.baiyue.order.dto.TenantOrderCountResponse;
|
||||
@@ -28,6 +30,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -155,6 +158,18 @@ public class PlatformFirstLegOrderController {
|
||||
return transit(user, tenantId, id, OrderStatus.APPROVED, null);
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "ORDER_BATCH_APPROVE", resourceType = "FIRST_LEG_ORDER_BATCH",
|
||||
resourceId = "#request.orderIds", tenantId = "#tenantId", description = "批量审核通过订单")
|
||||
@Operation(summary = "批量审核通过订单", description = "单租户模式下逐单独立审核,返回成功和失败明细。")
|
||||
@PostMapping("/batch-approve")
|
||||
@PreAuthorize("hasAuthority('platform:order:approve')")
|
||||
public ApiResponse<BatchOrderReviewResponse> batchApprove(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long tenantId,
|
||||
@Valid @RequestBody OrderRequests.BatchReviewRequest request) {
|
||||
return batchTransit(user, tenantId, request.orderIds(), OrderStatus.APPROVED, null);
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "ORDER_ASSIGN_CARRIER", resourceType = "FIRST_LEG_ORDER", resourceId = "#id", description = "指派订单承运商")
|
||||
@Operation(summary = "指派承运商", description = "可选地为待审核订单选择启用的公共承运商,名称快照由服务端主数据回填;该动作不是审核前置条件。")
|
||||
@PutMapping("/{id}/carrier")
|
||||
@@ -179,6 +194,18 @@ public class PlatformFirstLegOrderController {
|
||||
return transit(user, tenantId, id, OrderStatus.REJECTED, request.reason());
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "ORDER_BATCH_REJECT", resourceType = "FIRST_LEG_ORDER_BATCH",
|
||||
resourceId = "#request.orderIds", tenantId = "#tenantId", description = "批量驳回订单")
|
||||
@Operation(summary = "批量驳回订单", description = "单租户模式下使用统一原因逐单独立驳回,返回成功和失败明细。")
|
||||
@PostMapping("/batch-reject")
|
||||
@PreAuthorize("hasAuthority('platform:order:reject')")
|
||||
public ApiResponse<BatchOrderReviewResponse> batchReject(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long tenantId,
|
||||
@Valid @RequestBody OrderRequests.BatchRejectRequest request) {
|
||||
return batchTransit(user, tenantId, request.orderIds(), OrderStatus.REJECTED, request.reason());
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "ORDER_ACCEPT", resourceType = "FIRST_LEG_ORDER", resourceId = "#id", description = "Accept order / 受理订单")
|
||||
@Operation(summary = "Accept / 受理订单", description = "Accepts an approved order for fulfillment processing. / 受理已审核通过的订单,进入履约处理。")
|
||||
@PostMapping("/{id}/accept")
|
||||
@@ -255,4 +282,22 @@ public class PlatformFirstLegOrderController {
|
||||
);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
private ApiResponse<BatchOrderReviewResponse> batchTransit(
|
||||
AuthenticatedUser user, long tenantId, List<Long> orderIds, OrderStatus target, String reason) {
|
||||
long scopedTenantId = OrderScopeGuard.platform(user, tenantId);
|
||||
List<Long> successOrderIds = new ArrayList<>();
|
||||
List<BatchOrderReviewResponse.FailedOrder> failedOrders = new ArrayList<>();
|
||||
for (Long orderId : orderIds) {
|
||||
try {
|
||||
service.transit(scopedTenantId, user.userId(), user.username(), orderId, target, reason);
|
||||
successOrderIds.add(orderId);
|
||||
} catch (BizException exception) {
|
||||
failedOrders.add(new BatchOrderReviewResponse.FailedOrder(
|
||||
orderId, exception.getResultCode().code(), exception.getMessage()));
|
||||
}
|
||||
}
|
||||
return ApiResponse.success(new BatchOrderReviewResponse(orderIds.size(), successOrderIds.size(),
|
||||
failedOrders.size(), List.copyOf(successOrderIds), List.copyOf(failedOrders)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.baiyue.order.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 单租户订单批量审核结果。 */
|
||||
@Schema(description = "订单批量审核结果")
|
||||
public record BatchOrderReviewResponse(
|
||||
@Schema(description = "请求订单总数") int total,
|
||||
@Schema(description = "成功数量") int successCount,
|
||||
@Schema(description = "失败数量") int failureCount,
|
||||
@Schema(description = "成功订单 ID") List<Long> successOrderIds,
|
||||
@Schema(description = "失败订单及原因") List<FailedOrder> failedOrders
|
||||
) {
|
||||
@Schema(description = "单笔失败结果")
|
||||
public record FailedOrder(
|
||||
@Schema(description = "订单 ID") long orderId,
|
||||
@Schema(description = "业务错误码") String code,
|
||||
@Schema(description = "失败原因") String message
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -166,6 +166,30 @@ public final class OrderRequests {
|
||||
) {
|
||||
}
|
||||
|
||||
@Schema(name = "FirstLegOrderBatchReviewRequest", description = "单租户订单批量审核请求")
|
||||
public record BatchReviewRequest(
|
||||
@Schema(description = "订单 ID 列表,单次最多 100 条", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty @Size(max = 100) List<@NotNull @Min(1) Long> orderIds
|
||||
) {
|
||||
@AssertTrue(message = "订单 ID 不能重复")
|
||||
public boolean hasDistinctOrderIds() {
|
||||
return orderIds == null || orderIds.size() == orderIds.stream().distinct().count();
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "FirstLegOrderBatchRejectRequest", description = "单租户订单批量驳回请求")
|
||||
public record BatchRejectRequest(
|
||||
@Schema(description = "订单 ID 列表,单次最多 100 条", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty @Size(max = 100) List<@NotNull @Min(1) Long> orderIds,
|
||||
@Schema(description = "统一驳回原因", maxLength = 500, requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Size(max = 500) String reason
|
||||
) {
|
||||
@AssertTrue(message = "订单 ID 不能重复")
|
||||
public boolean hasDistinctOrderIds() {
|
||||
return orderIds == null || orderIds.size() == orderIds.stream().distinct().count();
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "OrderInternalNoteRequest", description = "Platform internal order note / 平台内部订单备注")
|
||||
public record NoteRequest(
|
||||
@Schema(description = "Internal note content / 内部备注内容", example = "Customer confirmed the shipping schedule / 客户已确认船期", maxLength = 2000, requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
|
||||
@@ -43,6 +43,10 @@ class OrderControllerPermissionTest {
|
||||
AuthenticatedUser.class, long.class, long.class, String.class);
|
||||
assertAuthority(PlatformFirstLegOrderController.class, "assignCarrier", "hasAuthority('platform:order:approve')",
|
||||
AuthenticatedUser.class, long.class, long.class, OrderRequests.CarrierRequest.class);
|
||||
assertAuthority(PlatformFirstLegOrderController.class, "batchApprove", "hasAuthority('platform:order:approve')",
|
||||
AuthenticatedUser.class, long.class, OrderRequests.BatchReviewRequest.class);
|
||||
assertAuthority(PlatformFirstLegOrderController.class, "batchReject", "hasAuthority('platform:order:reject')",
|
||||
AuthenticatedUser.class, long.class, OrderRequests.BatchRejectRequest.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.baiyue.order.controller;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.order.dto.BatchOrderReviewResponse;
|
||||
import com.baiyue.order.dto.OrderRequests;
|
||||
import com.baiyue.order.entity.OrderStatus;
|
||||
import com.baiyue.order.service.OrderApplicationService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -11,8 +14,11 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
@@ -66,6 +72,35 @@ class PlatformOrderPageControllerTest {
|
||||
verifyNoInteractions(service);
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchApproveReturnsPartialFailureAndContinuesRemainingOrders() {
|
||||
doThrow(new BizException(ResultCode.CONFLICT, "订单已审核"))
|
||||
.when(service).transit(10001L, 1L, "operator", 102L, OrderStatus.APPROVED, null);
|
||||
|
||||
var response = controller.batchApprove(user("PLATFORM", 0L), 10001L,
|
||||
new OrderRequests.BatchReviewRequest(List.of(101L, 102L, 103L)));
|
||||
|
||||
BatchOrderReviewResponse result = response.data();
|
||||
assertThat(result.total()).isEqualTo(3);
|
||||
assertThat(result.successOrderIds()).containsExactly(101L, 103L);
|
||||
assertThat(result.failedOrders()).singleElement().satisfies(failure -> {
|
||||
assertThat(failure.orderId()).isEqualTo(102L);
|
||||
assertThat(failure.code()).isEqualTo(ResultCode.CONFLICT.code());
|
||||
assertThat(failure.message()).isEqualTo("订单已审核");
|
||||
});
|
||||
verify(service).transit(10001L, 1L, "operator", 101L, OrderStatus.APPROVED, null);
|
||||
verify(service).transit(10001L, 1L, "operator", 103L, OrderStatus.APPROVED, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchRejectUsesSharedReasonForEveryOrder() {
|
||||
controller.batchReject(user("PLATFORM", 0L), 10001L,
|
||||
new OrderRequests.BatchRejectRequest(List.of(101L, 102L), "资料不完整"));
|
||||
|
||||
verify(service).transit(10001L, 1L, "operator", 101L, OrderStatus.REJECTED, "资料不完整");
|
||||
verify(service).transit(10001L, 1L, "operator", 102L, OrderStatus.REJECTED, "资料不完整");
|
||||
}
|
||||
|
||||
private AuthenticatedUser user(String appCode, Long tenantId) {
|
||||
return new AuthenticatedUser(1L, tenantId, null, null, "operator", appCode, "WEB", Set.of());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user