326 lines
18 KiB
Java
326 lines
18 KiB
Java
package com.baiyue.firstleg.service;
|
|
|
|
import com.baiyue.common.core.api.ResultCode;
|
|
import com.baiyue.common.core.exception.BizException;
|
|
import com.baiyue.firstleg.client.PrintPdfClient;
|
|
import com.baiyue.firstleg.dto.InboundCartonLabels;
|
|
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.Pallet;
|
|
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.SkuItem;
|
|
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.mockito.ArgumentCaptor;
|
|
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
|
|
import org.springframework.http.HttpHeaders;
|
|
import org.springframework.http.MediaType;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.test.util.ReflectionTestUtils;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.util.ArrayList;
|
|
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.mock;
|
|
import static org.mockito.Mockito.verify;
|
|
import static org.mockito.Mockito.verifyNoInteractions;
|
|
import static org.mockito.Mockito.when;
|
|
|
|
class InboundCartonLabelPrintServiceTest {
|
|
private final InboundCartonLabelService labels = mock(InboundCartonLabelService.class);
|
|
private final PrintPdfClient print = mock(PrintPdfClient.class);
|
|
private final DropshipPalletizationMapper palletization = mock(DropshipPalletizationMapper.class);
|
|
private final IamCustomerSnapshotService customerSnapshots = mock(IamCustomerSnapshotService.class);
|
|
private InboundCartonLabelPrintService service;
|
|
|
|
@BeforeEach
|
|
void setUp() {
|
|
service = new InboundCartonLabelPrintService(labels, print, new ObjectMapper());
|
|
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
|
|
ReflectionTestUtils.setField(service, "palletizationMapper", palletization);
|
|
ReflectionTestUtils.setField(service, "customerSnapshots", customerSnapshots);
|
|
}
|
|
|
|
@Test
|
|
void dropshipPalletLabelIsAvailableBeforeSeal() {
|
|
Pallet open = dropshipPallet("OPEN");
|
|
stubPalletFacts(90001L);
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.setContentType(MediaType.APPLICATION_PDF);
|
|
when(print.generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.anyString(),
|
|
org.mockito.ArgumentMatchers.eq("request-open"), org.mockito.ArgumentMatchers.any()))
|
|
.thenReturn(new ResponseEntity<>(new byte[] {7, 8, 9}, headers,
|
|
org.springframework.http.HttpStatus.OK));
|
|
|
|
ResponseEntity<byte[]> response = service.generateDropshipPallet(open, "PCHK2608040174", "request-open");
|
|
assertThat(response.getBody()).containsExactly(7, 8, 9);
|
|
|
|
ArgumentCaptor<PrintPdfClient.DropshipPalletA4PdfRequest> request =
|
|
ArgumentCaptor.forClass(PrintPdfClient.DropshipPalletA4PdfRequest.class);
|
|
verify(print).generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.eq("internal-token"),
|
|
org.mockito.ArgumentMatchers.eq("request-open"), request.capture());
|
|
assertThat(request.getValue().templateCode()).isEqualTo("DROPSHIP_PALLET_A4_V2");
|
|
assertThat(request.getValue().pageSpec().widthMm()).isEqualByComparingTo("210");
|
|
assertThat(request.getValue().pageSpec().heightMm()).isEqualByComparingTo("297");
|
|
assertThat(request.getValue().documents()).singleElement().satisfies(document -> {
|
|
assertThat(document.palletNo()).isEqualTo("DP-260911-0001");
|
|
assertThat(document.destinationWarehouse()).isEqualTo("NJDF");
|
|
assertThat(document.inboundRows()).singleElement().satisfies(row -> {
|
|
assertThat(row.inboundCode()).isEqualTo("PCHK2608040174");
|
|
assertThat(row.customerNickname()).isEqualTo("小虫科技");
|
|
assertThat(row.cartonCount()).isEqualTo(1);
|
|
assertThat(row.itemQuantity()).isEqualTo(19);
|
|
});
|
|
assertThat(document.skuRows()).singleElement().satisfies(row -> {
|
|
assertThat(row.skuCode()).isEqualTo("SKU-1");
|
|
assertThat(row.cartonCount()).isEqualTo(1);
|
|
assertThat(row.itemQuantity()).isEqualTo(19);
|
|
assertThat(row.unitsPerCarton()).isEqualTo("19");
|
|
});
|
|
});
|
|
}
|
|
|
|
@Test
|
|
void multiplePreGeneratedPalletsAreReturnedInOnePdf() {
|
|
Pallet first = dropshipPallet("OPEN");
|
|
Pallet second = new Pallet(90002L, "DP-260911-0002", "OPEN", 0, 0, 0,
|
|
BigDecimal.ZERO, BigDecimal.ZERO, 1, List.of(), List.of(), null);
|
|
stubPalletFacts(90001L);
|
|
stubPalletFacts(90002L);
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.setContentType(MediaType.APPLICATION_PDF);
|
|
when(print.generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.anyString(),
|
|
org.mockito.ArgumentMatchers.eq("request-all"), org.mockito.ArgumentMatchers.any()))
|
|
.thenReturn(new ResponseEntity<>(new byte[] {4, 5, 6}, headers,
|
|
org.springframework.http.HttpStatus.OK));
|
|
|
|
ResponseEntity<byte[]> response = service.generateDropshipPallets(
|
|
List.of(first, second), "PCHK2608040174", "request-all");
|
|
|
|
ArgumentCaptor<PrintPdfClient.DropshipPalletA4PdfRequest> request =
|
|
ArgumentCaptor.forClass(PrintPdfClient.DropshipPalletA4PdfRequest.class);
|
|
verify(print).generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.eq("internal-token"),
|
|
org.mockito.ArgumentMatchers.eq("request-all"), request.capture());
|
|
assertThat(request.getValue().documents()).extracting("palletNo")
|
|
.containsExactly("DP-260911-0001", "DP-260911-0002");
|
|
assertThat(request.getValue().downloadFileName()).contains("2托");
|
|
assertThat(response.getBody()).containsExactly(4, 5, 6);
|
|
}
|
|
|
|
@Test
|
|
void splitsLargeDropshipPalletDetailsAcrossPrintableA4Pages() {
|
|
Pallet pallet = dropshipPallet("OPEN");
|
|
stubPalletFacts(90001L);
|
|
List<DropshipPalletizationMapper.PrintSkuRow> skuRows = new ArrayList<>();
|
|
for (int index = 1; index <= 35; index++) {
|
|
DropshipPalletizationMapper.PrintSkuRow row = new DropshipPalletizationMapper.PrintSkuRow();
|
|
row.setSkuCode("SKU-" + index);
|
|
row.setCartonCount(1);
|
|
row.setItemQuantity(index);
|
|
row.setMinUnitsPerCarton(index);
|
|
row.setMaxUnitsPerCarton(index);
|
|
skuRows.add(row);
|
|
}
|
|
when(palletization.printSkuRows(90001L)).thenReturn(skuRows);
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.setContentType(MediaType.APPLICATION_PDF);
|
|
when(print.generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.anyString(),
|
|
org.mockito.ArgumentMatchers.eq("request-large"), org.mockito.ArgumentMatchers.any()))
|
|
.thenReturn(new ResponseEntity<>(new byte[] {1}, headers,
|
|
org.springframework.http.HttpStatus.OK));
|
|
|
|
service.generateDropshipPallet(pallet, "PCHK2608040174", "request-large");
|
|
|
|
ArgumentCaptor<PrintPdfClient.DropshipPalletA4PdfRequest> request =
|
|
ArgumentCaptor.forClass(PrintPdfClient.DropshipPalletA4PdfRequest.class);
|
|
verify(print).generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.eq("internal-token"),
|
|
org.mockito.ArgumentMatchers.eq("request-large"), request.capture());
|
|
assertThat(request.getValue().documents()).hasSize(2).allSatisfy(document -> {
|
|
assertThat(document.palletNo()).isEqualTo("DP-260911-0001");
|
|
assertThat(document.inboundRows()).isNotEmpty().hasSizeLessThanOrEqualTo(10);
|
|
assertThat(document.skuRows()).hasSizeLessThanOrEqualTo(34);
|
|
});
|
|
assertThat(request.getValue().documents().get(0).skuRows()).hasSize(34);
|
|
assertThat(request.getValue().documents().get(1).skuRows()).hasSize(1);
|
|
}
|
|
|
|
@Test
|
|
void printsPalletDocumentWhenCustomerNicknameIsMissing() {
|
|
Pallet pallet = dropshipPallet("OPEN");
|
|
stubPalletFacts(90001L);
|
|
when(customerSnapshots.customerNicknameForPrint(10001L)).thenReturn("—");
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.setContentType(MediaType.APPLICATION_PDF);
|
|
when(print.generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.anyString(),
|
|
org.mockito.ArgumentMatchers.eq("request-missing-nickname"), org.mockito.ArgumentMatchers.any()))
|
|
.thenReturn(new ResponseEntity<>(new byte[] {1}, headers, org.springframework.http.HttpStatus.OK));
|
|
|
|
service.generateDropshipPallet(pallet, "PCHK2608040174", "request-missing-nickname");
|
|
|
|
ArgumentCaptor<PrintPdfClient.DropshipPalletA4PdfRequest> request =
|
|
ArgumentCaptor.forClass(PrintPdfClient.DropshipPalletA4PdfRequest.class);
|
|
verify(print).generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.anyString(),
|
|
org.mockito.ArgumentMatchers.eq("request-missing-nickname"), request.capture());
|
|
assertThat(request.getValue().documents().get(0).inboundRows().get(0).customerNickname()).isEqualTo("—");
|
|
}
|
|
|
|
@Test
|
|
void forwardsStableLabelsWithoutSkuAndOnlySafePdfHeaders() throws Exception {
|
|
when(labels.list(10001L, 70001L, List.of(1))).thenReturn(batch());
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.setContentType(MediaType.APPLICATION_PDF);
|
|
headers.setContentDispositionFormData("attachment", "labels.pdf");
|
|
headers.set("X-Print-Page-Count", "1");
|
|
headers.set("X-Internal-Service-Token", "must-not-leak");
|
|
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
|
|
org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any()))
|
|
.thenReturn(new ResponseEntity<>(new byte[] {1, 2, 3}, headers,
|
|
org.springframework.http.HttpStatus.OK));
|
|
|
|
ResponseEntity<byte[]> response = service.generate(10001L, 70001L, List.of(1), "request-1");
|
|
|
|
ArgumentCaptor<PrintPdfClient.InboundMarkPdfRequest> request =
|
|
ArgumentCaptor.forClass(PrintPdfClient.InboundMarkPdfRequest.class);
|
|
verify(print).generateInboundMarks(org.mockito.ArgumentMatchers.eq("internal-token"),
|
|
org.mockito.ArgumentMatchers.eq("request-1"), request.capture());
|
|
assertThat(request.getValue().downloadFileName()).isEqualTo("PCHK2608040174-19件-箱唛.pdf");
|
|
assertThat(request.getValue().labels()).singleElement().satisfies(label -> {
|
|
assertThat(label.qrContent()).isEqualTo("PCHK2608040174#19-1");
|
|
assertThat(label.cartonText()).isEqualTo("CTNS:19-1");
|
|
});
|
|
assertThat(batch().labels().get(0).skuCode()).isEqualTo("SKU-BOX-ONLY-IN-BUSINESS-DATA");
|
|
assertThat(new ObjectMapper().writeValueAsString(request.getValue()))
|
|
.doesNotContain("skuCode", "SKU-BOX-ONLY-IN-BUSINESS-DATA");
|
|
assertThat(response.getBody()).containsExactly(1, 2, 3);
|
|
assertThat(response.getHeaders().getFirst("X-Print-Page-Count")).isEqualTo("1");
|
|
assertThat(response.getHeaders()).doesNotContainKey("X-Internal-Service-Token");
|
|
}
|
|
|
|
@Test
|
|
void convertsPrintJsonBusinessFailureInsteadOfReturningItAsPdf() throws Exception {
|
|
when(labels.list(10001L, 70001L, null)).thenReturn(batch());
|
|
byte[] error = "{\"code\":\"A0422\",\"message\":\"标签字段超出模板限制\","
|
|
.concat("\"data\":null,\"requestId\":\"req-2\",\"traceId\":\"trace-2\"}")
|
|
.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
|
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
|
|
org.mockito.ArgumentMatchers.isNull(), org.mockito.ArgumentMatchers.any()))
|
|
.thenReturn(ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(error));
|
|
|
|
assertThatThrownBy(() -> service.generate(10001L, 70001L, null, null))
|
|
.isInstanceOf(BizException.class)
|
|
.hasMessageContaining("标签字段超出模板限制")
|
|
.extracting(value -> ((BizException) value).getResultCode())
|
|
.isEqualTo(ResultCode.VALIDATION_FAILED);
|
|
}
|
|
|
|
@Test
|
|
void convertsCircuitBreakerFailureToServiceUnavailable() {
|
|
when(labels.list(10001L, 70001L, null)).thenReturn(batch());
|
|
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
|
|
org.mockito.ArgumentMatchers.isNull(), org.mockito.ArgumentMatchers.any()))
|
|
.thenThrow(new NoFallbackAvailableException("print unavailable",
|
|
new IllegalStateException("down")));
|
|
|
|
assertThatThrownBy(() -> service.generate(10001L, 70001L, null, null))
|
|
.isInstanceOf(BizException.class)
|
|
.hasMessage("打印服务暂不可用,请稍后重试")
|
|
.extracting(value -> ((BizException) value).getResultCode())
|
|
.isEqualTo(ResultCode.SERVICE_UNAVAILABLE);
|
|
}
|
|
|
|
@Test
|
|
void batchCombinesSuccessfulOrdersAndReportsIsolatedFailures() {
|
|
when(labels.requireInboundIdByOrder(10001L, 60001L)).thenReturn(70001L);
|
|
when(labels.requireInboundIdByOrder(10001L, 60002L))
|
|
.thenThrow(new BizException(ResultCode.NOT_FOUND, "missing"));
|
|
when(labels.list(10001L, 70001L, null)).thenReturn(batch());
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.setContentType(MediaType.APPLICATION_PDF);
|
|
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
|
|
org.mockito.ArgumentMatchers.eq("request-batch"), org.mockito.ArgumentMatchers.any()))
|
|
.thenReturn(new ResponseEntity<>(new byte[] {1, 2, 3}, headers,
|
|
org.springframework.http.HttpStatus.OK));
|
|
|
|
ResponseEntity<byte[]> response = service.generateByOrders(
|
|
10001L, List.of(60001L, 60002L), "request-batch");
|
|
|
|
assertThat(response.getHeaders().getFirst("X-Carton-Label-Order-Count")).isEqualTo("2");
|
|
assertThat(response.getHeaders().getFirst("X-Carton-Label-Failure-Count")).isEqualTo("1");
|
|
assertThat(response.getHeaders().getFirst("X-Carton-Label-Failures")).contains("60002:A0404");
|
|
verify(print).generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
|
|
org.mockito.ArgumentMatchers.eq("request-batch"), org.mockito.ArgumentMatchers.any());
|
|
}
|
|
|
|
@Test
|
|
void platformBatchCombinesLabelsAcrossCustomers() {
|
|
when(labels.list(10001L, 70001L, null)).thenReturn(batch());
|
|
when(labels.list(10002L, 70002L, null)).thenReturn(batch());
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.setContentType(MediaType.APPLICATION_PDF);
|
|
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
|
|
org.mockito.ArgumentMatchers.eq("platform-batch"), org.mockito.ArgumentMatchers.any()))
|
|
.thenReturn(new ResponseEntity<>(new byte[] {1, 2, 3}, headers,
|
|
org.springframework.http.HttpStatus.OK));
|
|
|
|
ResponseEntity<byte[]> response = service.generateForPlatformInbounds(List.of(
|
|
new InboundCartonLabels.PlatformBatchItem(10001L, 70001L),
|
|
new InboundCartonLabels.PlatformBatchItem(10002L, 70002L)), "platform-batch");
|
|
|
|
assertThat(response.getHeaders().getFirst("X-Carton-Label-Order-Count")).isEqualTo("2");
|
|
assertThat(response.getBody()).containsExactly(1, 2, 3);
|
|
}
|
|
|
|
@Test
|
|
void platformBatchFailsWhenAnySelectedInboundCannotGenerateLabels() {
|
|
when(labels.list(10001L, 70001L, null)).thenReturn(batch());
|
|
when(labels.list(10002L, 70002L, null))
|
|
.thenThrow(new BizException(ResultCode.NOT_FOUND, "箱唛入库单不存在"));
|
|
|
|
assertThatThrownBy(() -> service.generateForPlatformInbounds(List.of(
|
|
new InboundCartonLabels.PlatformBatchItem(10001L, 70001L),
|
|
new InboundCartonLabels.PlatformBatchItem(10002L, 70002L)), "platform-batch"))
|
|
.isInstanceOf(BizException.class)
|
|
.hasMessage("箱唛入库单不存在");
|
|
verifyNoInteractions(print);
|
|
}
|
|
|
|
private InboundCartonLabels.Batch batch() {
|
|
InboundCartonLabels.Label label = new InboundCartonLabels.Label(
|
|
1L, 2L, 1, "音箱", 19, "PCHK2608040174#19-1", 19, 1,
|
|
"50x40x30cm", new BigDecimal("50"), new BigDecimal("40"), new BigDecimal("30"),
|
|
"SKU-BOX-ONLY-IN-BUSINESS-DATA",
|
|
"PCHK2608040174", "MCC1", "CTNS:19-1", "洛杉矶普船海卡");
|
|
return new InboundCartonLabels.Batch(70001L, 80001L, "PCHK2608040174", 19, List.of(label));
|
|
}
|
|
|
|
private Pallet dropshipPallet(String status) {
|
|
SkuItem item = new SkuItem(70001L, "PCHK2608040174", 2L,
|
|
"SKU-1", "音箱", 1, 19, List.of("PCHK2608040174#19-1"));
|
|
return new Pallet(90001L, "DP-260911-0001", status, 1, 1, 0,
|
|
BigDecimal.ONE, BigDecimal.ONE, 1, List.of(item), List.of(), null);
|
|
}
|
|
|
|
private void stubPalletFacts(long palletId) {
|
|
DropshipPalletizationMapper.PrintInboundRow inbound =
|
|
new DropshipPalletizationMapper.PrintInboundRow();
|
|
inbound.setTenantId(10001L);
|
|
inbound.setInboundId(70001L);
|
|
inbound.setInboundCode("PCHK2608040174");
|
|
inbound.setDestinationWarehouse("NJDF");
|
|
inbound.setCartonCount(palletId == 90001L ? 1 : 0);
|
|
inbound.setItemQuantity(palletId == 90001L ? 19 : 0);
|
|
when(palletization.printInboundRows(palletId)).thenReturn(List.of(inbound));
|
|
DropshipPalletizationMapper.PrintSkuRow sku = new DropshipPalletizationMapper.PrintSkuRow();
|
|
sku.setSkuCode("SKU-1");
|
|
sku.setCartonCount(1);
|
|
sku.setItemQuantity(19);
|
|
sku.setMinUnitsPerCarton(19);
|
|
sku.setMaxUnitsPerCarton(19);
|
|
when(palletization.printSkuRows(palletId))
|
|
.thenReturn(palletId == 90001L ? List.of(sku) : List.of());
|
|
when(customerSnapshots.customerNicknameForPrint(10001L)).thenReturn("小虫科技");
|
|
}
|
|
}
|