fix: keep pallet labels on A4 templates

This commit is contained in:
2026-09-20 14:05:00 +08:00
parent ee97565f67
commit 493d62e04c
15 changed files with 269 additions and 27 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
| 托盘解析与业务绑定 | `controller/InternalWarehousePalletController.java`、`dto/WarehousePalletResolveResponse.java`、`entity/WarehousePalletBindingEntity.java` |
| 柜级入库计划批量托盘 | `controller/InternalWarehousePalletBatchController.java`、`service/WarehousePalletBatchService.java`、`warehouse-pallet-batch-internal-v1.yaml`;以稳定批次键幂等执行 `ensure-reserved → confirm`,权威托盘码已存在且资源可预留时必须复用,未提供或不存在时才创建;Lastleg 本地计划事务失败时仅补偿仍为 `RESERVED` 的未确认资源,已 `CONFIRMED` 批次不得回退,同柜重放不得产生第二批资源 |
| 仓储资源服务 | `service/WarehouseResourceService.java`、`WarehouseResourceLockService.java` |
| 单张托盘标签 PDF | `controller/PlatformWarehouseResourceController.java` 的 `/pallets/{id}/label`、`service/PalletLabelPrintService.java`、`client/PrintPdfClient.java`;契约 `src/main/resources/openapi/pallet-label-v1.yaml`。有 FBA 仓码的托盘与国内统一调用 Print `/internal/print/v1/pdfs/fba-pallet-labels`、`FBA_PALLET_A4_LANDSCAPE_V1`:297×210 mm,顶部 SCK8 等仓码,完整托盘号作为 Code 128 与底部文字,141 等内部编号原样保留;其他托盘使用 `INBOUND_MARK_V1`。模板只在 Print 维护;沿用托盘列表权限与海外 Gateway 的 `warehouse-admin/**` 路由。`baiyue.clients.print.url` 对应 `PRINT_SERVICE_URL`,海外 Compose 必须与 BOL 一样显式注入国内 Print 地址,不能依赖海外 Nacos 发现国内服务;Feign 及熔断包装异常统一返回 `S0503` |
| 单张托盘标签 PDF | `controller/PlatformWarehouseResourceController.java` 的 `/pallets/{id}/label`、`service/PalletLabelPrintService.java`、`client/PrintPdfClient.java`;契约 `src/main/resources/openapi/pallet-label-v1.yaml`。有 FBA 仓码的托盘与国内统一调用 Print `/internal/print/v1/pdfs/fba-pallet-labels`、`FBA_PALLET_A4_LANDSCAPE_V1`;其他托盘调用 `/internal/print/v1/pdfs/pallet-a4-labels`、`PALLET_A4_LANDSCAPE_V1`。两者均为 297×210 mm A4 横向,托盘完整编码作为 Code 128 与底部文字,托盘标不得回退到 100×100 mm 箱唛模板。模板只在 Print 维护;沿用托盘列表权限与海外 Gateway 的 `warehouse-admin/**` 路由。`baiyue.clients.print.url` 对应 `PRINT_SERVICE_URL`,海外 Compose 必须与 BOL 一样显式注入国内 Print 地址,不能依赖海外 Nacos 发现国内服务;Feign 及熔断包装异常统一返回 `S0503` |
| 内部服务令牌 | `security/InternalServiceTokenGuard.java` |
| 数据库基线与中文注释契约 | `src/main/resources/db/warehouse.sql`、`src/test/java/com/baiyue/warehouse/schema/SchemaChineseCommentContractTest.java` |
| 测试 | `src/test/java/com/baiyue/warehouse/` |
@@ -21,13 +21,14 @@ public interface PrintPdfClient {
record FbaPalletRequest(String templateCode, PageSpec pageSpec, String downloadFileName, List<FbaPalletLabel> labels) {}
record FbaPalletLabel(String fbaWarehouseCode, String palletCode) {}
@PostMapping(value = "/inbound-marks", consumes = "application/json")
ResponseEntity<byte[]> generateInboundMarks(
@PostMapping(value = "/pallet-a4-labels", consumes = "application/json")
ResponseEntity<byte[]> generatePalletA4Labels(
@RequestHeader("X-Internal-Service-Token") String token,
@RequestHeader(value = "X-Request-Id", required = false) String requestId,
@RequestBody Request request);
@RequestBody PalletA4Request request);
record PalletA4Request(String templateCode, PageSpec pageSpec, String downloadFileName, List<PalletA4Label> labels) {}
record PalletA4Label(String heading, String reference, String palletCode) {}
record Request(String templateCode, PageSpec pageSpec, String downloadFileName, List<Label> labels) {}
record PageSpec(BigDecimal widthMm, BigDecimal heightMm) {}
record Label(String topCode, String warehouseText, String cartonText, String routeText, String qrContent) {}
}
@@ -39,7 +39,7 @@ public class PalletLabelPrintService {
String code = pallet.getPalletCode();
String fbaCode = pallet.getFbaWarehouseCode();
boolean fba = fbaCode != null && !fbaCode.isBlank();
int maxCodeLength = fba ? 80 : 34;
int maxCodeLength = 80;
// Preserve the authoritative pallet identity on both the barcode and visible text.
if (code == null || !code.matches("[\\x20-\\x7E]{1," + maxCodeLength + "}") || code.isBlank()
|| !code.equals(code.strip())) {
@@ -47,17 +47,14 @@ public class PalletLabelPrintService {
}
var warehouse = warehouses.required(warehouseId);
String fileName = code.replaceAll("[\\\\/:*?\"<>|]", "-") + "-托盘码.pdf";
var label = new PrintPdfClient.Label(code,
fbaCode == null || fbaCode.isBlank() ? "目的仓待定" : fbaCode,
reference, warehouse.warehouseName(), code);
var request = new PrintPdfClient.Request("INBOUND_MARK_V1",
new PrintPdfClient.PageSpec(new BigDecimal("100"), new BigDecimal("100")), fileName, List.of(label));
try {
var response = fba
? print.generateFbaPalletLabels(internalToken, requestId, new PrintPdfClient.FbaPalletRequest(
"FBA_PALLET_A4_LANDSCAPE_V1", new PrintPdfClient.PageSpec(new BigDecimal("297"), new BigDecimal("210")),
fileName, List.of(new PrintPdfClient.FbaPalletLabel(fbaCode, code))))
: print.generateInboundMarks(internalToken, requestId, request);
: print.generatePalletA4Labels(internalToken, requestId, new PrintPdfClient.PalletA4Request(
"PALLET_A4_LANDSCAPE_V1", new PrintPdfClient.PageSpec(new BigDecimal("297"), new BigDecimal("210")),
fileName, List.of(new PrintPdfClient.PalletA4Label(warehouse.warehouseName(), reference, code))));
byte[] body = response == null ? null : response.getBody();
MediaType type = response == null ? null : response.getHeaders().getContentType();
if (response == null || !response.getStatusCode().is2xxSuccessful() || body == null || body.length == 0
@@ -7,7 +7,7 @@ paths:
get:
operationId: printOverseasPalletLabel
summary: 下载指定托盘的一页标签 PDF
description: 平台公共资源,要求 platform:overseas-warehouse:pallet:list。校验仓库和托盘归属后,有 FBA 仓码的托盘统一调用 Print FBA_PALLET_A4_LANDSCAPE_V1(297×210mm),顶部显示 SCK8 等 FBA 仓码,托盘完整编码同时作为 Code 128 内容和底部文字;内部编号如 141 原样保留。FBA 编码限 1–80 位可打印 ASCII,禁止首尾空格。不创建或修改托盘。其他托盘继续使用其现有 INBOUND_MARK_V1(100×100mm,编码限 1–34 位)。
description: 平台公共资源,要求 platform:overseas-warehouse:pallet:list。校验仓库和托盘归属后,有 FBA 仓码的托盘统一调用 Print FBA_PALLET_A4_LANDSCAPE_V1(297×210mm),顶部显示 SCK8 等 FBA 仓码;其他托盘调用 PALLET_A4_LANDSCAPE_V1(297×210mm),展示仓库名称和业务参考。托盘完整编码同时作为 Code 128 内容和底部文字,限 1–80 位可打印 ASCII 且禁止首尾空格。不创建或修改托盘,不复用 100×100mm 箱唛模板。
security:
- bearerAuth: []
parameters:
@@ -56,7 +56,6 @@ class PalletLabelPrintServiceTest {
assertThat(response.getBody()).isEqualTo(pdf);
verify(resources).pallet(0L, 200L, 9L);
verifyNoMoreInteractions(resources);
verify(print, never()).generateInboundMarks(any(), any(), any());
}
@Test
@@ -64,18 +63,20 @@ class PalletLabelPrintServiceTest {
pallet.setPalletCode("ABCD1234567-ABE3-003");
pallet.setFbaWarehouseCode(null);
byte[] pdf = "%PDF-transfer-label".getBytes(java.nio.charset.StandardCharsets.US_ASCII);
when(print.generateInboundMarks(anyString(), anyString(), any()))
when(print.generatePalletA4Labels(anyString(), anyString(), any()))
.thenReturn(ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).body(pdf));
service.printTransfer(200L, 9L, "WTR-20260916-1", "request-transfer");
var request = ArgumentCaptor.forClass(PrintPdfClient.Request.class);
verify(print).generateInboundMarks(eq("test-token"), eq("request-transfer"), request.capture());
var request = ArgumentCaptor.forClass(PrintPdfClient.PalletA4Request.class);
verify(print).generatePalletA4Labels(eq("test-token"), eq("request-transfer"), request.capture());
assertThat(request.getValue().templateCode()).isEqualTo("PALLET_A4_LANDSCAPE_V1");
assertThat(request.getValue().pageSpec().widthMm()).isEqualByComparingTo("297");
assertThat(request.getValue().pageSpec().heightMm()).isEqualByComparingTo("210");
assertThat(request.getValue().labels()).singleElement().satisfies(label -> {
assertThat(label.topCode()).isEqualTo("ABCD1234567-ABE3-003");
assertThat(label.cartonText()).isEqualTo("转运单 WTR-20260916-1");
assertThat(label.warehouseText()).isEqualTo("目的仓待定");
assertThat(label.routeText()).isEqualTo("洛杉矶仓");
assertThat(label.heading()).isEqualTo("洛杉矶仓");
assertThat(label.reference()).isEqualTo("转运单 WTR-20260916-1");
assertThat(label.palletCode()).isEqualTo("ABCD1234567-ABE3-003");
});
}
@@ -120,12 +121,12 @@ class PalletLabelPrintServiceTest {
@Test
void keepsNonFbaLabelsOnTheirOwnTemplate() {
pallet.setFbaWarehouseCode(null);
when(print.generateInboundMarks(anyString(), anyString(), any()))
when(print.generatePalletA4Labels(anyString(), anyString(), any()))
.thenReturn(ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).body("%PDF-other".getBytes()));
service.print(200L, 9L, "request-1");
var captor = ArgumentCaptor.forClass(PrintPdfClient.Request.class);
verify(print).generateInboundMarks(anyString(), anyString(), captor.capture());
assertThat(captor.getValue().templateCode()).isEqualTo("INBOUND_MARK_V1");
var captor = ArgumentCaptor.forClass(PrintPdfClient.PalletA4Request.class);
verify(print).generatePalletA4Labels(anyString(), anyString(), captor.capture());
assertThat(captor.getValue().templateCode()).isEqualTo("PALLET_A4_LANDSCAPE_V1");
verify(print, never()).generateFbaPalletLabels(any(), any(), any());
}
}