This commit is contained in:
2026-08-31 10:09:14 +08:00
parent ff6a8498a6
commit aa583efa4a
14 changed files with 320 additions and 92 deletions
@@ -5,11 +5,10 @@ import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode; import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException; import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.AuthenticatedUser; import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.BatchQueryRequest; import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.BusinessSummary;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CancelRequest; import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CancelRequest;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CreateRequest; import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CreateRequest;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.GroupSnapshot; import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.GroupSnapshot;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.OrderGroupInfo;
import com.baiyue.firstleg.loading.coload.service.ColoadGroupService; import com.baiyue.firstleg.loading.coload.service.ColoadGroupService;
import com.baiyue.firstleg.service.FirstlegScopeGuard; import com.baiyue.firstleg.service.FirstlegScopeGuard;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -17,9 +16,11 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@@ -27,8 +28,6 @@ import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/** 平台订单列表使用的同柜不可拆组管理接口。 */ /** 平台订单列表使用的同柜不可拆组管理接口。 */
@Validated @Validated
@RestController @RestController
@@ -69,15 +68,15 @@ public class PlatformColoadGroupController {
idempotencyKey, user.userId(), user.username())); idempotencyKey, user.userId(), user.username()));
} }
@PostMapping("/batch-query") @GetMapping("/by-inbound-code/{inboundCode}")
@PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:list')")
@Operation(summary = "按订单ID批量查询同柜不可拆组", @Operation(summary = "按入仓码查询同柜组业务摘要",
description = "供平台订单列表批量补齐组号、状态、全体成员和整组满箱数;最多200个订单。") description = "仅返回业务状态、有效成员数及去重后的全部成员入仓码;内部组ID和组号不作为页面识别键。")
public ApiResponse<List<OrderGroupInfo>> batchQuery( public ApiResponse<BusinessSummary> byInboundCode(
@AuthenticationPrincipal AuthenticatedUser user, @AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody BatchQueryRequest request) { @PathVariable @Size(min = 1, max = 40) String inboundCode) {
requirePlatform(user); requirePlatform(user);
return ApiResponse.success(service.queryByOrderIds(request.orderIds())); return ApiResponse.success(service.queryByInboundCode(inboundCode));
} }
private static void requirePlatform(AuthenticatedUser user) { private static void requirePlatform(AuthenticatedUser user) {
@@ -24,11 +24,6 @@ public final class ColoadGroupModels {
) { ) {
} }
public record BatchQueryRequest(
@NotEmpty @Size(max = 200) List<@NotNull @Min(1) Long> orderIds
) {
}
public record Member( public record Member(
long tenantId, long tenantId,
long orderId, long orderId,
@@ -65,9 +60,18 @@ public final class ColoadGroupModels {
} }
} }
public record OrderGroupInfo( public record BusinessMember(String inboundCode) {
long orderId, }
GroupSnapshot group
/** 页面只读业务摘要;内部组标识不作为用户识别键。 */
public record BusinessSummary(
String status,
int memberCount,
List<BusinessMember> members
) { ) {
public BusinessSummary {
members = members == null ? List.of() : List.copyOf(members);
memberCount = members.size();
}
} }
} }
@@ -85,15 +85,28 @@ public interface ColoadGroupMapper {
GroupRow activeGroupByInbound(@Param("tenantId") long tenantId, GroupRow activeGroupByInbound(@Param("tenantId") long tenantId,
@Param("inboundId") long inboundId); @Param("inboundId") long inboundId);
@Select({"<script>", """ @Select("""
SELECT member.order_id orderId,member.group_id groupId SELECT DISTINCT group_row.id groupId,group_row.group_status groupStatus
FROM fl_coload_group_member member FROM fl_inbound_order requested
JOIN fl_coload_group_member member
ON member.tenant_id=requested.tenant_id AND member.inbound_id=requested.id
JOIN fl_coload_group group_row ON group_row.id=member.group_id JOIN fl_coload_group group_row ON group_row.id=member.group_id
WHERE member.order_id IN WHERE requested.deleted=0 AND requested.inbound_code=#{inboundCode}
""", "<foreach collection='orderIds' item='orderId' open='(' separator=',' close=')'>", AND group_row.group_status IN ('ACTIVE','LOCKED','CONSUMED')
"#{orderId}", "</foreach>", ORDER BY group_row.id
"AND group_row.group_status&lt;&gt;'CANCELLED' ORDER BY member.order_id,group_row.created_at DESC", "</script>"}) """)
List<OrderGroupLinkRow> linksByOrderIds(@Param("orderIds") List<Long> orderIds); List<InboundGroupLookupRow> groupsByInboundCode(@Param("inboundCode") String inboundCode);
@Select("""
SELECT DISTINCT inbound.inbound_code inboundCode
FROM fl_coload_group_member member
JOIN fl_inbound_order inbound
ON inbound.tenant_id=member.tenant_id AND inbound.id=member.inbound_id
WHERE member.group_id=#{groupId} AND inbound.deleted=0
AND inbound.inbound_code IS NOT NULL AND TRIM(inbound.inbound_code)<>''
ORDER BY inbound.inbound_code
""")
List<MemberCodeRow> effectiveMemberCodes(@Param("groupId") long groupId);
@Update(""" @Update("""
UPDATE fl_coload_group UPDATE fl_coload_group
@@ -49,9 +49,15 @@ public final class ColoadPersistenceRows {
@Getter @Getter
@Setter @Setter
public static class OrderGroupLinkRow { public static class InboundGroupLookupRow {
private Long orderId;
private Long groupId; private Long groupId;
private String groupStatus;
}
@Getter
@Setter
public static class MemberCodeRow {
private String inboundCode;
} }
@Getter @Getter
@@ -5,7 +5,8 @@ import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException; import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.GroupSnapshot; import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.GroupSnapshot;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.Member; import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.Member;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.OrderGroupInfo; import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.BusinessMember;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.BusinessSummary;
import com.baiyue.firstleg.loading.coload.persistence.ColoadGroupMapper; import com.baiyue.firstleg.loading.coload.persistence.ColoadGroupMapper;
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.CommandRow; import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.CommandRow;
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.GroupRow; import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.GroupRow;
@@ -89,17 +90,28 @@ public class ColoadGroupService implements ColoadGroupAssignmentPort {
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<OrderGroupInfo> queryByOrderIds(List<Long> requestedOrderIds) { public BusinessSummary queryByInboundCode(String inboundCode) {
List<Long> orderIds = normalizedOrderIds(requestedOrderIds, 1, 200); String code = trim(inboundCode);
Map<Long, OrderGroupInfo> latest = new LinkedHashMap<>(); if (code == null || code.length() > 40) throw badRequest("inboundCode不能为空且不能超过40字符");
Map<Long, GroupSnapshot> groups = new LinkedHashMap<>(); var groups = mapper.groupsByInboundCode(code).stream()
for (var link : mapper.linksByOrderIds(orderIds)) { .filter(row -> row.getGroupId() != null && CURRENT_GROUP_STATUSES.contains(row.getGroupStatus()))
if (latest.containsKey(link.getOrderId())) continue; .toList();
GroupSnapshot group = groups.computeIfAbsent(link.getGroupId(), this::requiredSnapshot); long distinctGroups = groups.stream().map(row -> row.getGroupId()).distinct().limit(2).count();
if (!CURRENT_GROUP_STATUSES.contains(group.status())) continue; if (distinctGroups == 0) {
latest.put(link.getOrderId(), new OrderGroupInfo(link.getOrderId(), group)); throw new BizException(ResultCode.NOT_FOUND, "入仓码未关联有效同柜不可拆组");
} }
return orderIds.stream().map(latest::get).filter(Objects::nonNull).toList(); if (distinctGroups > 1) throw conflict("入仓码关联多个有效同柜不可拆组");
long groupId = groups.get(0).getGroupId();
String status = groups.get(0).getGroupStatus();
List<BusinessMember> members = mapper.effectiveMemberCodes(groupId).stream()
.map(row -> trim(row.getInboundCode()))
.filter(Objects::nonNull)
.distinct()
.sorted()
.map(BusinessMember::new)
.toList();
if (members.isEmpty()) throw conflict("同柜不可拆组不存在有效入仓片段");
return new BusinessSummary(status, members.size(), members);
} }
@Override @Override
@@ -35,17 +35,17 @@ paths:
responses: responses:
'200': {$ref: '#/components/responses/ColoadGroupResponse'} '200': {$ref: '#/components/responses/ColoadGroupResponse'}
'403': {description: 缺少智能排柜分配权限} '403': {description: 缺少智能排柜分配权限}
/api/platform/v1/first-leg/coload-groups/batch-query: /api/platform/v1/first-leg/coload-groups/by-inbound-code/{inboundCode}:
post: get:
summary: 按订单ID批量查询同柜不可拆组 summary: 按入仓码查询同柜组业务摘要
description: >- description: >-
需要 platform:firstleg:smart-loading:assign。最多200个订单;返回当前非取消组的组号、状态、 需要 platform:firstleg:smart-loading:list。使用任一有效成员的全局唯一 inboundCode 查询;
全部成员和整组满箱数,供平台订单列表批量补齐,不按单成员修改。 仅返回组业务状态、有效成员数和去重后的全部成员入仓码。页面不持有或展示 groupId、groupNo、orderId。
requestBody: parameters:
required: true - name: inboundCode
content: in: path
application/json: required: true
schema: {$ref: '#/components/schemas/BatchQueryColoadGroupRequest'} schema: {type: string, minLength: 1, maxLength: 40}
responses: responses:
'200': '200':
description: 业务响应 description: 业务响应
@@ -56,11 +56,8 @@ paths:
- {$ref: '#/components/schemas/ApiEnvelope'} - {$ref: '#/components/schemas/ApiEnvelope'}
- type: object - type: object
properties: properties:
data: data: {$ref: '#/components/schemas/ColoadGroupBusinessSummary'}
type: array '403': {description: 缺少智能排柜列表权限}
maxItems: 200
items: {$ref: '#/components/schemas/OrderColoadGroupInfo'}
'403': {description: 缺少智能排柜分配权限}
components: components:
parameters: parameters:
IdempotencyKey: IdempotencyKey:
@@ -107,17 +104,6 @@ components:
properties: properties:
expectedVersion: {type: integer, minimum: 0} expectedVersion: {type: integer, minimum: 0}
reason: {type: string, maxLength: 500, nullable: true} reason: {type: string, maxLength: 500, nullable: true}
BatchQueryColoadGroupRequest:
type: object
additionalProperties: false
required: [orderIds]
properties:
orderIds:
type: array
minItems: 1
maxItems: 200
uniqueItems: true
items: {type: string, pattern: '^[1-9][0-9]*$'}
ColoadGroupMember: ColoadGroupMember:
type: object type: object
additionalProperties: false additionalProperties: false
@@ -147,10 +133,21 @@ components:
fullPackageCount: {type: integer, minimum: 2, description: 全部成员满箱数之和} fullPackageCount: {type: integer, minimum: 2, description: 全部成员满箱数之和}
createdAt: {type: string, format: date-time} createdAt: {type: string, format: date-time}
updatedAt: {type: string, format: date-time} updatedAt: {type: string, format: date-time}
OrderColoadGroupInfo: ColoadGroupBusinessMember:
type: object type: object
additionalProperties: false additionalProperties: false
required: [orderId, group] required: [inboundCode]
properties: properties:
orderId: {type: string, pattern: '^[1-9][0-9]*$'} inboundCode: {type: string, minLength: 1, maxLength: 40}
group: {$ref: '#/components/schemas/ColoadGroup'} ColoadGroupBusinessSummary:
type: object
additionalProperties: false
required: [status, memberCount, members]
properties:
status: {type: string, enum: [ACTIVE, LOCKED, CONSUMED]}
memberCount: {type: integer, minimum: 1}
members:
type: array
minItems: 1
uniqueItems: true
items: {$ref: '#/components/schemas/ColoadGroupBusinessMember'}
@@ -2,13 +2,13 @@ package com.baiyue.firstleg.loading.coload;
import com.baiyue.common.security.AuthenticatedUser; import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.loading.coload.controller.PlatformColoadGroupController; import com.baiyue.firstleg.loading.coload.controller.PlatformColoadGroupController;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.BatchQueryRequest;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CancelRequest; import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CancelRequest;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CreateRequest; import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CreateRequest;
import com.baiyue.firstleg.loading.coload.port.ColoadGroupAssignmentPort; import com.baiyue.firstleg.loading.coload.port.ColoadGroupAssignmentPort;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@@ -25,9 +25,15 @@ class ColoadGroupContractTest {
assertEndpoint("create", "", AuthenticatedUser.class, String.class, CreateRequest.class); assertEndpoint("create", "", AuthenticatedUser.class, String.class, CreateRequest.class);
assertEndpoint("cancel", "/{groupId}/cancel", AuthenticatedUser.class, long.class, assertEndpoint("cancel", "/{groupId}/cancel", AuthenticatedUser.class, long.class,
String.class, CancelRequest.class); String.class, CancelRequest.class);
assertEndpoint("batchQuery", "/batch-query", AuthenticatedUser.class, BatchQueryRequest.class); Method read = PlatformColoadGroupController.class.getDeclaredMethod(
"byInboundCode", AuthenticatedUser.class, String.class);
assertThat(read.getAnnotation(GetMapping.class).value())
.containsExactly("/by-inbound-code/{inboundCode}");
assertThat(read.getAnnotation(PreAuthorize.class).value())
.isEqualTo("hasAuthority('platform:firstleg:smart-loading:list')");
assertThat(Arrays.stream(PlatformColoadGroupController.class.getDeclaredMethods()) assertThat(Arrays.stream(PlatformColoadGroupController.class.getDeclaredMethods())
.map(Method::getName)).doesNotContain("addMember", "removeMember", "updateMember"); .map(Method::getName)).doesNotContain(
"batchQuery", "addMember", "removeMember", "updateMember");
} }
@Test @Test
@@ -12,23 +12,25 @@ import static org.assertj.core.api.Assertions.assertThat;
class ColoadGroupOpenApiContractTest { class ColoadGroupOpenApiContractTest {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
void publishesBoundedLongIdGroupContractWithoutNewPermission() throws Exception { void publishesInboundCodeBusinessSummaryWithoutExposingInternalGroupIdentity() throws Exception {
String source = Files.readString(Path.of("src/main/resources/openapi/coload-groups-v1.yaml")); String source = Files.readString(Path.of("src/main/resources/openapi/coload-groups-v1.yaml"));
Map<String, Object> yaml = new Yaml().load(source); Map<String, Object> yaml = new Yaml().load(source);
Map<String, Object> paths = (Map<String, Object>) yaml.get("paths"); Map<String, Object> paths = (Map<String, Object>) yaml.get("paths");
Map<String, Object> components = (Map<String, Object>) yaml.get("components"); Map<String, Object> components = (Map<String, Object>) yaml.get("components");
Map<String, Object> schemas = (Map<String, Object>) components.get("schemas"); Map<String, Object> schemas = (Map<String, Object>) components.get("schemas");
Map<String, Object> orderInfo = (Map<String, Object>) schemas.get("OrderColoadGroupInfo"); Map<String, Object> summary = (Map<String, Object>) schemas.get("ColoadGroupBusinessSummary");
Map<String, Object> orderInfoProperties = (Map<String, Object>) orderInfo.get("properties"); Map<String, Object> summaryProperties = (Map<String, Object>) summary.get("properties");
Map<String, Object> member = (Map<String, Object>) schemas.get("ColoadGroupBusinessMember");
Map<String, Object> memberProperties = (Map<String, Object>) member.get("properties");
assertThat(paths).containsKeys( assertThat(paths).containsKeys(
"/api/platform/v1/first-leg/coload-groups", "/api/platform/v1/first-leg/coload-groups",
"/api/platform/v1/first-leg/coload-groups/{groupId}/cancel", "/api/platform/v1/first-leg/coload-groups/{groupId}/cancel",
"/api/platform/v1/first-leg/coload-groups/batch-query"); "/api/platform/v1/first-leg/coload-groups/by-inbound-code/{inboundCode}");
assertThat(paths).doesNotContainKey("/api/platform/v1/first-leg/coload-groups/batch-query");
assertThat(source) assertThat(source)
.contains("minItems: 2") .contains("minItems: 2")
.contains("maxItems: 100") .contains("maxItems: 100")
.contains("maxItems: 200")
.contains("uniqueItems: true") .contains("uniqueItems: true")
.contains("pattern: '^[1-9][0-9]*$'") .contains("pattern: '^[1-9][0-9]*$'")
.contains("enum: [ACTIVE, LOCKED, CONSUMED, CANCELLED]") .contains("enum: [ACTIVE, LOCKED, CONSUMED, CANCELLED]")
@@ -36,12 +38,14 @@ class ColoadGroupOpenApiContractTest {
.contains("expectedVersion") .contains("expectedVersion")
.contains("fullPackageCount") .contains("fullPackageCount")
.contains("platform:firstleg:smart-loading:assign") .contains("platform:firstleg:smart-loading:assign")
.contains("platform:firstleg:smart-loading:list")
.contains("全部 WAIT_SPLIT") .contains("全部 WAIT_SPLIT")
.contains("国内仓和渠道一致") .contains("国内仓和渠道一致")
.doesNotContain("destinationType") .doesNotContain("destinationType")
.doesNotContain("destinationWarehouseId") .doesNotContain("destinationWarehouseId")
.doesNotContain("destinationCode") .doesNotContain("destinationCode")
.doesNotContain("platform:firstleg:coload"); .doesNotContain("platform:firstleg:coload");
assertThat(orderInfoProperties).containsOnlyKeys("orderId", "group"); assertThat(summaryProperties).containsOnlyKeys("status", "memberCount", "members");
assertThat(memberProperties).containsOnlyKeys("inboundCode");
} }
} }
@@ -5,6 +5,8 @@ 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.GroupRow;
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.InboundCandidateRow; import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.InboundCandidateRow;
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.MemberRow; import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.MemberRow;
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.InboundGroupLookupRow;
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.MemberCodeRow;
import com.baiyue.firstleg.loading.coload.service.ColoadGroupService; import com.baiyue.firstleg.loading.coload.service.ColoadGroupService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -33,6 +35,54 @@ class ColoadGroupServiceTest {
@Resource @Resource
private ColoadGroupService service; private ColoadGroupService service;
@Test
void readsCrossCustomerTwoMemberGroupByEitherInboundCodeWithoutInternalIdentifiers() {
when(mapper.groupsByInboundCode("IN-A")).thenReturn(List.of(lookup(500L, "ACTIVE")));
when(mapper.effectiveMemberCodes(500L)).thenReturn(List.of(code("IN-A"), code("IN-B")));
var result = service.queryByInboundCode(" IN-A ");
assertThat(result.status()).isEqualTo("ACTIVE");
assertThat(result.memberCount()).isEqualTo(2);
assertThat(result.members()).extracting("inboundCode").containsExactly("IN-A", "IN-B");
verify(mapper).groupsByInboundCode("IN-A");
}
@Test
void returnsEveryEffectiveSplitFragmentAndEliminatesDuplicateInboundCodes() {
when(mapper.groupsByInboundCode("MASTER-A-2")).thenReturn(List.of(lookup(500L, "LOCKED")));
when(mapper.effectiveMemberCodes(500L)).thenReturn(List.of(
code("MASTER-A-3"), code("MASTER-A-2"), code("MASTER-A-2"), code("ORDER-B")));
var result = service.queryByInboundCode("MASTER-A-2");
assertThat(result.status()).isEqualTo("LOCKED");
assertThat(result.memberCount()).isEqualTo(3);
assertThat(result.members()).extracting("inboundCode")
.containsExactly("MASTER-A-2", "MASTER-A-3", "ORDER-B");
}
@Test
void preservesConsumedStatusWhileReadingClosedFrozenMemberships() {
when(mapper.groupsByInboundCode("IN-A")).thenReturn(List.of(lookup(500L, "CONSUMED")));
when(mapper.effectiveMemberCodes(500L)).thenReturn(List.of(code("IN-A"), code("IN-B")));
assertThat(service.queryByInboundCode("IN-A").status()).isEqualTo("CONSUMED");
}
@Test
void failsClosedWhenGroupHasNoEffectiveMemberOrLookupIsAmbiguous() {
when(mapper.groupsByInboundCode("EMPTY")).thenReturn(List.of(lookup(500L, "ACTIVE")));
when(mapper.effectiveMemberCodes(500L)).thenReturn(List.of());
when(mapper.groupsByInboundCode("AMBIGUOUS")).thenReturn(List.of(
lookup(500L, "ACTIVE"), lookup(501L, "LOCKED")));
assertThatThrownBy(() -> service.queryByInboundCode("EMPTY"))
.isInstanceOf(BizException.class).hasMessageContaining("不存在有效入仓片段");
assertThatThrownBy(() -> service.queryByInboundCode("AMBIGUOUS"))
.isInstanceOf(BizException.class).hasMessageContaining("多个有效");
}
@Test @Test
void createsCrossCustomerGroupAndFreezesAllAvailableFragmentsForEveryOrder() { void createsCrossCustomerGroupAndFreezesAllAvailableFragmentsForEveryOrder() {
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of( when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of(
@@ -209,6 +259,19 @@ class ColoadGroupServiceTest {
return value; return value;
} }
private static InboundGroupLookupRow lookup(long groupId, String status) {
InboundGroupLookupRow value = new InboundGroupLookupRow();
value.setGroupId(groupId);
value.setGroupStatus(status);
return value;
}
private static MemberCodeRow code(String inboundCode) {
MemberCodeRow value = new MemberCodeRow();
value.setInboundCode(inboundCode);
return value;
}
private static org.assertj.core.groups.Tuple tuple(Object... values) { private static org.assertj.core.groups.Tuple tuple(Object... values) {
return org.assertj.core.groups.Tuple.tuple(values); return org.assertj.core.groups.Tuple.tuple(values);
} }
@@ -56,6 +56,25 @@ class ColoadGroupSqlContractTest {
.doesNotContain("destination_code"); .doesNotContain("destination_code");
} }
@Test
void inboundCodeReadKeepsConsumedGroupsAndAllFrozenEffectiveFragments() throws Exception {
Method lookup = ColoadGroupMapper.class.getDeclaredMethod("groupsByInboundCode", String.class);
String lookupSql = String.join(" ", lookup.getAnnotation(Select.class).value());
Method members = ColoadGroupMapper.class.getDeclaredMethod("effectiveMemberCodes", long.class);
String memberSql = String.join(" ", members.getAnnotation(Select.class).value());
assertThat(lookupSql)
.contains("requested.inbound_code=#{inboundCode}")
.contains("requested.deleted=0")
.contains("'ACTIVE','LOCKED','CONSUMED'")
.doesNotContain("group_no", "order_id=#{");
assertThat(memberSql)
.contains("SELECT DISTINCT inbound.inbound_code inboundCode")
.contains("member.group_id=#{groupId}")
.contains("inbound.deleted=0")
.doesNotContain("member.active_flag=1", "inbound_role", "split_status", "split_purpose");
}
private static String tableDefinition(String source, String tableName) { private static String tableDefinition(String source, String tableName) {
int start = source.indexOf("CREATE TABLE IF NOT EXISTS " + tableName + " ("); int start = source.indexOf("CREATE TABLE IF NOT EXISTS " + tableName + " (");
int end = source.indexOf(';', start); int end = source.indexOf(';', start);
@@ -0,0 +1,23 @@
package com.baiyue.order.client;
import com.baiyue.common.core.api.ApiResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import java.util.List;
/** Platform-scoped read contract for Firstleg co-load group aggregation. */
@FeignClient(name = "firstleg-service", contextId = "orderFirstlegColoadGroupClient",
url = "${baiyue.clients.firstleg.url:}", path = "/api/platform/v1/first-leg/coload-groups")
public interface FirstlegColoadGroupClient {
@PostMapping("/batch-query")
ApiResponse<List<OrderGroupInfo>> batchQuery(@RequestHeader("Authorization") String authorization,
@RequestBody BatchQueryRequest request);
record BatchQueryRequest(List<Long> orderIds) {}
record OrderGroupInfo(Long orderId, GroupSnapshot group) {}
record GroupSnapshot(Long groupId, String groupNo, String status, List<Member> members) {}
record Member(Long tenantId, Long orderId, Long inboundId, Integer fullPackageCount) {}
}
@@ -24,6 +24,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
@@ -41,11 +42,12 @@ public class PlatformUnifiedOrderController {
@Operation(summary = "统一订单列表", description = "返回七态产品主状态及测量、预装、装柜、派送摘要。") @Operation(summary = "统一订单列表", description = "返回七态产品主状态及测量、预装、装柜、派送摘要。")
public ApiResponse<PageResult<UnifiedOrderResponse.Row>> page( public ApiResponse<PageResult<UnifiedOrderResponse.Row>> page(
@AuthenticationPrincipal AuthenticatedUser user, @AuthenticationPrincipal AuthenticatedUser user,
@RequestHeader("Authorization") String authorization,
@RequestParam(required = false) Long tenantId, @RequestParam(required = false) Long tenantId,
@Valid UnifiedOrderQuery query) { @Valid UnifiedOrderQuery query) {
OrderScopeGuard.platform(user); OrderScopeGuard.platform(user);
if (tenantId != null) OrderScopeGuard.platform(user, tenantId); if (tenantId != null) OrderScopeGuard.platform(user, tenantId);
return ApiResponse.success(service.page(tenantId, query)); return ApiResponse.success(service.platformPage(tenantId, query, authorization));
} }
@GetMapping("/{orderId}") @GetMapping("/{orderId}")
@@ -53,9 +55,11 @@ public class PlatformUnifiedOrderController {
@Operation(summary = "统一订单详情", description = "合并订单和履约时间线;平台响应包含内部备注。") @Operation(summary = "统一订单详情", description = "合并订单和履约时间线;平台响应包含内部备注。")
public ApiResponse<UnifiedOrderResponse.Detail> detail( public ApiResponse<UnifiedOrderResponse.Detail> detail(
@AuthenticationPrincipal AuthenticatedUser user, @AuthenticationPrincipal AuthenticatedUser user,
@RequestHeader("Authorization") String authorization,
@RequestParam long tenantId, @RequestParam long tenantId,
@PathVariable long orderId) { @PathVariable long orderId) {
return ApiResponse.success(service.detail(OrderScopeGuard.platform(user, tenantId), orderId, true)); return ApiResponse.success(service.platformDetail(
OrderScopeGuard.platform(user, tenantId), orderId, authorization));
} }
@GetMapping("/{orderId}/fulfillment-tree") @GetMapping("/{orderId}/fulfillment-tree")
@@ -32,6 +32,7 @@ public final class UnifiedOrderResponse {
List<String> skuNos, Integer totalQuantity, String shipmentId, String referenceId, List<String> skuNos, Integer totalQuantity, String shipmentId, String referenceId,
MeasurementSummary measurement, PreloadSummary preload, MeasurementSummary measurement, PreloadSummary preload,
LoadingSummary loading, DeliverySummary delivery, TransportSummary transport, LoadingSummary loading, DeliverySummary delivery, TransportSummary transport,
CoLoadGroupSummary coLoadGroup,
String customerRemark, String latestInternalNote, String customerRemark, String latestInternalNote,
LocalDateTime createdAt, LocalDateTime updatedAt, LocalDateTime requestedPickupAt LocalDateTime createdAt, LocalDateTime updatedAt, LocalDateTime requestedPickupAt
) {} ) {}
@@ -39,6 +40,11 @@ public final class UnifiedOrderResponse {
public record OrderRelationSummary(String role, Long rootOrderId, Long parentOrderId, public record OrderRelationSummary(String role, Long rootOrderId, Long parentOrderId,
Integer splitSequence, Integer childCount) {} Integer splitSequence, Integer childCount) {}
/** Platform-only Firstleg co-load group view; identifiers stay strings for routing only. */
public record CoLoadGroupSummary(String groupId, String groupNo, String status,
Integer memberCount, List<CoLoadGroupMemberSummary> members) {}
public record CoLoadGroupMemberSummary(String orderId, String inboundCode) {}
public record Detail(Row summary, OrderDetailResponse order, public record Detail(Row summary, OrderDetailResponse order,
List<TimelineEvent> timeline) {} List<TimelineEvent> timeline) {}
@@ -5,6 +5,7 @@ import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.core.api.ResultCode; import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException; import com.baiyue.common.core.exception.BizException;
import com.baiyue.order.client.FirstlegUnifiedOrderClient; import com.baiyue.order.client.FirstlegUnifiedOrderClient;
import com.baiyue.order.client.FirstlegColoadGroupClient;
import com.baiyue.order.dto.OrderDetailResponse; import com.baiyue.order.dto.OrderDetailResponse;
import com.baiyue.order.dto.UnifiedOrderQuery; import com.baiyue.order.dto.UnifiedOrderQuery;
import com.baiyue.order.dto.UnifiedOrderResponse; import com.baiyue.order.dto.UnifiedOrderResponse;
@@ -44,15 +45,26 @@ public class UnifiedOrderQueryService {
@Resource private OrderRepository orders; @Resource private OrderRepository orders;
@Resource private FirstlegUnifiedOrderClient firstleg; @Resource private FirstlegUnifiedOrderClient firstleg;
@Resource private FirstlegColoadGroupClient coloadGroups;
@Value("${baiyue.security.internal-service-token}") private String internalToken; @Value("${baiyue.security.internal-service-token}") private String internalToken;
public PageResult<UnifiedOrderResponse.Row> page(Long tenantId, UnifiedOrderQuery query) { public PageResult<UnifiedOrderResponse.Row> page(Long tenantId, UnifiedOrderQuery query) {
return page(tenantId, query, null);
}
public PageResult<UnifiedOrderResponse.Row> platformPage(Long tenantId, UnifiedOrderQuery query,
String authorization) {
return page(tenantId, query, authorization);
}
private PageResult<UnifiedOrderResponse.Row> page(Long tenantId, UnifiedOrderQuery query,
String platformAuthorization) {
validate(query); validate(query);
if (requiresFirstlegQuery(query)) { if (requiresFirstlegQuery(query)) {
if ("PENDING_REVIEW".equals(query.mainStatus())) { if ("PENDING_REVIEW".equals(query.mainStatus())) {
return new PageResult<>(List.of(), query.page(), query.size(), 0, 0); return new PageResult<>(List.of(), query.page(), query.size(), 0, 0);
} }
return pageFromFirstleg(tenantId, query); return pageFromFirstleg(tenantId, query, platformAuthorization);
} }
List<OrderStatus> statuses = "PENDING_REVIEW".equals(query.mainStatus()) List<OrderStatus> statuses = "PENDING_REVIEW".equals(query.mainStatus())
? List.of(OrderStatus.SUBMITTED) : ACTIVE_ORDER_STATUSES; ? List.of(OrderStatus.SUBMITTED) : ACTIVE_ORDER_STATUSES;
@@ -64,13 +76,24 @@ public class UnifiedOrderQueryService {
Map<Long, CargoSummary> cargoSummaries = cargoSummaries(tenantId, Map<Long, CargoSummary> cargoSummaries = cargoSummaries(tenantId,
source.records().stream().map(OrderEntity::getId).toList()); source.records().stream().map(OrderEntity::getId).toList());
Map<Long, Integer> childCounts = childCounts(source.records()); Map<Long, Integer> childCounts = childCounts(source.records());
Map<Long, UnifiedOrderResponse.CoLoadGroupSummary> coLoadByOrder = coLoadGroups(
source.records().stream().map(OrderEntity::getId).toList(), platformAuthorization);
List<UnifiedOrderResponse.Row> rows = source.records().stream() List<UnifiedOrderResponse.Row> rows = source.records().stream()
.map(order -> row(order, inboundByOrder.get(order.getId()), latestNotes.get(order.getId()), .map(order -> row(order, inboundByOrder.get(order.getId()), latestNotes.get(order.getId()),
childCounts, cargoSummaries.get(order.getId()))).toList(); childCounts, cargoSummaries.get(order.getId()), coLoadByOrder.get(order.getId()))).toList();
return new PageResult<>(rows, source.page(), source.size(), source.total(), source.pages()); return new PageResult<>(rows, source.page(), source.size(), source.total(), source.pages());
} }
public UnifiedOrderResponse.Detail detail(long tenantId, long orderId, boolean includeInternalNotes) { public UnifiedOrderResponse.Detail detail(long tenantId, long orderId, boolean includeInternalNotes) {
return detail(tenantId, orderId, includeInternalNotes, null);
}
public UnifiedOrderResponse.Detail platformDetail(long tenantId, long orderId, String authorization) {
return detail(tenantId, orderId, true, authorization);
}
private UnifiedOrderResponse.Detail detail(long tenantId, long orderId, boolean includeInternalNotes,
String platformAuthorization) {
OrderEntity order = orders.findOwned(tenantId, orderId); OrderEntity order = orders.findOwned(tenantId, orderId);
if (order == null) throw new BizException(ResultCode.NOT_FOUND, "订单不存在"); if (order == null) throw new BizException(ResultCode.NOT_FOUND, "订单不存在");
OrderDetailResponse orderDetail = new OrderDetailResponse(order, orders.findCargo(tenantId, orderId), OrderDetailResponse orderDetail = new OrderDetailResponse(order, orders.findCargo(tenantId, orderId),
@@ -90,7 +113,8 @@ public class UnifiedOrderQueryService {
.findFirst() .findFirst()
.orElse(null)); .orElse(null));
UnifiedOrderResponse.Row summary = row(order, inbound == null ? null : inbound.summary(), latestNote, UnifiedOrderResponse.Row summary = row(order, inbound == null ? null : inbound.summary(), latestNote,
childCounts(List.of(order)), CargoSummary.from(orderDetail.cargoes()), creatorName); childCounts(List.of(order)), CargoSummary.from(orderDetail.cargoes()), creatorName,
coLoadGroups(List.of(orderId), platformAuthorization).get(orderId));
List<UnifiedOrderResponse.TimelineEvent> timeline = new ArrayList<>(); List<UnifiedOrderResponse.TimelineEvent> timeline = new ArrayList<>();
orderDetail.statusHistory().forEach(item -> timeline.add(new UnifiedOrderResponse.TimelineEvent( orderDetail.statusHistory().forEach(item -> timeline.add(new UnifiedOrderResponse.TimelineEvent(
"ORDER", "STATUS", orderMainStatus(item.getToStatus()), item.getAction(), item.getReason(), "ORDER", "STATUS", orderMainStatus(item.getToStatus()), item.getAction(), item.getReason(),
@@ -105,7 +129,8 @@ public class UnifiedOrderQueryService {
return new UnifiedOrderResponse.Detail(summary, orderDetail, timeline); return new UnifiedOrderResponse.Detail(summary, orderDetail, timeline);
} }
private PageResult<UnifiedOrderResponse.Row> pageFromFirstleg(Long tenantId, UnifiedOrderQuery query) { private PageResult<UnifiedOrderResponse.Row> pageFromFirstleg(Long tenantId, UnifiedOrderQuery query,
String platformAuthorization) {
List<Long> candidateOrderIds = StringUtils.hasText(query.fbaCode()) List<Long> candidateOrderIds = StringUtils.hasText(query.fbaCode())
? orders.findOrderIdsBySkuNo(tenantId, query.fbaCode()) : null; ? orders.findOrderIdsBySkuNo(tenantId, query.fbaCode()) : null;
if (candidateOrderIds != null && candidateOrderIds.isEmpty()) { if (candidateOrderIds != null && candidateOrderIds.isEmpty()) {
@@ -126,10 +151,13 @@ public class UnifiedOrderQueryService {
Map<Long, String> latestNotes = latestNotes(tenantId, orderIds); Map<Long, String> latestNotes = latestNotes(tenantId, orderIds);
Map<Long, CargoSummary> cargoSummaries = cargoSummaries(tenantId, orderIds); Map<Long, CargoSummary> cargoSummaries = cargoSummaries(tenantId, orderIds);
Map<Long, Integer> childCounts = childCounts(List.copyOf(orderById.values())); Map<Long, Integer> childCounts = childCounts(List.copyOf(orderById.values()));
Map<Long, UnifiedOrderResponse.CoLoadGroupSummary> coLoadByOrder =
coLoadGroups(orderIds, platformAuthorization);
List<UnifiedOrderResponse.Row> records = source.records().stream() List<UnifiedOrderResponse.Row> records = source.records().stream()
.map(summary -> row(summary, orderById.get(summary.inbound().orderId()), .map(summary -> row(summary, orderById.get(summary.inbound().orderId()),
latestNotes.get(summary.inbound().orderId()), childCounts, latestNotes.get(summary.inbound().orderId()), childCounts,
cargoSummaries.get(summary.inbound().orderId()))) cargoSummaries.get(summary.inbound().orderId()),
coLoadByOrder.get(summary.inbound().orderId())))
.toList(); .toList();
return new PageResult<>(records, source.page(), source.size(), source.total(), source.pages()); return new PageResult<>(records, source.page(), source.size(), source.total(), source.pages());
} }
@@ -168,6 +196,40 @@ public class UnifiedOrderQueryService {
.collect(Collectors.toMap(OrderEntity::getParentOrderId, ignored -> 1, Integer::sum)); .collect(Collectors.toMap(OrderEntity::getParentOrderId, ignored -> 1, Integer::sum));
} }
private Map<Long, UnifiedOrderResponse.CoLoadGroupSummary> coLoadGroups(
List<Long> orderIds, String platformAuthorization) {
if (!StringUtils.hasText(platformAuthorization) || orderIds.isEmpty()) return Map.of();
List<FirstlegColoadGroupClient.OrderGroupInfo> data = require(
call(() -> coloadGroups.batchQuery(platformAuthorization,
new FirstlegColoadGroupClient.BatchQueryRequest(orderIds.stream().distinct().toList()))),
"同柜组摘要响应无效");
List<Long> memberOrderIds = data.stream()
.filter(Objects::nonNull).map(FirstlegColoadGroupClient.OrderGroupInfo::group)
.filter(Objects::nonNull).flatMap(group -> safeMembers(group).stream())
.map(FirstlegColoadGroupClient.Member::orderId).filter(Objects::nonNull).distinct().toList();
Map<Long, String> inboundCodeByOrder = orders.findByIds(null, memberOrderIds).stream()
.collect(Collectors.toMap(OrderEntity::getId, OrderEntity::getInboundCode, (left, right) -> left));
return data.stream().filter(Objects::nonNull)
.filter(info -> info.orderId() != null && info.group() != null)
.collect(Collectors.toMap(FirstlegColoadGroupClient.OrderGroupInfo::orderId,
info -> coLoadGroup(info.group(), inboundCodeByOrder), (left, right) -> left));
}
private List<FirstlegColoadGroupClient.Member> safeMembers(FirstlegColoadGroupClient.GroupSnapshot group) {
return group.members() == null ? List.of() : group.members();
}
private UnifiedOrderResponse.CoLoadGroupSummary coLoadGroup(
FirstlegColoadGroupClient.GroupSnapshot group, Map<Long, String> inboundCodeByOrder) {
List<UnifiedOrderResponse.CoLoadGroupMemberSummary> members = safeMembers(group).stream()
.filter(member -> member.orderId() != null)
.map(member -> new UnifiedOrderResponse.CoLoadGroupMemberSummary(
string(member.orderId()), inboundCodeByOrder.get(member.orderId())))
.toList();
return new UnifiedOrderResponse.CoLoadGroupSummary(
string(group.groupId()), group.groupNo(), group.status(), members.size(), members);
}
private UnifiedOrderResponse.OrderRelationSummary relation(OrderEntity order, private UnifiedOrderResponse.OrderRelationSummary relation(OrderEntity order,
Map<Long, Integer> childCounts) { Map<Long, Integer> childCounts) {
if (order == null) return null; if (order == null) return null;
@@ -184,12 +246,20 @@ public class UnifiedOrderQueryService {
private UnifiedOrderResponse.Row row(OrderEntity order, FirstlegUnifiedOrderClient.Summary summary, private UnifiedOrderResponse.Row row(OrderEntity order, FirstlegUnifiedOrderClient.Summary summary,
String latestInternalNote, Map<Long, Integer> childCounts, String latestInternalNote, Map<Long, Integer> childCounts,
CargoSummary cargo) { CargoSummary cargo) {
return row(order, summary, latestInternalNote, childCounts, cargo, null); return row(order, summary, latestInternalNote, childCounts, cargo, null, null);
} }
private UnifiedOrderResponse.Row row(OrderEntity order, FirstlegUnifiedOrderClient.Summary summary, private UnifiedOrderResponse.Row row(OrderEntity order, FirstlegUnifiedOrderClient.Summary summary,
String latestInternalNote, Map<Long, Integer> childCounts, String latestInternalNote, Map<Long, Integer> childCounts,
CargoSummary cargo, String creatorName) { CargoSummary cargo,
UnifiedOrderResponse.CoLoadGroupSummary coLoadGroup) {
return row(order, summary, latestInternalNote, childCounts, cargo, null, coLoadGroup);
}
private UnifiedOrderResponse.Row row(OrderEntity order, FirstlegUnifiedOrderClient.Summary summary,
String latestInternalNote, Map<Long, Integer> childCounts,
CargoSummary cargo, String creatorName,
UnifiedOrderResponse.CoLoadGroupSummary coLoadGroup) {
if (summary == null) { if (summary == null) {
String mainStatus = orderMainStatus(order.getStatus()); String mainStatus = orderMainStatus(order.getStatus());
return new UnifiedOrderResponse.Row(order.getId(), order.getTenantId(), order.getOrderNo(), mainStatus, return new UnifiedOrderResponse.Row(order.getId(), order.getTenantId(), order.getOrderNo(), mainStatus,
@@ -204,7 +274,7 @@ public class UnifiedOrderQueryService {
order.getCustomsInspection(), order.getDestinationType(), order.getAmazonWarehouseCode(), order.getCustomsInspection(), order.getDestinationType(), order.getAmazonWarehouseCode(),
null, null, null, null, netWeightKg(cargo), null, null, order.getExpressCompany(), order.getExpressTrackingNo(), null, null, null, null, netWeightKg(cargo), null, null, order.getExpressCompany(), order.getExpressTrackingNo(),
StringUtils.hasText(creatorName) ? creatorName : string(order.getCreatedBy()), skuNos(cargo), totalQuantity(cargo), shipmentId(order, cargo), referenceId(order, cargo), StringUtils.hasText(creatorName) ? creatorName : string(order.getCreatedBy()), skuNos(cargo), totalQuantity(cargo), shipmentId(order, cargo), referenceId(order, cargo),
null, null, null, null, null, null, null, null, null, null, coLoadGroup,
order.getCustomerRemark(), latestInternalNote, order.getCustomerRemark(), latestInternalNote,
order.getCreatedAt(), order.getUpdatedAt(), order.getRequestedPickupAt()); order.getCreatedAt(), order.getUpdatedAt(), order.getRequestedPickupAt());
} }
@@ -224,13 +294,14 @@ public class UnifiedOrderQueryService {
inbound.measureCompletedAt(), inbound.inboundedAt(), inbound.expressCompanyName(), inbound.expressTrackingNo(), inbound.measureCompletedAt(), inbound.inboundedAt(), inbound.expressCompanyName(), inbound.expressTrackingNo(),
StringUtils.hasText(creatorName) ? creatorName : inbound.createdByName(), skuNos(cargo), totalQuantity(cargo), shipmentId(order, cargo), referenceId(order, cargo), measurement(inbound), StringUtils.hasText(creatorName) ? creatorName : inbound.createdByName(), skuNos(cargo), totalQuantity(cargo), shipmentId(order, cargo), referenceId(order, cargo), measurement(inbound),
preload(summary.preload()), loading(summary.loading()), preload(summary.preload()), loading(summary.loading()),
delivery(summary.delivery()), transport(inbound), order.getCustomerRemark(), latestInternalNote, delivery(summary.delivery()), transport(inbound), coLoadGroup, order.getCustomerRemark(), latestInternalNote,
order.getCreatedAt(), order.getUpdatedAt(), inbound.requestedPickupAt()); order.getCreatedAt(), order.getUpdatedAt(), inbound.requestedPickupAt());
} }
private UnifiedOrderResponse.Row row(FirstlegUnifiedOrderClient.Summary summary, OrderEntity order, private UnifiedOrderResponse.Row row(FirstlegUnifiedOrderClient.Summary summary, OrderEntity order,
String latestInternalNote, Map<Long, Integer> childCounts, String latestInternalNote, Map<Long, Integer> childCounts,
CargoSummary cargo) { CargoSummary cargo,
UnifiedOrderResponse.CoLoadGroupSummary coLoadGroup) {
var inbound = summary.inbound(); var inbound = summary.inbound();
return new UnifiedOrderResponse.Row(inbound.orderId(), inbound.tenantId(), inbound.orderNo(), return new UnifiedOrderResponse.Row(inbound.orderId(), inbound.tenantId(), inbound.orderNo(),
summary.mainStatus(), summary.nextAction(), relation(order, childCounts), summary.mainStatus(), summary.nextAction(), relation(order, childCounts),
@@ -250,7 +321,8 @@ public class UnifiedOrderQueryService {
inbound.measureCompletedAt(), inbound.inboundedAt(), inbound.expressCompanyName(), inbound.expressTrackingNo(), inbound.measureCompletedAt(), inbound.inboundedAt(), inbound.expressCompanyName(), inbound.expressTrackingNo(),
inbound.createdByName(), skuNos(cargo), totalQuantity(cargo), shipmentId(order, cargo), referenceId(order, cargo), inbound.createdByName(), skuNos(cargo), totalQuantity(cargo), shipmentId(order, cargo), referenceId(order, cargo),
measurement(inbound), preload(summary.preload()), loading(summary.loading()), measurement(inbound), preload(summary.preload()), loading(summary.loading()),
delivery(summary.delivery()), transport(inbound), order == null ? null : order.getCustomerRemark(), latestInternalNote, delivery(summary.delivery()), transport(inbound), coLoadGroup,
order == null ? null : order.getCustomerRemark(), latestInternalNote,
inbound.createdAt(), inbound.updatedAt(), inbound.requestedPickupAt()); inbound.createdAt(), inbound.updatedAt(), inbound.requestedPickupAt());
} }