diff --git a/deploy/sql/头程数据库统一增量-MySQL8.sql b/deploy/sql/头程数据库统一增量-MySQL8.sql index 302100797..61903f0a2 100644 --- a/deploy/sql/头程数据库统一增量-MySQL8.sql +++ b/deploy/sql/头程数据库统一增量-MySQL8.sql @@ -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), diff --git a/firstleg/AGENTS.md b/firstleg/AGENTS.md index 1365faa92..e555ecbb1 100644 --- a/firstleg/AGENTS.md +++ b/firstleg/AGENTS.md @@ -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` 传递来源托盘。 | 目标 | 入口 | |---|---| diff --git a/firstleg/src/main/java/com/baiyue/firstleg/mapper/LoadingPalletLabelMapper.java b/firstleg/src/main/java/com/baiyue/firstleg/mapper/LoadingPalletLabelMapper.java index df49db14f..6b05deda6 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/mapper/LoadingPalletLabelMapper.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/mapper/LoadingPalletLabelMapper.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; diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/ContainerService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/ContainerService.java index d4406208f..abf45f64a 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/ContainerService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/ContainerService.java @@ -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()); diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/FirstlegDocumentNumberGenerator.java b/firstleg/src/main/java/com/baiyue/firstleg/service/FirstlegDocumentNumberGenerator.java index fdec50227..382e54e53 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/FirstlegDocumentNumberGenerator.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/FirstlegDocumentNumberGenerator.java @@ -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 SUPPORTED_PREFIXES = Set.of("OB", "TA", "CB", "CAB", "LP", "DP"); + private static final Set 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; } } diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPalletLabelService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPalletLabelService.java index 91821161f..84ca04530 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPalletLabelService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPalletLabelService.java @@ -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(); } diff --git a/firstleg/src/main/resources/db/phase3-smart-loading-schema.sql b/firstleg/src/main/resources/db/phase3-smart-loading-schema.sql index bec1abee8..8c289977c 100644 --- a/firstleg/src/main/resources/db/phase3-smart-loading-schema.sql +++ b/firstleg/src/main/resources/db/phase3-smart-loading-schema.sql @@ -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), diff --git a/firstleg/src/main/resources/openapi/firstleg-v1.yaml b/firstleg/src/main/resources/openapi/firstleg-v1.yaml index 3a06bf0dc..0985e25d6 100644 --- a/firstleg/src/main/resources/openapi/firstleg-v1.yaml +++ b/firstleg/src/main/resources/openapi/firstleg-v1.yaml @@ -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 时为空} diff --git a/firstleg/src/main/resources/openapi/loading-lists-v1.yaml b/firstleg/src/main/resources/openapi/loading-lists-v1.yaml index e297c6c29..5756c25c2 100644 --- a/firstleg/src/main/resources/openapi/loading-lists-v1.yaml +++ b/firstleg/src/main/resources/openapi/loading-lists-v1.yaml @@ -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: 总毛重,单位千克} diff --git a/firstleg/src/main/resources/openapi/phase3-container-overview-v1.yaml b/firstleg/src/main/resources/openapi/phase3-container-overview-v1.yaml index 73a9812f9..3b7d0f913 100644 --- a/firstleg/src/main/resources/openapi/phase3-container-overview-v1.yaml +++ b/firstleg/src/main/resources/openapi/phase3-container-overview-v1.yaml @@ -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}}} diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/ContainerServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/ContainerServiceTest.java index 18f035f6b..e241e630f 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/ContainerServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/ContainerServiceTest.java @@ -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"); diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/FirstlegDocumentNumberGeneratorTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/FirstlegDocumentNumberGeneratorTest.java index 911518b9c..783adf9fe 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/FirstlegDocumentNumberGeneratorTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/FirstlegDocumentNumberGeneratorTest.java @@ -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); } } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPalletLabelServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPalletLabelServiceTest.java index 56569c323..892ca2bcf 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPalletLabelServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPalletLabelServiceTest.java @@ -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);