优化x
This commit is contained in:
@@ -4,6 +4,8 @@ import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@@ -29,6 +31,13 @@ public interface PendingReviewOrderClient {
|
||||
@RequestParam long page,
|
||||
@RequestParam long size);
|
||||
|
||||
@PostMapping("/batch")
|
||||
ApiResponse<List<Row>> batch(
|
||||
@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody BatchRequest request);
|
||||
|
||||
record BatchRequest(List<Long> orderIds) {}
|
||||
|
||||
record Row(Long orderId, String orderNo, String inboundCode, Long customerId,
|
||||
String customerName, Long warehouseId, String channelCode, String channelName,
|
||||
int totalPackageCount, LocalDateTime createdAt, LocalDateTime expectedArrivalAt,
|
||||
|
||||
+5
@@ -13,6 +13,11 @@ import static com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRo
|
||||
/** 同柜不可拆组在 Firstleg 单库内的唯一持久化入口。 */
|
||||
@Mapper
|
||||
public interface ColoadGroupMapper {
|
||||
@Select({"<script>", "SELECT DISTINCT order_id FROM fl_inbound_order WHERE deleted=0 AND order_id IN",
|
||||
"<foreach collection='orderIds' item='orderId' open='(' separator=',' close=')'>",
|
||||
"#{orderId}", "</foreach>", "</script>"})
|
||||
List<Long> existingOrderIds(@Param("orderIds") List<Long> orderIds);
|
||||
|
||||
@Select({"<script>", """
|
||||
SELECT o.tenant_id tenantId,o.order_id orderId,o.order_no orderNo,
|
||||
o.id inboundId,o.inbound_code inboundCode,o.process_status processStatus,
|
||||
|
||||
+44
@@ -13,6 +13,10 @@ import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.Comm
|
||||
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.GroupRow;
|
||||
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.InboundCandidateRow;
|
||||
import com.baiyue.firstleg.loading.coload.port.ColoadGroupAssignmentPort;
|
||||
import com.baiyue.firstleg.client.OrderCargoClient;
|
||||
import com.baiyue.firstleg.client.PendingReviewOrderClient;
|
||||
import com.baiyue.firstleg.service.InboundOrderCreationService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -41,6 +45,10 @@ public class ColoadGroupService implements ColoadGroupAssignmentPort {
|
||||
|
||||
@Resource
|
||||
private ColoadGroupMapper mapper;
|
||||
@Resource private PendingReviewOrderClient pendingReviewOrders;
|
||||
@Resource private OrderCargoClient orderCargoClient;
|
||||
@Resource private InboundOrderCreationService inboundOrderCreationService;
|
||||
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
|
||||
|
||||
@Transactional
|
||||
public GroupSnapshot create(List<Long> requestedOrderIds, String idempotencyKey,
|
||||
@@ -51,6 +59,7 @@ public class ColoadGroupService implements ColoadGroupAssignmentPort {
|
||||
CommandClaim claim = claim(operatorId, "CREATE", key, requestHash);
|
||||
if (claim.replayGroupId() != null) return requiredSnapshot(claim.replayGroupId());
|
||||
|
||||
preparePendingReviewInbounds(orderIds);
|
||||
List<InboundCandidateRow> candidates = authoritativeCandidates(orderIds, null);
|
||||
validateSameRoute(candidates);
|
||||
Long lockedPlanId = validateAllocationState(candidates);
|
||||
@@ -265,6 +274,37 @@ public class ColoadGroupService implements ColoadGroupAssignmentPort {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private void preparePendingReviewInbounds(List<Long> orderIds) {
|
||||
Set<Long> existing = Set.copyOf(mapper.existingOrderIds(orderIds));
|
||||
List<Long> missing = orderIds.stream().filter(orderId -> !existing.contains(orderId)).toList();
|
||||
if (missing.isEmpty()) return;
|
||||
try {
|
||||
var response = pendingReviewOrders.batch(internalToken,
|
||||
new PendingReviewOrderClient.BatchRequest(missing));
|
||||
if (response == null || !response.successful() || response.data() == null) throw unavailable();
|
||||
for (PendingReviewOrderClient.Row row : response.data()) {
|
||||
if (row == null || row.orderId() == null || !missing.contains(row.orderId())
|
||||
|| row.customerId() == null || !"PENDING_REVIEW".equals(row.mainStatus())) {
|
||||
throw conflict("待审核订单权威快照已变化,请刷新后重试");
|
||||
}
|
||||
var snapshotResponse = orderCargoClient.inboundSnapshot(
|
||||
internalToken, row.customerId(), row.orderId());
|
||||
if (snapshotResponse == null || !snapshotResponse.successful()
|
||||
|| snapshotResponse.data() == null
|
||||
|| snapshotResponse.data().tenantId() != row.customerId()
|
||||
|| snapshotResponse.data().orderId() != row.orderId()
|
||||
|| !Objects.equals(snapshotResponse.data().inboundCode(), row.inboundCode())) {
|
||||
throw conflict("待审核订单履约快照已变化,请刷新后重试");
|
||||
}
|
||||
inboundOrderCreationService.prepareFromDraft(snapshotResponse.data());
|
||||
}
|
||||
} catch (BizException exception) {
|
||||
throw exception;
|
||||
} catch (RuntimeException exception) {
|
||||
throw unavailable();
|
||||
}
|
||||
}
|
||||
|
||||
private String unavailableReason(InboundCandidateRow row, Long allowedGroupId) {
|
||||
if (row.getTenantId() == null || row.getTenantId() <= 0
|
||||
|| row.getInboundId() == null || row.getInboundId() <= 0
|
||||
@@ -470,6 +510,10 @@ public class ColoadGroupService implements ColoadGroupAssignmentPort {
|
||||
return new BizException(ResultCode.CONFLICT, message);
|
||||
}
|
||||
|
||||
private static BizException unavailable() {
|
||||
return new BizException(ResultCode.INTERNAL_ERROR, "待审核订单权威服务暂不可用");
|
||||
}
|
||||
|
||||
private record CommandClaim(long commandId, Long replayGroupId) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ paths:
|
||||
summary: 创建同柜不可拆组
|
||||
description: >-
|
||||
需要 platform:firstleg:smart-loading:assign。orderIds 至少2项;服务端解析每个订单处于待审核、
|
||||
待测量/测量中、待分柜或装柜中的全部有效入仓片段,要求国内仓和渠道一致,并冻结全部片段及其满箱数。
|
||||
待测量/测量中、待分柜或装柜中的全部有效入仓片段;待审核订单先经 Order 批量权威核验并幂等准备
|
||||
PREPARING 入库投影。全部成员要求国内仓和渠道一致,并冻结全部片段及其满箱数。
|
||||
全部成员未进入装柜计划时创建 ACTIVE 组;全部成员已经按满箱数完整进入同一个装柜计划时直接创建并
|
||||
锁定为 LOCKED 组。仅部分成员进柜、分散在不同装柜计划、箱数不完整、已属于其他活动组或权威仓库/
|
||||
渠道快照缺失时拒绝。跨成员仓库或渠道不一致时返回入仓码及仓库名/渠道名;平台错误不得展示
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package com.baiyue.firstleg.loading.coload;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.firstleg.client.OrderCargoClient;
|
||||
import com.baiyue.firstleg.client.PendingReviewOrderClient;
|
||||
import com.baiyue.firstleg.dto.OrderApprovedInboundCommand;
|
||||
import com.baiyue.firstleg.service.InboundOrderCreationService;
|
||||
import com.baiyue.firstleg.loading.coload.persistence.ColoadGroupMapper;
|
||||
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.GroupRow;
|
||||
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.CommandRow;
|
||||
@@ -12,6 +17,7 @@ import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.Orde
|
||||
import com.baiyue.firstleg.loading.coload.service.ColoadGroupService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
@@ -35,9 +41,19 @@ import static org.mockito.Mockito.when;
|
||||
class ColoadGroupServiceTest {
|
||||
@MockBean
|
||||
private ColoadGroupMapper mapper;
|
||||
@MockBean private PendingReviewOrderClient pendingReviewOrders;
|
||||
@MockBean private OrderCargoClient orderCargoClient;
|
||||
@MockBean private InboundOrderCreationService inboundOrderCreationService;
|
||||
@Resource
|
||||
private ColoadGroupService service;
|
||||
|
||||
@BeforeEach
|
||||
void allowNoPendingReviewProjectionByDefault() {
|
||||
when(mapper.existingOrderIds(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(pendingReviewOrders.batch(anyString(), any(PendingReviewOrderClient.BatchRequest.class)))
|
||||
.thenReturn(ApiResponse.success(List.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readsCrossCustomerTwoMemberGroupByEitherInboundCodeWithoutInternalIdentifiers() {
|
||||
when(mapper.groupsByInboundCode("IN-A")).thenReturn(List.of(lookup(500L, "ACTIVE")));
|
||||
@@ -154,6 +170,31 @@ class ColoadGroupServiceTest {
|
||||
verify(mapper).insertMember(anyLong(), eq(groupId.getValue()), eq(202L), eq(12L), eq(1002L), eq(50));
|
||||
}
|
||||
|
||||
@Test
|
||||
void provisionsSelectedPendingReviewOrderBeforeResolvingColoadCandidates() {
|
||||
PendingReviewOrderClient.Row pending = new PendingReviewOrderClient.Row(
|
||||
11L, "FO-11", "IN-1001", 101L, "客户", 9L, "CH", "美森海卡",
|
||||
30, LocalDateTime.now(), null, "PENDING_REVIEW");
|
||||
OrderApprovedInboundCommand snapshot = new OrderApprovedInboundCommand(
|
||||
101L, 11L, "FO-11", "IN-1001", 101L, "客户", 9L, "义乌仓",
|
||||
null, null, null, "FIRST_LEG", "CH", "美森海卡", null, null,
|
||||
null, null, null, false, "FBA", null, null, null, null, null, null, 30);
|
||||
when(pendingReviewOrders.batch(anyString(), any(PendingReviewOrderClient.BatchRequest.class)))
|
||||
.thenReturn(ApiResponse.success(List.of(pending)));
|
||||
when(mapper.existingOrderIds(List.of(11L, 12L))).thenReturn(List.of(12L));
|
||||
when(orderCargoClient.inboundSnapshot(anyString(), eq(101L), eq(11L)))
|
||||
.thenReturn(ApiResponse.success(snapshot));
|
||||
when(inboundOrderCreationService.prepareFromDraft(snapshot)).thenReturn(1001L);
|
||||
stubSuccessfulCreate("pending-review", List.of(
|
||||
candidate(101L, 11L, 1001L, 30), candidate(202L, 12L, 1002L, 50)), "ACTIVE", null);
|
||||
|
||||
var result = service.create(List.of(11L, 12L), "pending-review", 7L, "operator");
|
||||
|
||||
assertThat(result.status()).isEqualTo("ACTIVE");
|
||||
verify(inboundOrderCreationService).prepareFromDraft(snapshot);
|
||||
verify(pendingReviewOrders).batch(anyString(), eq(new PendingReviewOrderClient.BatchRequest(List.of(11L))));
|
||||
}
|
||||
|
||||
@org.junit.jupiter.params.ParameterizedTest
|
||||
@org.junit.jupiter.params.provider.ValueSource(strings = {
|
||||
"PREPARING", "WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED",
|
||||
|
||||
+12
@@ -49,6 +49,9 @@ class ColoadGroupSqlContractTest {
|
||||
.contains("FROM fl_inbound_order o")
|
||||
.contains("o.order_no orderNo", "o.inbound_code inboundCode")
|
||||
.contains("o.warehouse_name warehouseName", "o.channel_name channelName")
|
||||
.contains("activeAllocationPlanCount", "activeAllocationPlanId", "activeAllocatedPackageCount")
|
||||
.contains("COUNT(DISTINCT item.loading_plan_id)", "MIN(item.loading_plan_id)")
|
||||
.contains("COALESCE(SUM(item.package_count),0)")
|
||||
.contains("FROM fl_loading_plan_item item")
|
||||
.contains("item.deleted=0")
|
||||
.contains("member.active_flag=1")
|
||||
@@ -63,6 +66,15 @@ class ColoadGroupSqlContractTest {
|
||||
.doesNotContain("destination_code");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapperDetectsExistingFirstlegProjectionsBeforeCallingThePendingReviewOwner() throws Exception {
|
||||
Method method = ColoadGroupMapper.class.getDeclaredMethod("existingOrderIds", java.util.List.class);
|
||||
String sql = String.join(" ", method.getAnnotation(Select.class).value());
|
||||
assertThat(sql).contains("SELECT DISTINCT order_id FROM fl_inbound_order")
|
||||
.contains("deleted=0", "order_id IN")
|
||||
.doesNotContain("FOR UPDATE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inboundCodeReadKeepsConsumedGroupsAndAllFrozenEffectiveFragments() throws Exception {
|
||||
Method lookup = ColoadGroupMapper.class.getDeclaredMethod("groupsByInboundCode", String.class);
|
||||
|
||||
Reference in New Issue
Block a user