优化
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
| 柜次费用明细 | `controller/PlatformContainerFeeDetailController.java`、`service/ContainerFeeDetailService.java`、`dto/ContainerFeeDetailDtos.java`;Admin 消费端见 `../../apps/admin-web/src/modules/loading-lists/api/containerFees.ts` |
|
||||
| 财务整柜成本分摊快照 | `loading/controller/InternalFinanceCostSnapshotController.java`、`loading/service/FinanceCostSnapshotService.java` |
|
||||
| 分柜与预装 | `split/controller/PlatformSplitPreloadController.java`、`controller/PlatformLoadingListController.java` |
|
||||
| 同柜不可拆组 | `loading/coload/controller/PlatformColoadGroupController.java`、`service/ColoadGroupService.java`、`persistence/ColoadGroupMapper.java`;契约 `src/main/resources/openapi/coload-groups-v1.yaml`。请求按订单 ID 建组,服务端冻结每票全部 `WAIT_SPLIT` 可用入仓片段与满箱数;仅要求同国内仓、同渠道,允许跨客户及不同目的仓港;分配、移除、取消均保持整组原子 |
|
||||
| 货柜与智能排柜 | `controller/PlatformContainerController.java`、`PlatformSmartLoadingController.java`、`service/SmartLoadingService.java`、`repository/SmartLoadingRepository.java`、`loading/`;按入仓码候选与箱数分配分别从 `/inbound-candidates`、`/plans/{planId}/inbound-assignments` 进入,明细规格拆分留在服务端 |
|
||||
| 货柜资料、PDA 证据与业务总览 | `controller/PlatformContainerController.java` 的 `/{id}/overview`、`/{id}/documents/{documentType}`,聚合 `mapper/ContainerOverviewMapper.java`;契约 `src/main/resources/openapi/phase3-container-overview-v1.yaml`,四类资料固定为目的港清关、国内出口报关、DO、PU 的当前唯一版本 |
|
||||
| PDA 拣货与装柜 | `controller/PlatformLoadingPdaController.java`、`loading/controller/PlatformLoadingDeliveryController.java` |
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.LoadingAssociationDtos.Summary;
|
||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||
import com.baiyue.firstleg.service.LoadingAssociationQueryService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/first-leg/loading-associations")
|
||||
public class PlatformLoadingAssociationController {
|
||||
@Resource
|
||||
private LoadingAssociationQueryService service;
|
||||
|
||||
@GetMapping("/summary")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:smart-loading:list')")
|
||||
public ApiResponse<Summary> summary(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required = false) @Positive Long inboundId,
|
||||
@RequestParam(required = false) @Size(max = 40) String inboundCode) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.summary(inboundId, inboundCode));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Admin 统一关联抽屉使用的装柜/PDA只读业务摘要。 */
|
||||
public final class LoadingAssociationDtos {
|
||||
private LoadingAssociationDtos() {
|
||||
}
|
||||
|
||||
public record ItemReference(String itemId) {
|
||||
}
|
||||
|
||||
public record Summary(
|
||||
String inboundId,
|
||||
String inboundCode,
|
||||
String orderNo,
|
||||
String loadingPlanId,
|
||||
String loadingPlanNo,
|
||||
String containerBookingId,
|
||||
String cabinetNo,
|
||||
String containerNo,
|
||||
String loadingStatus,
|
||||
String taskId,
|
||||
String pdaTaskStatus,
|
||||
String coLoadGroupId,
|
||||
String coLoadGroupNo,
|
||||
List<ItemReference> items
|
||||
) {
|
||||
public Summary {
|
||||
items = items == null ? List.of() : List.copyOf(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Firstleg 单库内按入仓片段读取装柜、PDA与同柜组稳定关联。 */
|
||||
@Mapper
|
||||
public interface LoadingAssociationMapper {
|
||||
String SELECT_ASSOCIATIONS = """
|
||||
SELECT inbound.id inboundId,inbound.inbound_code inboundCode,inbound.order_no orderNo,
|
||||
item.id itemId,plan.id loadingPlanId,plan.plan_no loadingPlanNo,
|
||||
booking.id containerBookingId,booking.cabinet_no cabinetNo,
|
||||
booking.container_no containerNo,plan.plan_status loadingStatus,
|
||||
task.id taskId,
|
||||
CASE WHEN task.id IS NULL THEN NULL
|
||||
WHEN task.task_status='COMPLETED' THEN 'COMPLETED'
|
||||
WHEN task.task_status='COMPLETING' THEN 'PROCESSING'
|
||||
WHEN task.task_status='COMPLETION_FAILED' THEN 'COMPLETION_FAILED'
|
||||
WHEN assignment.pick_task_id IS NULL THEN 'UNASSIGNED'
|
||||
WHEN NOT EXISTS (SELECT 1 FROM fl_loading_pick_item unresolved
|
||||
WHERE unresolved.pick_task_id=task.id
|
||||
AND unresolved.item_status IN ('PENDING','PICKING'))
|
||||
THEN 'AWAITING_REVIEW'
|
||||
ELSE 'IN_PROGRESS' END pdaTaskStatus,
|
||||
group_row.id coLoadGroupId,group_row.group_no coLoadGroupNo
|
||||
FROM fl_inbound_order inbound
|
||||
LEFT JOIN fl_loading_plan_item item
|
||||
ON item.account_id=inbound.tenant_id AND item.inbound_id=inbound.id AND item.deleted=0
|
||||
LEFT JOIN fl_loading_plan plan ON plan.id=item.loading_plan_id
|
||||
LEFT JOIN fl_container_booking booking ON booking.id=plan.container_booking_id
|
||||
LEFT JOIN fl_loading_pick_task task ON task.loading_plan_id=plan.id
|
||||
LEFT JOIN fl_loading_pick_assignment assignment ON assignment.pick_task_id=task.id
|
||||
LEFT JOIN fl_coload_group_member group_member ON group_member.id=(
|
||||
SELECT MAX(member_latest.id) FROM fl_coload_group_member member_latest
|
||||
JOIN fl_coload_group group_latest ON group_latest.id=member_latest.group_id
|
||||
AND group_latest.group_status<>'CANCELLED'
|
||||
WHERE member_latest.tenant_id=inbound.tenant_id
|
||||
AND member_latest.inbound_id=inbound.id)
|
||||
LEFT JOIN fl_coload_group group_row ON group_row.id=group_member.group_id
|
||||
WHERE inbound.deleted=0
|
||||
""";
|
||||
|
||||
@Select(SELECT_ASSOCIATIONS + " AND inbound.id=#{inboundId} ORDER BY item.id")
|
||||
List<Row> byInboundId(@Param("inboundId") long inboundId);
|
||||
|
||||
@Select(SELECT_ASSOCIATIONS + " AND inbound.inbound_code=#{inboundCode} ORDER BY inbound.id,item.id")
|
||||
List<Row> byInboundCode(@Param("inboundCode") String inboundCode);
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
class Row {
|
||||
private Long inboundId;
|
||||
private String inboundCode;
|
||||
private String orderNo;
|
||||
private Long itemId;
|
||||
private Long loadingPlanId;
|
||||
private String loadingPlanNo;
|
||||
private Long containerBookingId;
|
||||
private String cabinetNo;
|
||||
private String containerNo;
|
||||
private String loadingStatus;
|
||||
private Long taskId;
|
||||
private String pdaTaskStatus;
|
||||
private Long coLoadGroupId;
|
||||
private String coLoadGroupNo;
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.LoadingAssociationDtos.ItemReference;
|
||||
import com.baiyue.firstleg.dto.LoadingAssociationDtos.Summary;
|
||||
import com.baiyue.firstleg.mapper.LoadingAssociationMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingAssociationMapper.Row;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/** 装柜关联只读查询;inboundCode解析和业务摘要编排均不改变现有写接口主键。 */
|
||||
@Service
|
||||
public class LoadingAssociationQueryService {
|
||||
@Resource
|
||||
private LoadingAssociationMapper mapper;
|
||||
|
||||
public Summary summary(Long inboundId, String inboundCode) {
|
||||
String code = StringUtils.hasText(inboundCode) ? inboundCode.trim() : null;
|
||||
if ((inboundId == null) == (code == null)) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "inboundId与inboundCode必须且只能提供一个");
|
||||
}
|
||||
if (inboundId != null && inboundId <= 0) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "inboundId必须大于0");
|
||||
}
|
||||
List<Row> rows = inboundId == null ? mapper.byInboundCode(code) : mapper.byInboundId(inboundId);
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "入仓片段不存在");
|
||||
}
|
||||
Row first = rows.get(0);
|
||||
requireSingleValue(rows.stream().map(Row::getInboundId).toList(), "inboundCode匹配多个入仓片段");
|
||||
requireSingleValue(rows.stream().map(Row::getLoadingPlanId).filter(Objects::nonNull).toList(),
|
||||
"同一入仓片段关联多个有效装柜计划");
|
||||
requireSingleValue(rows.stream().map(Row::getTaskId).filter(Objects::nonNull).toList(),
|
||||
"同一入仓片段关联多个PDA任务");
|
||||
requireSingleValue(rows.stream().map(Row::getCoLoadGroupId).filter(Objects::nonNull).toList(),
|
||||
"同一入仓片段关联多个有效同柜组");
|
||||
List<ItemReference> items = rows.stream().map(Row::getItemId).filter(Objects::nonNull).distinct()
|
||||
.sorted().map(String::valueOf).map(ItemReference::new).toList();
|
||||
return new Summary(id(first.getInboundId()), first.getInboundCode(), first.getOrderNo(),
|
||||
id(first.getLoadingPlanId()), first.getLoadingPlanNo(), id(first.getContainerBookingId()),
|
||||
first.getCabinetNo(), first.getContainerNo(), first.getLoadingStatus(), id(first.getTaskId()),
|
||||
first.getPdaTaskStatus(), id(first.getCoLoadGroupId()), first.getCoLoadGroupNo(), items);
|
||||
}
|
||||
|
||||
private static void requireSingleValue(List<Long> values, String message) {
|
||||
if (values.stream().distinct().limit(2).count() > 1) {
|
||||
throw new BizException(ResultCode.CONFLICT, message);
|
||||
}
|
||||
}
|
||||
|
||||
private static String id(Long value) {
|
||||
return value == null ? null : value.toString();
|
||||
}
|
||||
}
|
||||
@@ -414,6 +414,29 @@ paths:
|
||||
- {name: bookingId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
- {name: warehouseId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
responses: {'200': {description: 成功,data 为入仓码级候选列表}}
|
||||
/api/platform/v1/first-leg/loading-associations/summary:
|
||||
get:
|
||||
summary: 查询入仓片段的装柜与国内PDA关联摘要
|
||||
description: >-
|
||||
需要 platform:firstleg:smart-loading:list 权限。inboundId 与 inboundCode 必须且只能提供一个;
|
||||
inboundCode 是订单当前有效入仓片段业务号。响应优先提供 orderNo、loadingPlanNo、cabinetNo/containerNo、
|
||||
loadingStatus、pdaTaskStatus 与 coLoadGroupNo 供页面展示;loadingPlanId、containerBookingId、taskId、
|
||||
coLoadGroupId 和 items[].itemId 仅为技术关联,不得替代业务号作为UI主标识。
|
||||
parameters:
|
||||
- {name: inboundId, in: query, required: false, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
- {name: inboundCode, in: query, required: false, schema: {type: string, minLength: 1, maxLength: 40}}
|
||||
responses:
|
||||
'200':
|
||||
description: 成功或业务失败;未进入装柜时仍返回 inboundId/inboundCode,其余关联字段为空
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [code, message, data]
|
||||
properties:
|
||||
code: {type: string, example: '200'}
|
||||
message: {type: string}
|
||||
data: {$ref: '#/components/schemas/LoadingAssociationSummary'}
|
||||
/api/platform/v1/first-leg/smart-loading/plans/{planId}:
|
||||
get:
|
||||
summary: 查询智能装柜草稿详情
|
||||
@@ -1682,6 +1705,31 @@ components:
|
||||
expectedPlanVersion: {type: integer, minimum: 0}
|
||||
expectedItemVersion: {type: integer, minimum: 0}
|
||||
operationId: {type: string, minLength: 1, maxLength: 128}
|
||||
LoadingAssociationSummary:
|
||||
type: object
|
||||
required: [inboundId, inboundCode, orderNo, items]
|
||||
properties:
|
||||
inboundId: {type: string, pattern: '^[1-9][0-9]*$', description: 入仓片段内部技术ID}
|
||||
inboundCode: {type: string, minLength: 1, maxLength: 40, description: 订单当前入仓片段业务号,UI主展示}
|
||||
orderNo: {type: string, nullable: true, description: 订单业务号,UI主展示}
|
||||
loadingPlanId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: 装柜计划技术关联ID}
|
||||
loadingPlanNo: {type: string, nullable: true, pattern: '^LP-[0-9]{6}-[0-9]{4}$', description: 装柜计划业务号,UI主展示}
|
||||
containerBookingId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: 柜次技术关联ID}
|
||||
cabinetNo: {type: string, nullable: true, description: 稳定柜次业务号,UI主展示}
|
||||
containerNo: {type: string, nullable: true, description: 真实集装箱号,UI主展示}
|
||||
loadingStatus: {type: string, enum: [EDITING, LOADING, COMPLETED], nullable: true}
|
||||
taskId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: 国内PDA任务技术关联ID}
|
||||
pdaTaskStatus: {type: string, enum: [UNASSIGNED, IN_PROGRESS, AWAITING_REVIEW, PROCESSING, COMPLETION_FAILED, COMPLETED], nullable: true, description: 国内PDA任务业务状态}
|
||||
coLoadGroupId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: 同柜不可拆组技术关联ID}
|
||||
coLoadGroupNo: {type: string, nullable: true, description: 同柜不可拆组业务号,UI主展示}
|
||||
items:
|
||||
type: array
|
||||
description: 装柜明细技术关联;不作为UI主展示
|
||||
items:
|
||||
type: object
|
||||
required: [itemId]
|
||||
properties:
|
||||
itemId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
SmartLoadingPlan:
|
||||
type: object
|
||||
required: [id, planNo, status, bookingId, version, canAssign, canEdit, finalCompletionStatus, items]
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.LoadingAssociationDtos;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PlatformLoadingAssociationControllerContractTest {
|
||||
@Test
|
||||
void technicalAssociationIdsAreExplicitStringsInResponseDto() {
|
||||
assertThat(LoadingAssociationDtos.Summary.class.getRecordComponents())
|
||||
.filteredOn(component -> component.getName().endsWith("Id"))
|
||||
.allSatisfy(component -> assertThat(component.getType()).isEqualTo(String.class));
|
||||
assertThat(LoadingAssociationDtos.ItemReference.class.getRecordComponents()[0].getType())
|
||||
.isEqualTo(String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void summaryUsesSmartLoadingReadPermission() throws Exception {
|
||||
Method method = PlatformLoadingAssociationController.class.getDeclaredMethod(
|
||||
"summary", AuthenticatedUser.class, Long.class, String.class);
|
||||
|
||||
assertThat(method.getAnnotation(PreAuthorize.class).value())
|
||||
.isEqualTo("hasAuthority('platform:firstleg:smart-loading:list')");
|
||||
}
|
||||
|
||||
@Test
|
||||
void openApiKeepsBusinessNumbersPrimaryAndTechnicalIdsAsAssociations() throws Exception {
|
||||
String yaml = Files.readString(Path.of("src/main/resources/openapi/firstleg-v1.yaml"));
|
||||
|
||||
assertThat(yaml)
|
||||
.contains("/api/platform/v1/first-leg/loading-associations/summary:")
|
||||
.contains("inboundId 与 inboundCode 必须且只能提供一个")
|
||||
.contains("LoadingAssociationSummary:")
|
||||
.contains("orderNo:", "loadingPlanNo:", "cabinetNo:", "containerNo:", "coLoadGroupNo:")
|
||||
.contains("pdaTaskStatus:")
|
||||
.contains("loadingPlanId:", "containerBookingId:", "taskId:", "itemId:")
|
||||
.contains("仅为技术关联,不得替代业务号作为UI主标识");
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LoadingAssociationMapperContractTest {
|
||||
@Test
|
||||
void mapperIsExplicitAndReadsOnlyFirstlegOwnedTables() throws Exception {
|
||||
assertThat(LoadingAssociationMapper.class).hasAnnotation(Mapper.class);
|
||||
Method method = LoadingAssociationMapper.class.getDeclaredMethod("byInboundId", long.class);
|
||||
String sql = String.join(" ", method.getAnnotation(Select.class).value());
|
||||
|
||||
assertThat(sql)
|
||||
.contains("fl_inbound_order", "fl_loading_plan_item", "fl_loading_plan",
|
||||
"fl_container_booking", "fl_loading_pick_task", "fl_coload_group_member")
|
||||
.contains("plan.plan_no loadingPlanNo")
|
||||
.contains("inbound.order_no orderNo")
|
||||
.contains("booking.cabinet_no cabinetNo", "booking.container_no containerNo")
|
||||
.contains("group_row.group_no coLoadGroupNo")
|
||||
.contains("'UNASSIGNED'", "'IN_PROGRESS'", "'AWAITING_REVIEW'", "'PROCESSING'",
|
||||
"'COMPLETION_FAILED'", "'COMPLETED'")
|
||||
.doesNotContain("baiyue_order", "baiyue_warehouse", "baiyue_iam");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inboundCodeQueryUsesExactBusinessIdentifier() throws Exception {
|
||||
Method method = LoadingAssociationMapper.class.getDeclaredMethod("byInboundCode", String.class);
|
||||
String sql = String.join(" ", method.getAnnotation(Select.class).value());
|
||||
|
||||
assertThat(sql).contains("inbound.inbound_code=#{inboundCode}")
|
||||
.doesNotContain("LIKE CONCAT");
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.mapper.LoadingAssociationMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingAssociationMapper.Row;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
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.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(LoadingAssociationQueryService.class)
|
||||
class LoadingAssociationQueryServiceTest {
|
||||
@MockBean
|
||||
private LoadingAssociationMapper mapper;
|
||||
@Resource
|
||||
private LoadingAssociationQueryService service;
|
||||
|
||||
@Test
|
||||
void returnsBusinessIdentifiersAndPreservesEveryTechnicalItemReference() {
|
||||
Row first = row(800L);
|
||||
Row second = row(801L);
|
||||
when(mapper.byInboundCode("ZBYHK2608300001-2")).thenReturn(List.of(first, second));
|
||||
|
||||
var result = service.summary(null, " ZBYHK2608300001-2 ");
|
||||
|
||||
assertThat(result.inboundCode()).isEqualTo("ZBYHK2608300001-2");
|
||||
assertThat(result.orderNo()).isEqualTo("FO-260830-0001");
|
||||
assertThat(result.inboundId()).isEqualTo("100");
|
||||
assertThat(result.loadingPlanId()).isEqualTo("200");
|
||||
assertThat(result.loadingPlanNo()).isEqualTo("LP-260830-0001");
|
||||
assertThat(result.cabinetNo()).isEqualTo("CAB-260830-0001");
|
||||
assertThat(result.containerNo()).isEqualTo("MSCU1234567");
|
||||
assertThat(result.loadingStatus()).isEqualTo("LOADING");
|
||||
assertThat(result.pdaTaskStatus()).isEqualTo("AWAITING_REVIEW");
|
||||
assertThat(result.coLoadGroupNo()).isEqualTo("CLG-260830-0001");
|
||||
assertThat(result.items()).extracting(item -> item.itemId()).containsExactly("800", "801");
|
||||
verify(mapper).byInboundCode("ZBYHK2608300001-2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsFragmentIdentityWhenItHasNoLoadingAssociation() {
|
||||
Row row = new Row();
|
||||
row.setInboundId(100L);
|
||||
row.setInboundCode("ZBYHK2608300002");
|
||||
when(mapper.byInboundId(100L)).thenReturn(List.of(row));
|
||||
|
||||
var result = service.summary(100L, null);
|
||||
|
||||
assertThat(result.inboundCode()).isEqualTo("ZBYHK2608300002");
|
||||
assertThat(result.loadingPlanId()).isNull();
|
||||
assertThat(result.pdaTaskStatus()).isNull();
|
||||
assertThat(result.items()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectorMustBeExclusiveAndExactCodeCannotResolveMultipleFragments() {
|
||||
assertThatThrownBy(() -> service.summary(null, null))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("必须且只能");
|
||||
assertThatThrownBy(() -> service.summary(100L, "ZBYHK2608300001"))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("必须且只能");
|
||||
Row first = row(800L);
|
||||
Row other = row(801L);
|
||||
other.setInboundId(101L);
|
||||
when(mapper.byInboundCode("DUPLICATE")).thenReturn(List.of(first, other));
|
||||
|
||||
assertThatThrownBy(() -> service.summary(null, "DUPLICATE"))
|
||||
.isInstanceOfSatisfying(BizException.class, error ->
|
||||
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT));
|
||||
}
|
||||
|
||||
private static Row row(long itemId) {
|
||||
Row row = new Row();
|
||||
row.setInboundId(100L);
|
||||
row.setInboundCode("ZBYHK2608300001-2");
|
||||
row.setOrderNo("FO-260830-0001");
|
||||
row.setItemId(itemId);
|
||||
row.setLoadingPlanId(200L);
|
||||
row.setLoadingPlanNo("LP-260830-0001");
|
||||
row.setContainerBookingId(300L);
|
||||
row.setCabinetNo("CAB-260830-0001");
|
||||
row.setContainerNo("MSCU1234567");
|
||||
row.setLoadingStatus("LOADING");
|
||||
row.setTaskId(400L);
|
||||
row.setPdaTaskStatus("AWAITING_REVIEW");
|
||||
row.setCoLoadGroupId(500L);
|
||||
row.setCoLoadGroupNo("CLG-260830-0001");
|
||||
return row;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user