fix(firstleg): use short cabinet pallet identities

This commit is contained in:
2026-09-18 13:37:46 +08:00
parent 674cac8ec2
commit b0c2e1d967
13 changed files with 67 additions and 32 deletions
@@ -1884,7 +1884,7 @@ CREATE TABLE fl_customs_frozen_document_inbound (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='开始报关时冻结的客户资料覆盖范围';
CREATE TABLE fl_document_sequence (
sequence_key varchar(32) NOT NULL COMMENT '单据类型与北京时间日期组合键,例如CAB:20260818',
sequence_key varchar(32) NOT NULL COMMENT '单据序列键;日序列如CB:20260818,柜次长期序列固定为CABINET',
current_value int unsigned NOT NULL COMMENT '该类型当日已分配的最大序号',
PRIMARY KEY (sequence_key),
CONSTRAINT chk_fl_document_sequence_value CHECK ((current_value > 0))
@@ -3195,7 +3195,7 @@ CREATE TABLE fl_loading_pallet_label (
destination_warehouse_code varchar(64) NOT NULL COMMENT 'FBA仓编码快照',
destination_warehouse_name varchar(128) DEFAULT NULL COMMENT 'FBA仓名称快照',
pallet_index int unsigned NOT NULL COMMENT '当前柜当前FBA仓内从1开始的序号',
pallet_code varchar(128) NOT NULL COMMENT '托盘码:集装箱号-FBA仓编码-三位序号',
pallet_code varchar(128) NOT NULL COMMENT '托盘码:平台柜次号-FBA仓内部编号-三位序号;不依赖可跨提单复用的真实柜号',
created_at datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '生成时间',
PRIMARY KEY (id),
UNIQUE KEY uk_loading_pallet_code (pallet_code),
+1 -1
View File
@@ -9,7 +9,7 @@
## 模块入口
散货托盘码:`service/LoadingPalletLabelService.java`、`mapper/LoadingPalletLabelMapper.java`、`controller/PlatformLoadingListController.java`,契约 `src/main/resources/openapi/loading-lists-v1.yaml` 的 `/pallet-labels/prepare` 与 `/pallet-labels/print`。仅在计划 `LOADING/COMPLETED` 且真实柜号有效时,为非一件代发的 FBA 明细按柜号、FBA 地址内部编号、三位序号生成冻结标签;内部编号从 Warehouse 启用主数据读取,缺失或同柜重复时拒绝生成。重复生成须核对已有标签与明细一致,冲突直接拒绝。全部或按仓指定张数打印统一调用 Print `/internal/print/v1/pdfs/fba-pallet-labels`,模板 `FBA_PALLET_A4_LANDSCAPE_V1` 每托一页 297×210 mm,顶部为 SCK8 等 FBA 仓码,条码和底部文字为包含 141 等内部编号的完整托盘号,与海外补打共用版式;装柜完成通过 `LoadingCompletedOutboxService.java` 传递来源托盘。
柜次号与散货托盘码:`service/FirstlegDocumentNumberGenerator.java` 为新柜生成全局连续且不按日期重置的 `D0001`、`D0002` 柜次号;`service/LoadingPalletLabelService.java`、`mapper/LoadingPalletLabelMapper.java`、`controller/PlatformLoadingListController.java` 负责散货托盘码,契约为 `src/main/resources/openapi/loading-lists-v1.yaml` 的 `/pallet-labels/prepare` 与 `/pallet-labels/print`。仅在计划 `LOADING/COMPLETED` 且真实柜号有效时,为非一件代发的 FBA 明细按平台柜次号、FBA 地址内部编号、三位序号生成冻结标签;托盘码不依赖可跨提单复用的真实柜号,内部编号从 Warehouse 启用主数据读取,缺失或同柜重复时拒绝生成。重复生成须核对已有标签与明细一致,冲突直接拒绝。全部或按仓指定张数打印统一调用 Print `/internal/print/v1/pdfs/fba-pallet-labels`,模板 `FBA_PALLET_A4_LANDSCAPE_V1` 每托一页 297×210 mm,顶部为 SCK8 等 FBA 仓码,条码和底部文字为完整托盘号,与海外补打共用版式;装柜完成通过 `LoadingCompletedOutboxService.java` 传递来源托盘。
| 目标 | 入口 |
|---|---|
@@ -12,7 +12,7 @@ import java.util.List;
@Mapper
public interface LoadingPalletLabelMapper {
@Select("""
SELECT p.id AS plan_id,p.plan_no,p.plan_status,b.container_no,b.warehouse_id,
SELECT p.id AS plan_id,p.plan_no,p.plan_status,b.container_no,b.cabinet_no,b.warehouse_id,
i.id AS item_id,i.account_id,i.inbound_id,i.inbound_code,i.destination_warehouse_id,
i.destination_warehouse_code,i.destination_warehouse_name,
COALESCE(NULLIF(i.pallet_count,0),
@@ -55,6 +55,7 @@ public interface LoadingPalletLabelMapper {
@Getter @Setter
class SourceRow {
private Long planId; private String planNo; private String planStatus; private String containerNo;
private String cabinetNo;
private Long warehouseId; private Long itemId; private Long accountId; private Long inboundId;
private Long destinationWarehouseId;
private String inboundCode; private String destinationWarehouseCode; private String destinationWarehouseName;
@@ -63,7 +63,7 @@ public class ContainerService {
.map(ChannelRow::getChannelCode).anyMatch(channelGroup.channelCodes()::contains);
if (!activeGroup) throw conflict("所选物流渠道分组在当前仓库已失效,请重新选择");
ContainerBookingEntity value = new ContainerBookingEntity(); value.setId(IdWorker.getId());
value.setBookingNo(documentNumbers.next("CB")); value.setCabinetNo(documentNumbers.next("CAB"));
value.setBookingNo(documentNumbers.next("CB")); value.setCabinetNo(documentNumbers.nextCabinetNo());
value.setSourceType("SIMULATION"); value.setRecordStatus("ACTIVE");
value.setChannelGroupCode(channelGroup.code()); value.setChannelCode(null); value.setChannelName(null);
value.setWarehouseId(warehouse.id()); value.setWarehouseName(warehouse.warehouseName());
@@ -19,7 +19,7 @@ import java.util.Set;
public class FirstlegDocumentNumberGenerator {
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyMMdd", Locale.ROOT);
private static final Set<String> SUPPORTED_PREFIXES = Set.of("OB", "TA", "CB", "CAB", "LP", "DP");
private static final Set<String> SUPPORTED_PREFIXES = Set.of("OB", "TA", "CB", "LP", "DP");
private static final long MAX_DAILY_SEQUENCE = 9_999L;
private final JdbcTemplate jdbc;
@@ -42,16 +42,30 @@ public class FirstlegDocumentNumberGenerator {
}
LocalDate sequenceDate = LocalDate.now(clock);
String sequenceKey = prefix + ":" + DateTimeFormatter.BASIC_ISO_DATE.format(sequenceDate);
long sequence = nextSequence(sequenceKey);
if (sequence > MAX_DAILY_SEQUENCE) {
throw new BizException(ResultCode.CONFLICT, "当日" + prefix + "单据编号已用尽");
}
return prefix + "-" + DATE_FORMAT.format(sequenceDate) + "-" +
String.format(Locale.ROOT, "%04d", sequence);
}
/** 平台柜次号是长期业务身份,使用不按日期重置的 D 序号。 */
@Transactional(propagation = Propagation.MANDATORY)
public String nextCabinetNo() {
return "D" + String.format(Locale.ROOT, "%04d", nextSequence("CABINET"));
}
private long nextSequence(String sequenceKey) {
jdbc.update(
"INSERT INTO fl_document_sequence(sequence_key,current_value) " +
"VALUES(?,LAST_INSERT_ID(1)) ON DUPLICATE KEY UPDATE " +
"current_value=LAST_INSERT_ID(current_value+1)",
sequenceKey);
Long sequence = jdbc.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
if (sequence == null || sequence <= 0 || sequence > MAX_DAILY_SEQUENCE) {
throw new BizException(ResultCode.CONFLICT, "当日" + prefix + "单据编号已用尽");
if (sequence == null || sequence <= 0) {
throw new BizException(ResultCode.CONFLICT, "业务编号生成失败");
}
return prefix + "-" + DATE_FORMAT.format(sequenceDate) + "-" +
String.format(Locale.ROOT, "%04d", sequence);
return sequence;
}
}
@@ -35,7 +35,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
/** 散货托盘码按真实柜号、FBA 地址内部编号和仓内序号组成;一件代发继续使用装箱打托事实。 */
/** 散货托盘码按平台柜次号、FBA 地址内部编号和仓内序号组成;一件代发继续使用装箱打托事实。 */
@Service
public class LoadingPalletLabelService {
@Resource private LoadingPalletLabelMapper mapper;
@@ -107,7 +107,8 @@ public class LoadingPalletLabelService {
row.setItemId(source.getItemId()); row.setAccountId(source.getAccountId()); row.setInboundId(source.getInboundId());
row.setInboundCode(source.getInboundCode()); row.setContainerNo(containerNo);
row.setDestinationWarehouseCode(warehouseCode); row.setDestinationWarehouseName(source.getDestinationWarehouseName());
row.setPalletIndex(index); row.setPalletCode(containerNo + "-" + internalCode + "-" + String.format("%03d", index));
row.setPalletIndex(index); row.setPalletCode(plan.getCabinetNo() + "-" + internalCode + "-"
+ String.format("%03d", index));
if (row.getPalletCode().length() > 128) throw new BizException(ResultCode.CONFLICT, "托盘码长度超过系统限制");
result.add(row);
}
@@ -165,10 +166,14 @@ public class LoadingPalletLabelService {
}
private PalletLabel label(LabelRow row) { return new PalletLabel(row.getLabelId(), row.getItemId(), row.getAccountId(), row.getInboundId(), row.getInboundCode(), row.getContainerNo(), row.getDestinationWarehouseCode(), internalCode(row), row.getDestinationWarehouseName(), row.getPalletIndex(), row.getPalletCode()); }
private String internalCode(LabelRow row) {
String prefix = row.getContainerNo() + "-";
String suffix = "-" + String.format("%03d", row.getPalletIndex());
String palletCode = row.getPalletCode();
return palletCode.substring(prefix.length(), palletCode.length() - suffix.length());
String body = palletCode.substring(0, palletCode.length() - suffix.length());
int separator = body.lastIndexOf('-');
if (separator < 0 || separator == body.length() - 1) {
throw new BizException(ResultCode.CONFLICT, "托盘码缺少柜次号或 FBA 仓内部编号");
}
return body.substring(separator + 1);
}
private static String normalize(String value) { return value == null || value.isBlank() ? null : value.trim(); }
@@ -27,7 +27,7 @@ CALL phase3_add_allocation_picked_count();
DROP PROCEDURE phase3_add_allocation_picked_count;
CREATE TABLE IF NOT EXISTS fl_document_sequence (
sequence_key VARCHAR(32) NOT NULL COMMENT '单据类型与北京时间日期组合键,例如CAB:20260818',
sequence_key VARCHAR(32) NOT NULL COMMENT '单据序列键;日序列如CB:20260818,柜次长期序列固定为CABINET',
current_value INT UNSIGNED NOT NULL COMMENT '该类型当日已分配的最大序号',
PRIMARY KEY (sequence_key),
CONSTRAINT chk_fl_document_sequence_value CHECK (current_value > 0)
@@ -388,7 +388,7 @@ CREATE TABLE IF NOT EXISTS fl_loading_pallet_label (
destination_warehouse_code VARCHAR(64) NOT NULL COMMENT 'FBA仓编码快照',
destination_warehouse_name VARCHAR(128) NULL COMMENT 'FBA仓名称快照',
pallet_index INT UNSIGNED NOT NULL COMMENT '当前柜当前FBA仓内从1开始的序号',
pallet_code VARCHAR(128) NOT NULL COMMENT '托盘码:集装箱号-FBA仓编码-三位序号',
pallet_code VARCHAR(128) NOT NULL COMMENT '托盘码:平台柜次号-FBA仓内部编号-三位序号;不依赖可跨提单复用的真实柜号',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '生成时间',
PRIMARY KEY (id),
UNIQUE KEY uk_loading_pallet_code (pallet_code),
@@ -2199,7 +2199,7 @@ components:
planNo: {type: string, nullable: true, pattern: '^LP-[0-9]{6}-[0-9]{4}$', example: LP-260818-0001, description: 预装与确认装柜清单共用的稳定编号;未创建计划时为空}
bookingNo: {type: string, pattern: '^CB-[0-9]{6}-[0-9]{4}$', example: CB-260818-0001, description: 平台内部货柜登记号}
containerNo: {type: string, maxLength: 64, nullable: true}
cabinetNo: {type: string, pattern: '^CAB-[0-9]{6}-[0-9]{4}$', example: CAB-260818-0001, description: 平台稳定柜次号}
cabinetNo: {type: string, pattern: '^D[0-9]{4,}$', example: D0001, description: 平台全局连续且不按日期重置的稳定柜次号}
sourceType: {type: string, enum: [SIMULATION, OCEAN_BOOKING], description: SIMULATION 为内部模拟柜;OCEAN_BOOKING 为订舱登记直接生成的真实柜}
recordStatus: {type: string, enum: [ACTIVE, MERGED], description: 正常列表和详情仅返回 ACTIVE;真实订舱柜被模拟柜消费后变为 MERGED 且不再作为可操作柜返回}
mergedIntoContainerBookingId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: MERGED 源真实柜指向保留的模拟柜稳定 containerBookingId;ACTIVE 时为空}
@@ -301,7 +301,7 @@ components:
containerBookingId: {$ref: '#/components/schemas/LongId'}
preloadNo: {type: string, pattern: '^LP-[0-9]{6}-[0-9]{4}$', example: LP-260818-0001, description: 预装单号;按北京时间创建日逐日编号,确认后继续作为同一装柜清单号使用}
containerNo: {type: string, nullable: true, description: 集装箱号}
cabinetNo: {type: string, nullable: true, pattern: '^CAB-[0-9]{6}-[0-9]{4}$', example: CAB-260818-0001, description: 稳定柜次号;不得与真实集装箱号混用}
cabinetNo: {type: string, nullable: true, pattern: '^D[0-9]{4,}$', example: D0001, description: 平台全局连续且不按日期重置的稳定柜次号;不得与真实集装箱号混用}
customers: {type: array, description: 计划包含的客户, items: {$ref: '#/components/schemas/Customer'}}
warehouseId: {$ref: '#/components/schemas/NullableLongId'}
warehouseName: {type: string, nullable: true, description: 仓库名称}
@@ -359,7 +359,7 @@ components:
warehouseId: {$ref: '#/components/schemas/NullableLongId'}
warehouseName: {type: string, nullable: true, description: 仓库名称}
containerNo: {type: string, nullable: true, description: 集装箱号}
cabinetNo: {type: string, nullable: true, pattern: '^CAB-[0-9]{6}-[0-9]{4}$', example: CAB-260818-0001, description: 稳定柜次号}
cabinetNo: {type: string, nullable: true, pattern: '^D[0-9]{4,}$', example: D0001, description: 平台全局连续且不按日期重置的稳定柜次号}
status: {type: string, enum: [COMPLETED], description: 装柜清单状态}
totalPackageCount: {type: string, pattern: '^[0-9]+$', description: 总箱数}
totalGrossWeightKg: {type: number, description: 总毛重,单位千克}
@@ -74,7 +74,7 @@ components:
exception: {$ref: '#/components/schemas/ExceptionState'}
exceptionHistory: {type: array, items: {$ref: '#/components/schemas/ExceptionHistory'}}
timeline: {type: array, items: {$ref: '#/components/schemas/TimelineEntry'}}
Container: {type: object, properties: {id: {type: integer, format: int64}, bookingNo: {type: string, pattern: '^CB-[0-9]{6}-[0-9]{4}$', example: CB-260818-0001}, cabinetNo: {type: string, pattern: '^CAB-[0-9]{6}-[0-9]{4}$', example: CAB-260818-0001}, containerNo: {type: string}, status: {type: string}, warehouseId: {type: integer, format: int64}, warehouseName: {type: string}, voyageNo: {type: string}, vesselName: {type: string}, pickupCode: {type: string, pattern: '^[0-9]{1,64}$', nullable: true}, pickupCodeUpdatedBy: {type: integer, format: int64, nullable: true}, pickupCodeUpdatedByName: {type: string, nullable: true}, pickupCodeUpdatedAt: {type: string, format: date-time, nullable: true}, version: {type: integer, minimum: 0}}}
Container: {type: object, properties: {id: {type: integer, format: int64}, bookingNo: {type: string, pattern: '^CB-[0-9]{6}-[0-9]{4}$', example: CB-260818-0001}, cabinetNo: {type: string, pattern: '^D[0-9]{4,}$', example: D0001}, containerNo: {type: string}, status: {type: string}, warehouseId: {type: integer, format: int64}, warehouseName: {type: string}, voyageNo: {type: string}, vesselName: {type: string}, pickupCode: {type: string, pattern: '^[0-9]{1,64}$', nullable: true}, pickupCodeUpdatedBy: {type: integer, format: int64, nullable: true}, pickupCodeUpdatedByName: {type: string, nullable: true}, pickupCodeUpdatedAt: {type: string, format: date-time, nullable: true}, version: {type: integer, minimum: 0}}}
PdaSummary: {type: object, properties: {taskId: {type: integer, format: int64}, loadingPlanId: {type: integer, format: int64}, containerBookingId: {type: integer, format: int64}, taskStatus: {type: string, description: '持久化任务态:LOADING/COMPLETED'}, workflowStatus: {type: string, enum: [IN_PROGRESS, AWAITING_REVIEW, COMPLETED], description: 'PDA作业展示态'}, taskVersion: {type: integer}, itemCount: {type: integer}, completedItemCount: {type: integer}, unresolvedItemCount: {type: integer}, photoCounts: {type: array, items: {$ref: '#/components/schemas/PhotoCount'}}, photos: {type: array, items: {$ref: '#/components/schemas/Photo'}}, exceptionReasons: {type: array, items: {type: string}}}}
PhotoCount: {type: object, properties: {evidenceType: {type: string}, count: {type: integer}}}
Photo: {type: object, properties: {evidenceId: {type: integer, format: int64}, tenantId: {type: integer, format: int64}, inboundId: {type: integer, format: int64}, evidenceType: {type: string}, fileId: {type: integer, format: int64}, fileName: {type: string}, status: {type: string, enum: [ACTIVE, VOID]}, downloadPath: {type: string, nullable: true, description: '仅 ACTIVE 照片提供鉴权下载路径;VOID 历史照片为空'}, uploadedAt: {type: string, format: date-time}}}
@@ -53,7 +53,7 @@ class ContainerServiceTest {
@BeforeEach
void setUpWarehouse() {
when(documentNumbers.next("CB")).thenReturn("CB-260818-0001");
when(documentNumbers.next("CAB")).thenReturn("CAB-260818-0001");
when(documentNumbers.nextCabinetNo()).thenReturn("D0001");
when(smartLoadingService.createForContainer(anyLong(), anyLong(), anyString())).thenAnswer(invocation ->
new com.baiyue.firstleg.dto.SmartLoadingDtos.Plan(501L, "LP-260818-0001", "EDITING",
invocation.getArgument(0, Long.class), null, null, 0, true, true, null,
@@ -77,7 +77,7 @@ class ContainerServiceTest {
ContainerBookingEntity saved = captor.getValue();
assertThat(created).isSameAs(saved);
assertThat(saved.getBookingNo()).isEqualTo("CB-260818-0001");
assertThat(saved.getCabinetNo()).isEqualTo("CAB-260818-0001");
assertThat(saved.getCabinetNo()).isEqualTo("D0001");
assertThat(saved.getContainerNo()).isNull();
assertThat(saved.getOceanBookingId()).isNull();
assertThat(saved.getChannelGroupCode()).isEqualTo("MATSON_CLX");
@@ -42,11 +42,23 @@ class FirstlegDocumentNumberGeneratorTest {
assertThat(generator.next("OB")).isEqualTo("OB-260818-0001");
assertThat(generator.next("TA")).isEqualTo("TA-260818-0001");
assertThat(generator.next("CB")).isEqualTo("CB-260818-0001");
assertThat(generator.next("CAB")).isEqualTo("CAB-260818-0001");
assertThat(generator.next("LP")).isEqualTo("LP-260818-0001");
assertThat(generator.nextCabinetNo()).isEqualTo("D0001");
verify(jdbc).update(org.mockito.ArgumentMatchers.startsWith("INSERT INTO fl_document_sequence"),
eq("OB:20260818"));
verify(jdbc).update(org.mockito.ArgumentMatchers.startsWith("INSERT INTO fl_document_sequence"),
eq("CABINET"));
}
@Test
void cabinetSequenceDoesNotResetOrStopAtFourDigits() {
JdbcTemplate jdbc = mock(JdbcTemplate.class);
when(jdbc.queryForObject("SELECT LAST_INSERT_ID()", Long.class)).thenReturn(9_999L, 10_000L);
FirstlegDocumentNumberGenerator generator = new FirstlegDocumentNumberGenerator(jdbc, CLOCK);
assertThat(generator.nextCabinetNo()).isEqualTo("D9999");
assertThat(generator.nextCabinetNo()).isEqualTo("D10000");
}
@Test
@@ -56,6 +68,6 @@ class FirstlegDocumentNumberGeneratorTest {
assertThatThrownBy(() -> generator.next("MSCU")).isInstanceOf(IllegalArgumentException.class);
when(jdbc.queryForObject("SELECT LAST_INSERT_ID()", Long.class)).thenReturn(10_000L);
assertThatThrownBy(() -> generator.next("CAB")).isInstanceOf(BizException.class);
assertThatThrownBy(() -> generator.next("LP")).isInstanceOf(BizException.class);
}
}
@@ -22,9 +22,11 @@ class LoadingPalletLabelServiceTest {
ReflectionTestUtils.setField(service, "printClient", print);
ReflectionTestUtils.setField(service, "internalToken", "test-token");
var label1 = new com.baiyue.firstleg.dto.LoadingListDtos.PalletLabel(1L, 1L, 1L, 1L,
"IN-1", "MSCU6639870", "SCK8", "141", "FBA destination", 1, "MSCU6639870-141-001");
"IN-1", "MSCU6639870", "SCK8", "141", "FBA destination", 1,
"D0002-141-001");
var label2 = new com.baiyue.firstleg.dto.LoadingListDtos.PalletLabel(2L, 1L, 1L, 1L,
"IN-1", "MSCU6639870", "SCK8", "141", "FBA destination", 2, "MSCU6639870-141-002");
"IN-1", "MSCU6639870", "SCK8", "141", "FBA destination", 2,
"D0002-141-002");
var manifest = new com.baiyue.firstleg.dto.LoadingListDtos.PalletLabelManifest(10L, "LP-1", "MSCU6639870", 2,
List.of(new com.baiyue.firstleg.dto.LoadingListDtos.PalletLabelGroup("SCK8", "141", "FBA destination", 2, List.of(label1, label2))));
org.mockito.Mockito.doReturn(manifest).when(service).prepare(10L, 20L);
@@ -39,16 +41,16 @@ class LoadingPalletLabelServiceTest {
assertThat(requests.get(0).pageSpec().widthMm()).isEqualByComparingTo("297");
assertThat(requests.get(0).pageSpec().heightMm()).isEqualByComparingTo("210");
assertThat(requests.get(0).labels()).extracting(com.baiyue.firstleg.client.PrintPdfClient.FbaPalletLabel::palletCode)
.containsExactly("MSCU6639870-141-001", "MSCU6639870-141-002");
.containsExactly("D0002-141-001", "D0002-141-002");
assertThat(requests.get(1).labels()).singleElement().satisfies(label -> {
assertThat(label.fbaWarehouseCode()).isEqualTo("SCK8");
assertThat(label.palletCode()).isEqualTo("MSCU6639870-141-001");
assertThat(label.palletCode()).isEqualTo("D0002-141-001");
});
org.mockito.Mockito.verify(print, org.mockito.Mockito.never()).generateInboundMarks(any(), any(), any());
}
@Test
void generatesStableIndexesPerContainerAndFbaAndSkipsDropship() {
void generatesStableIndexesPerCabinetAndFbaAndSkipsDropship() {
LoadingPalletLabelMapper mapper = mock(LoadingPalletLabelMapper.class);
LoadingPalletLabelService service = new LoadingPalletLabelService();
ReflectionTestUtils.setField(service, "mapper", mapper);
@@ -72,13 +74,14 @@ class LoadingPalletLabelServiceTest {
assertThat(manifest.groups()).extracting(group -> group.destinationWarehouseInternalCode())
.containsExactly("154", "CADF");
assertThat(saved).extracting(LoadingPalletLabelMapper.LabelRow::getPalletCode)
.containsExactly("MSCU6639870-154-001", "MSCU6639870-CADF-001",
"MSCU6639870-CADF-002", "MSCU6639870-CADF-003");
.containsExactly("D0002-154-001", "D0002-CADF-001",
"D0002-CADF-002", "D0002-CADF-003");
}
private LoadingPalletLabelMapper.SourceRow source(long itemId, String destination, int pallets, String fulfillment) {
var row = new LoadingPalletLabelMapper.SourceRow(); row.setPlanId(10L); row.setPlanNo("LP-1");
row.setPlanStatus("LOADING"); row.setContainerNo("MSCU6639870"); row.setWarehouseId(20L);
row.setPlanStatus("LOADING"); row.setContainerNo("MSCU6639870"); row.setCabinetNo("D0002");
row.setWarehouseId(20L);
row.setItemId(itemId); row.setAccountId(100L); row.setInboundId(itemId + 1000);
row.setInboundCode("RK-" + itemId); row.setDestinationWarehouseCode(destination);
row.setDestinationWarehouseId("LAX9".equals(destination) ? 101L : 102L);