优化
This commit is contained in:
@@ -30,6 +30,9 @@ public final class InboundMeasurementResponses {
|
||||
Long measurementId,
|
||||
String sourceType,
|
||||
String measurementStatus,
|
||||
@Schema(description = "入库主单乐观锁版本,用于测量审核、驳回和异常放行", example = "3")
|
||||
Integer inboundVersion,
|
||||
@Schema(description = "测量批次乐观锁版本,用于测量草稿和明细维护", example = "4")
|
||||
Integer version,
|
||||
String originalDocumentNo,
|
||||
BigDecimal originalGrossWeightKg,
|
||||
|
||||
+4
-4
@@ -17,13 +17,13 @@ public class InternalFinanceCostSnapshotController {
|
||||
@Value("${baiyue.security.internal-service-token}") private String expectedToken;
|
||||
public InternalFinanceCostSnapshotController(FinanceCostSnapshotService service) { this.service = service; }
|
||||
|
||||
@GetMapping("/customers/{customerId}/containers/{containerId}")
|
||||
public ApiResponse<FinanceCostSnapshotService.ContainerSnapshot> container(
|
||||
@GetMapping("/customers/{customerId}/loading-plans/{loadingPlanId}")
|
||||
public ApiResponse<FinanceCostSnapshotService.ContainerSnapshot> loadingPlan(
|
||||
@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
|
||||
@PathVariable long customerId, @PathVariable long containerId) {
|
||||
@PathVariable long customerId, @PathVariable long loadingPlanId) {
|
||||
if (token == null || !MessageDigest.isEqual(token.getBytes(StandardCharsets.UTF_8), expectedToken.getBytes(StandardCharsets.UTF_8))) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "内部服务令牌无效");
|
||||
}
|
||||
return ApiResponse.success(service.container(customerId, containerId));
|
||||
return ApiResponse.success(service.loadingPlan(customerId, loadingPlanId));
|
||||
}
|
||||
}
|
||||
|
||||
+8
-9
@@ -13,27 +13,26 @@ public class FinanceCostSnapshotService {
|
||||
private final SmartLoadingRepository repository;
|
||||
public FinanceCostSnapshotService(SmartLoadingRepository repository) { this.repository = repository; }
|
||||
|
||||
public ContainerSnapshot container(long customerId, long loadingPlanId) {
|
||||
public ContainerSnapshot loadingPlan(long customerId, long loadingPlanId) {
|
||||
if (customerId <= 0 || loadingPlanId <= 0) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "柜次不存在");
|
||||
}
|
||||
// Older Finance deployments called this endpoint with the physical
|
||||
// container booking ID. Resolve both identities during rollout while
|
||||
// preserving the caller's ID in the snapshot contract.
|
||||
var plan = repository.plan(loadingPlanId);
|
||||
if (plan == null) plan = repository.planByBooking(loadingPlanId);
|
||||
if (plan == null) throw new BizException(ResultCode.NOT_FOUND, "柜次不存在");
|
||||
long effectivePlanId = plan.getId() == null || plan.getId() <= 0 ? loadingPlanId : plan.getId();
|
||||
List<OrderVolume> orders = repository.items(effectivePlanId).stream()
|
||||
if (plan.getContainerBookingId() == null || plan.getContainerBookingId() <= 0) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "装柜计划未关联柜次");
|
||||
}
|
||||
List<OrderVolume> orders = repository.items(loadingPlanId).stream()
|
||||
.filter(item -> item.getAccountId() != null && item.getAccountId() == customerId)
|
||||
.filter(item -> item.getOrderId() != null && item.getOrderId() > 0)
|
||||
.filter(item -> item.getVolumeCbm() != null && item.getVolumeCbm().signum() > 0)
|
||||
.map(item -> new OrderVolume(item.getOrderId(), item.getInboundId(), item.getVolumeCbm()))
|
||||
.toList();
|
||||
if (orders.isEmpty()) throw new BizException(ResultCode.BAD_REQUEST, "柜次没有该客户的有效订单体积");
|
||||
return new ContainerSnapshot(loadingPlanId, customerId, orders);
|
||||
return new ContainerSnapshot(loadingPlanId, plan.getContainerBookingId(), customerId, orders);
|
||||
}
|
||||
|
||||
public record ContainerSnapshot(long containerId, long customerId, List<OrderVolume> orders) {}
|
||||
public record ContainerSnapshot(long loadingPlanId, long containerBookingId, long customerId,
|
||||
List<OrderVolume> orders) {}
|
||||
public record OrderVolume(long orderId, Long inboundId, BigDecimal volumeCbm) {}
|
||||
}
|
||||
|
||||
@@ -347,6 +347,7 @@ public class InboundMeasurementService {
|
||||
review == null ? null : review.getReviewerName(), measurement == null ? null : measurement.getId(),
|
||||
measurement == null ? null : measurement.getSourceType(),
|
||||
measurement == null ? null : measurement.getStatus(),
|
||||
inbound.getVersion(),
|
||||
measurement == null ? null : measurement.getVersion(),
|
||||
measurement == null ? null : measurement.getOriginalDocumentNo(),
|
||||
measurement == null ? null : measurement.getOriginalGrossWeightKg(),
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.baiyue.firstleg.loading.controller;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.loading.service.FinanceCostSnapshotService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
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;
|
||||
|
||||
class InternalFinanceCostSnapshotControllerTest {
|
||||
private final FinanceCostSnapshotService service = mock(FinanceCostSnapshotService.class);
|
||||
private final InternalFinanceCostSnapshotController controller = controller();
|
||||
|
||||
@Test
|
||||
void requiresInternalTokenAndUsesStableLoadingPlanIdentity() {
|
||||
assertThatThrownBy(() -> controller.loadingPlan("wrong", 10L, 88L))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("令牌无效");
|
||||
|
||||
controller.loadingPlan("finance-token", 10L, 88L);
|
||||
|
||||
verify(service).loadingPlan(10L, 88L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void exposesLoadingPlanSnapshotPath() throws Exception {
|
||||
var mapping = InternalFinanceCostSnapshotController.class
|
||||
.getAnnotation(org.springframework.web.bind.annotation.RequestMapping.class);
|
||||
assertThat(mapping.value()).containsExactly("/internal/firstleg/v1/finance-cost-snapshots");
|
||||
var method = InternalFinanceCostSnapshotController.class.getDeclaredMethod(
|
||||
"loadingPlan", String.class, long.class, long.class);
|
||||
assertThat(method.getAnnotation(GetMapping.class).value())
|
||||
.containsExactly("/customers/{customerId}/loading-plans/{loadingPlanId}");
|
||||
}
|
||||
|
||||
private InternalFinanceCostSnapshotController controller() {
|
||||
var value = new InternalFinanceCostSnapshotController(service);
|
||||
ReflectionTestUtils.setField(value, "expectedToken", "finance-token");
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+16
-15
@@ -24,28 +24,34 @@ class FinanceCostSnapshotServiceTest {
|
||||
|
||||
@Test
|
||||
void returnsOnlyValidVolumesOwnedByCustomer() {
|
||||
when(repository.plan(88L)).thenReturn(mock(LoadingPlanEntity.class));
|
||||
var plan = new LoadingPlanEntity();
|
||||
plan.setId(88L);
|
||||
plan.setContainerBookingId(99L);
|
||||
when(repository.plan(88L)).thenReturn(plan);
|
||||
when(repository.items(88L)).thenReturn(List.of(
|
||||
item(10L, 101L, 1001L, "1.25"),
|
||||
item(11L, 102L, 1002L, "2.00"),
|
||||
item(10L, 103L, 1003L, "0")));
|
||||
|
||||
var snapshot = service.container(10L, 88L);
|
||||
var snapshot = service.loadingPlan(10L, 88L);
|
||||
|
||||
assertThat(snapshot.customerId()).isEqualTo(10L);
|
||||
assertThat(snapshot.containerId()).isEqualTo(88L);
|
||||
assertThat(snapshot.loadingPlanId()).isEqualTo(88L);
|
||||
assertThat(snapshot.containerBookingId()).isEqualTo(99L);
|
||||
assertThat(snapshot.orders()).containsExactly(
|
||||
new FinanceCostSnapshotService.OrderVolume(101L, 1001L, new BigDecimal("1.25")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsContainerWithoutValidCustomerVolume() {
|
||||
when(repository.plan(88L)).thenReturn(mock(LoadingPlanEntity.class));
|
||||
var plan = new LoadingPlanEntity();
|
||||
plan.setContainerBookingId(99L);
|
||||
when(repository.plan(88L)).thenReturn(plan);
|
||||
when(repository.items(88L)).thenReturn(List.of(
|
||||
item(11L, 102L, 1002L, "2.00"),
|
||||
item(10L, 103L, 1003L, "0")));
|
||||
|
||||
assertThatThrownBy(() -> service.container(10L, 88L))
|
||||
assertThatThrownBy(() -> service.loadingPlan(10L, 88L))
|
||||
.hasMessageContaining("没有该客户的有效订单体积");
|
||||
}
|
||||
|
||||
@@ -53,23 +59,18 @@ class FinanceCostSnapshotServiceTest {
|
||||
void rejectsUnknownContainer() {
|
||||
when(repository.plan(88L)).thenReturn(null);
|
||||
|
||||
assertThatThrownBy(() -> service.container(10L, 88L))
|
||||
assertThatThrownBy(() -> service.loadingPlan(10L, 88L))
|
||||
.hasMessageContaining("柜次不存在");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvesLegacyContainerBookingIdToLoadingPlan() {
|
||||
void rejectsPlanWithoutContainerBooking() {
|
||||
var plan = new LoadingPlanEntity();
|
||||
plan.setId(88L);
|
||||
plan.setContainerBookingId(99L);
|
||||
when(repository.plan(99L)).thenReturn(null);
|
||||
when(repository.planByBooking(99L)).thenReturn(plan);
|
||||
when(repository.items(88L)).thenReturn(List.of(item(10L, 101L, 1001L, "1.25")));
|
||||
when(repository.plan(88L)).thenReturn(plan);
|
||||
|
||||
var snapshot = service.container(10L, 99L);
|
||||
|
||||
assertThat(snapshot.containerId()).isEqualTo(99L);
|
||||
assertThat(snapshot.orders()).hasSize(1);
|
||||
assertThatThrownBy(() -> service.loadingPlan(10L, 88L))
|
||||
.hasMessageContaining("未关联柜次");
|
||||
}
|
||||
|
||||
private LoadingPlanItemEntity item(long customerId, long orderId, long inboundId, String volume) {
|
||||
|
||||
+9
-1
@@ -87,12 +87,20 @@ class InboundMeasurementServiceTest {
|
||||
|
||||
@Test
|
||||
void detailReturnsStandardPalletRecommendationWithoutAllocatingPallet() {
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound("MEASURE_PENDING"));
|
||||
InboundOrderEntity inbound = inbound("MEASURE_PENDING");
|
||||
inbound.setVersion(7);
|
||||
InboundMeasurementEntity measurement = new InboundMeasurementEntity();
|
||||
measurement.setId(30L);
|
||||
measurement.setVersion(4);
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
|
||||
when(repository.findLatestMeasurement(1L, 10L)).thenReturn(measurement);
|
||||
when(repository.listWeights(1L, 10L)).thenReturn(List.of());
|
||||
|
||||
InboundMeasurementResponses.Detail result = service.detail(1L, 10L);
|
||||
|
||||
assertThat(result.recommendedPalletType()).isEqualTo("STANDARD");
|
||||
assertThat(result.inboundVersion()).isEqualTo(7);
|
||||
assertThat(result.version()).isEqualTo(4);
|
||||
verify(repository, never()).updateInbound(any());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user