优化
This commit is contained in:
+3
@@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -19,6 +20,8 @@ class InboundOrderControllerContractTest {
|
||||
AuthenticatedUser.class, Long.class, InboundOrderQuery.class);
|
||||
assertAuthority("detail", "hasAuthority('platform:firstleg:inbound:view')",
|
||||
AuthenticatedUser.class, long.class, long.class);
|
||||
assertAuthority("printCartonLabels", "hasAuthority('platform:firstleg:inbound:view')",
|
||||
AuthenticatedUser.class, long.class, long.class, List.class, String.class);
|
||||
|
||||
Method page = PlatformInboundOrderController.class.getDeclaredMethod(
|
||||
"page", AuthenticatedUser.class, Long.class, InboundOrderQuery.class);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("价格版本渠道 Mapper 契约")
|
||||
class PriceBookChannelMapperTest {
|
||||
@Test
|
||||
@DisplayName("重新生成版本快照时使用物理删除避免唯一键冲突")
|
||||
void shouldPhysicallyDeleteVersionChannels() throws Exception {
|
||||
Method method = PriceBookChannelMapper.class
|
||||
.getMethod("physicallyDeleteByPriceBookVersionId", long.class);
|
||||
|
||||
assertThat(method.getAnnotation(Delete.class).value())
|
||||
.containsExactly("DELETE FROM fl_price_book_channel WHERE price_book_version_id = #{versionId}");
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,10 @@ class PriceBookSqlContractTest {
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
String migration = new ClassPathResource("db/phase2-fba-pricing-migration.sql")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
String channelTable = schema.substring(
|
||||
schema.indexOf("CREATE TABLE IF NOT EXISTS fl_price_book_channel"),
|
||||
schema.indexOf("ENGINE=InnoDB COMMENT='价格版本渠道及船期快照'")
|
||||
);
|
||||
assertThat(schema)
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_price_channel_catalog")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_price_origin_alias")
|
||||
@@ -32,6 +36,11 @@ class PriceBookSqlContractTest {
|
||||
.contains("destination_name VARCHAR(128)")
|
||||
.contains("parse_issues_json JSON NULL")
|
||||
.contains("DEFAULT 'PENDING_CONFIRMATION'");
|
||||
assertThat(channelTable)
|
||||
.contains("created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID'")
|
||||
.contains("updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID'")
|
||||
.contains("deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记'")
|
||||
.contains("version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号'");
|
||||
assertThat(migration)
|
||||
.doesNotContain("fl_price_book_version")
|
||||
.doesNotContain("'pricing_tax_mode'")
|
||||
|
||||
+2
-1
@@ -50,6 +50,7 @@ class CartonMeasurementServiceTest {
|
||||
when(resolver.resolveByBoxCode(10001L, "RK-001#5-1"))
|
||||
.thenReturn(new CartonLabelBindingResolver.Binding(70001L, 80001L, "RK-001", 1, 5, "50x40x30cm"));
|
||||
when(repository.findInbound(10001L, 70001L)).thenReturn(inbound("WAIT_MEASURE"));
|
||||
when(repository.lockInbound(10001L, 70001L)).thenReturn(inbound("WAIT_MEASURE"));
|
||||
when(images.upload(any(), any(Long.TYPE), any(Long.TYPE), any(Long.TYPE), any()))
|
||||
.thenReturn(ApiResponse.success(new MeasurementImageClient.UploadResult(
|
||||
99001L, "measure.jpg", "image/jpeg", 10L, "NOT_REQUIRED", null, null, null, null)));
|
||||
@@ -130,7 +131,7 @@ class CartonMeasurementServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmedPackageCountCanBeCorrectedAfterSubmissionAndSyncsInboundSnapshot() {
|
||||
void confirmedPackageCountCanBeCorrectedAfterSubmissionWithoutChangingOriginalPackageCount() {
|
||||
InboundOrderEntity inbound = inbound("MEASURE_PENDING");
|
||||
InboundMeasurementEntity measurement = measurement(30001L, "SUBMITTED", 2);
|
||||
measurement.setTotalPackageCount(1);
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
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.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
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.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.when;
|
||||
|
||||
class InboundCartonLabelPrintServiceTest {
|
||||
private final InboundCartonLabelService labels = mock(InboundCartonLabelService.class);
|
||||
private final PrintPdfClient print = mock(PrintPdfClient.class);
|
||||
private InboundCartonLabelPrintService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new InboundCartonLabelPrintService(labels, print, new ObjectMapper());
|
||||
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
|
||||
}
|
||||
|
||||
@Test
|
||||
void forwardsStableLabelsAndOnlySafePdfHeaders() {
|
||||
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(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);
|
||||
}
|
||||
|
||||
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"),
|
||||
"PCHK2608040174", "MCC1", "CTNS:19-1", "洛杉矶普船海卡");
|
||||
return new InboundCartonLabels.Batch(70001L, 80001L, "PCHK2608040174", 19, List.of(label));
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.baiyue.firstleg.config.InboundProperties;
|
||||
import com.baiyue.firstleg.dto.ExceptionReleaseRequest;
|
||||
import com.baiyue.firstleg.dto.MeasurementReviewRequest;
|
||||
import com.baiyue.firstleg.entity.InboundMeasurementEntity;
|
||||
import com.baiyue.firstleg.entity.InboundMeasurementItemEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.entity.InboundStatusHistoryEntity;
|
||||
import com.baiyue.firstleg.repository.InboundWorkflowRepository;
|
||||
@@ -79,6 +80,23 @@ class MeasurementReviewServiceTest {
|
||||
assertThat(history.getValue().getToStatus()).isEqualTo("WAIT_SPLIT");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("确认箱数合计不等于订单总箱数时不能审核通过")
|
||||
void shouldRejectApprovalWhenConfirmedPackageCountDoesNotMatchOrder() {
|
||||
Fixture fixture = fixture(false);
|
||||
InboundMeasurementItemEntity item = new InboundMeasurementItemEntity();
|
||||
item.setPackageCount(4);
|
||||
when(fixture.repository.listMeasurementItems(10001L, 20L)).thenReturn(List.of(item));
|
||||
|
||||
assertThatThrownBy(() -> fixture.service.approve(10001L, 9L, "审核员", 10L, "approve-mismatch",
|
||||
new MeasurementReviewRequest(20L, 3, null)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.extracting(error -> ((BizException) error).getResultCode())
|
||||
.isEqualTo(ResultCode.VALIDATION_FAILED);
|
||||
|
||||
verify(fixture.repository, never()).transitionReview(anyLong(), anyLong(), anyLong(), anyInt(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("驳回原因必填且状态推进为测量驳回")
|
||||
void shouldRequireRejectReason() {
|
||||
@@ -175,10 +193,15 @@ class MeasurementReviewServiceTest {
|
||||
InboundOrderEntity order = new InboundOrderEntity();
|
||||
order.setProcessStatus("MEASURE_PENDING");
|
||||
order.setMeasureReviewStatus("PENDING");
|
||||
order.setTotalPackageCount(5);
|
||||
InboundMeasurementEntity measurement = new InboundMeasurementEntity();
|
||||
measurement.setId(20L);
|
||||
measurement.setStatus("SUBMITTED");
|
||||
InboundMeasurementItemEntity item = new InboundMeasurementItemEntity();
|
||||
item.setPackageCount(5);
|
||||
when(repository.findOwned(10001L, 10L)).thenReturn(order);
|
||||
when(repository.findMeasurement(10001L, 10L, 20L)).thenReturn(measurement);
|
||||
when(repository.listMeasurementItems(10001L, 20L)).thenReturn(List.of(item));
|
||||
return new Fixture(service, repository, idempotency, order, measurement);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user