diff --git a/firstleg/src/main/java/com/baiyue/firstleg/repository/ContainerFeeDetailRepository.java b/firstleg/src/main/java/com/baiyue/firstleg/repository/ContainerFeeDetailRepository.java index eeec19d02..401ba22bc 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/repository/ContainerFeeDetailRepository.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/repository/ContainerFeeDetailRepository.java @@ -40,9 +40,9 @@ public class ContainerFeeDetailRepository { .eq(ContainerFeeDetailEntity::getId, feeId)); } - public ContainerFeeDetailEntity find(long loadingPlanId, long feeId) { + public ContainerFeeDetailEntity findByContainerBookingId(long containerBookingId, long feeId) { return mapper.selectOne(new LambdaQueryWrapper() - .eq(ContainerFeeDetailEntity::getLoadingPlanId, loadingPlanId) + .eq(ContainerFeeDetailEntity::getContainerBookingId, containerBookingId) .eq(ContainerFeeDetailEntity::getId, feeId)); } diff --git a/firstleg/src/main/java/com/baiyue/firstleg/repository/ContainerFeeSourceSnapshotRepository.java b/firstleg/src/main/java/com/baiyue/firstleg/repository/ContainerFeeSourceSnapshotRepository.java index 62aa82d97..6c8b8182a 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/repository/ContainerFeeSourceSnapshotRepository.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/repository/ContainerFeeSourceSnapshotRepository.java @@ -49,10 +49,10 @@ public class ContainerFeeSourceSnapshotRepository { mapper.insert(value); } - public ContainerFeeSourceSnapshotEntity find(long loadingPlanId, long sourceFeeId, - int sourceVersion) { + public ContainerFeeSourceSnapshotEntity findByContainerBookingId(long containerBookingId, long sourceFeeId, + int sourceVersion) { return mapper.selectOne(new LambdaQueryWrapper() - .eq(ContainerFeeSourceSnapshotEntity::getLoadingPlanId, loadingPlanId) + .eq(ContainerFeeSourceSnapshotEntity::getContainerBookingId, containerBookingId) .eq(ContainerFeeSourceSnapshotEntity::getSourceFeeId, sourceFeeId) .eq(ContainerFeeSourceSnapshotEntity::getSourceVersion, sourceVersion)); } diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/ContainerFeeDetailService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/ContainerFeeDetailService.java index dffe32349..1f0c39d60 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/ContainerFeeDetailService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/ContainerFeeDetailService.java @@ -206,7 +206,7 @@ public class ContainerFeeDetailService { sourceSnapshots.saveApproved(approved); String eventType = approved.getSourceVersion() > 1 ? FinanceFeeSourceOutboxService.ADJUSTED : FinanceFeeSourceOutboxService.PUBLISHED; - financeOutbox.publishContainer(eventType, tenantId, loadingPlanId, feeId, + financeOutbox.publishContainer(eventType, tenantId, containerBookingId(approved), feeId, approved.getSourceVersion(), LocalDateTime.now()); } @@ -263,7 +263,7 @@ public class ContainerFeeDetailService { throw conflict("只有当前版本的已审核柜次费用可以作废"); } financeOutbox.publishContainer(FinanceFeeSourceOutboxService.VOIDED, tenantId, - loadingPlanId, feeId, expectedVersion, LocalDateTime.now()); + containerBookingId(current), feeId, expectedVersion, LocalDateTime.now()); } private PlanContext requireOwnedPlan(long tenantId, long loadingPlanId) { @@ -372,6 +372,13 @@ public class ContainerFeeDetailService { return new BizException(ResultCode.CONFLICT, message); } + private static long containerBookingId(ContainerFeeDetailEntity value) { + if (value.getContainerBookingId() == null || value.getContainerBookingId() <= 0) { + throw conflict("柜次费用缺少真实货柜关联"); + } + return value.getContainerBookingId(); + } + private record PlanContext(LoadingPlanEntity plan, ContainerBookingEntity booking) { } private static String text(String value) { return value == null || value.isBlank() ? null : value.trim(); } diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/FinanceFeeSourceCommandService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/FinanceFeeSourceCommandService.java index 1e445122a..ecd4bc69e 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/FinanceFeeSourceCommandService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/FinanceFeeSourceCommandService.java @@ -123,9 +123,10 @@ public class FinanceFeeSourceCommandService { } private void voidContainer(VoidRequest request) { - long containerId = positive(request.containerId(), "柜次ID"); + long containerBookingId = positive(request.containerId(), "柜次ID"); if (request.inboundId() != null) throw bad("柜次费用不能携带入库单ID"); - ContainerFeeDetailEntity detail = containerFees.find(containerId, request.sourceFeeId()); + ContainerFeeDetailEntity detail = containerFees.findByContainerBookingId( + containerBookingId, request.sourceFeeId()); if (detail == null || !Long.valueOf(request.customerId()).equals(detail.getTenantId())) { throw new BizException(ResultCode.NOT_FOUND, "指定客户的柜次费用来源版本不存在"); } @@ -133,7 +134,7 @@ public class FinanceFeeSourceCommandService { throw conflict("柜次费用来源版本已变化"); } if ("VOIDED".equals(detail.getSourceStatus())) return; - containerFeeService.voidApproved(request.customerId(), containerId, + containerFeeService.voidApproved(request.customerId(), detail.getLoadingPlanId(), request.sourceFeeId(), request.sourceVersion(), request.operatorId()); } diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/FinanceFeeSourceOutboxService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/FinanceFeeSourceOutboxService.java index c3eed5e24..23f278731 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/FinanceFeeSourceOutboxService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/FinanceFeeSourceOutboxService.java @@ -36,11 +36,11 @@ public class FinanceFeeSourceOutboxService { payload, occurredAt)); } - public void publishContainer(String eventType, long tenantId, long containerId, + public void publishContainer(String eventType, long tenantId, long containerBookingId, long feeId, int feeVersion, LocalDateTime occurredAt) { FinanceFeeSourceEvent.Payload payload = new FinanceFeeSourceEvent.Payload( - null, String.valueOf(containerId), String.valueOf(feeId), feeVersion); - publish(event(eventType, tenantId, "CONTAINER", containerId, feeId, feeVersion, + null, String.valueOf(containerBookingId), String.valueOf(feeId), feeVersion); + publish(event(eventType, tenantId, "CONTAINER", containerBookingId, feeId, feeVersion, payload, occurredAt)); } diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/FinanceFeeSourceSnapshotService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/FinanceFeeSourceSnapshotService.java index 1ee012cb0..8e506de96 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/FinanceFeeSourceSnapshotService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/FinanceFeeSourceSnapshotService.java @@ -58,12 +58,13 @@ public class FinanceFeeSourceSnapshotService { detail.getActualGrossWeightKg(), detail.getActualVolumeCbm(), sha256(canonical), 1); } - public FinanceFeeSourceSnapshot container(long containerId, long feeId, int sourceVersion) { - ContainerFeeSourceSnapshotEntity detail = containerSnapshots.find(containerId, feeId, sourceVersion); + public FinanceFeeSourceSnapshot container(long containerBookingId, long feeId, int sourceVersion) { + ContainerFeeSourceSnapshotEntity detail = containerSnapshots.findByContainerBookingId( + containerBookingId, feeId, sourceVersion); if (detail == null) throw new BizException(ResultCode.NOT_FOUND, "柜次费用来源不存在"); int version = detail.getSourceVersion(); String canonical = String.join("|", "CONTAINER_FEE", String.valueOf(feeId), - String.valueOf(version), String.valueOf(detail.getTenantId()), String.valueOf(containerId), + String.valueOf(version), String.valueOf(detail.getTenantId()), String.valueOf(containerBookingId), nullSafe(detail.getFeeItemCode()), nullSafe(detail.getDirection()), nullSafe(detail.getSupplierId()), nullSafe(detail.getSupplierName()), nullSafe(detail.getSupplierCategory()), @@ -76,7 +77,8 @@ public class FinanceFeeSourceSnapshotService { detail.getAmount().toPlainString(), nullSafe(detail.getCurrency()), nullSafe(detail.getReviewStatus())); return new FinanceFeeSourceSnapshot(detail.getTenantId(), "CONTAINER_FEE", feeId, - version, detail.getAllocationInboundId(), detail.getAllocationInboundCode(), detail.getAllocationOrderId(), containerId, detail.getContainerBookingId(), + version, detail.getAllocationInboundId(), detail.getAllocationInboundCode(), detail.getAllocationOrderId(), + containerBookingId, containerBookingId, detail.getContainerNo(), detail.getTenantId(), null, detail.getSupplierId(), detail.getSupplierName(), detail.getSupplierCategory(), detail.getSettlementMode(), detail.getPaidAt(), detail.getPaymentMethod(), detail.getPaymentReferenceNo(), diff --git a/firstleg/src/main/resources/openapi/firstleg-v1.yaml b/firstleg/src/main/resources/openapi/firstleg-v1.yaml index c2b48e20c..d20509348 100644 --- a/firstleg/src/main/resources/openapi/firstleg-v1.yaml +++ b/firstleg/src/main/resources/openapi/firstleg-v1.yaml @@ -184,7 +184,7 @@ paths: /internal/firstleg/v1/containers/{containerId}/fees/{feeId}/source-snapshot: get: summary: Finance 按柜次费用来源版本精确回查不可变快照 - description: sourceVersion 必填;只返回 sourceFeeId 与该版本精确匹配的已发布费用,不回退到当前可变版本。 + description: sourceVersion 必填;containerId 固定为真实 containerBookingId,只返回 sourceFeeId 与该版本精确匹配的已发布费用,不接受 loadingPlanId 回退。 security: - internalServiceToken: [] parameters: @@ -2649,8 +2649,8 @@ components: inboundId: {type: integer, format: int64, nullable: true} inboundCode: {type: string, nullable: true} orderId: {type: integer, format: int64, nullable: true} - containerId: {type: integer, format: int64, nullable: true} - containerBookingId: {type: integer, format: int64, nullable: true} + containerId: {type: integer, format: int64, nullable: true, description: CONTAINER_FEE时固定为真实containerBookingId} + containerBookingId: {type: integer, format: int64, nullable: true, description: 真实containerBookingId;CONTAINER_FEE时与containerId一致} containerNo: {type: string, nullable: true} customerId: {type: integer, format: int64} customerName: {type: string} diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/ContainerFeeDetailServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/ContainerFeeDetailServiceTest.java index 0c647f3b2..a0788b792 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/ContainerFeeDetailServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/ContainerFeeDetailServiceTest.java @@ -75,6 +75,7 @@ class ContainerFeeDetailServiceTest { approved.setId(51L); approved.setTenantId(10001L); approved.setLoadingPlanId(41L); + approved.setContainerBookingId(61L); approved.setFeeItemId(11L); approved.setDirection("RECEIVABLE"); approved.setSourceVersion(1); @@ -87,7 +88,7 @@ class ContainerFeeDetailServiceTest { service.approve(10001L, 41L, 51L, 9L, "admin"); verify(outbox).publishContainer(eq(FinanceFeeSourceOutboxService.PUBLISHED), - eq(10001L), eq(41L), eq(51L), eq(1), any(LocalDateTime.class)); + eq(10001L), eq(61L), eq(51L), eq(1), any(LocalDateTime.class)); } @Test @@ -284,6 +285,7 @@ class ContainerFeeDetailServiceTest { private void ownedPlan() { LoadingPlanEntity plan = new LoadingPlanEntity(); plan.setId(41L); + plan.setContainerBookingId(61L); when(loading.plan(41L)).thenReturn(plan); LoadingPlanItemEntity item = new LoadingPlanItemEntity(); item.setAccountId(10001L); @@ -316,6 +318,7 @@ class ContainerFeeDetailServiceTest { value.setId(51L); value.setTenantId(10001L); value.setLoadingPlanId(41L); + value.setContainerBookingId(61L); value.setFeeItemId(11L); value.setFeeItemCodeSnapshot("TRUCKING"); value.setFeeItemNameSnapshot("拖车费"); diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/FinanceFeeSourceCommandServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/FinanceFeeSourceCommandServiceTest.java index 772e4f0ad..109ac3c39 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/FinanceFeeSourceCommandServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/FinanceFeeSourceCommandServiceTest.java @@ -96,11 +96,12 @@ class FinanceFeeSourceCommandServiceTest { @Test void routesContainerVoidThroughExistingBusinessService() throws Exception { VoidRequest request = new VoidRequest("CONTAINER_FEE", 800L, 4, 9L, - null, 200L, 71L, "finance", "柜次费用作废"); + null, 61L, 71L, "finance", "柜次费用作废"); ContainerFeeDetailEntity detail = new ContainerFeeDetailEntity(); detail.setId(800L); detail.setTenantId(9L); detail.setLoadingPlanId(200L); + detail.setContainerBookingId(61L); detail.setSourceVersion(4); detail.setSourceStatus("ACTIVE"); - when(containerFees.find(200L, 800L)).thenReturn(detail); + when(containerFees.findByContainerBookingId(61L, 800L)).thenReturn(detail); service.voidSource("void-container", hash(request), request); diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/FinanceFeeSourceOutboxServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/FinanceFeeSourceOutboxServiceTest.java index 278c9a2a3..f9d3f259d 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/FinanceFeeSourceOutboxServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/FinanceFeeSourceOutboxServiceTest.java @@ -49,4 +49,18 @@ class FinanceFeeSourceOutboxServiceTest { assertThatCode(() -> service.publishContainer(FinanceFeeSourceOutboxService.VOIDED, 10001L, 41L, 51L, 2, LocalDateTime.now())).doesNotThrowAnyException(); } + + @Test + void containerEventUsesTheRealContainerBookingIdAsAggregateAndPayloadIdentity() { + service.publishContainer(FinanceFeeSourceOutboxService.PUBLISHED, + 10001L, 61L, 51L, 2, LocalDateTime.of(2026, 8, 11, 12, 0)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(InboundOutboxEntity.class); + verify(mapper).insert(captor.capture()); + InboundOutboxEntity event = captor.getValue(); + assertThat(event.getAggregateType()).isEqualTo("CONTAINER"); + assertThat(event.getAggregateId()).isEqualTo(61L); + assertThat(event.getPayloadJson()).contains("\"containerId\":\"61\"") + .doesNotContain("\"containerId\":\"41\""); + } } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/FinanceFeeSourceSnapshotServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/FinanceFeeSourceSnapshotServiceTest.java index 8895f4643..1c22f0a74 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/FinanceFeeSourceSnapshotServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/FinanceFeeSourceSnapshotServiceTest.java @@ -90,10 +90,12 @@ class FinanceFeeSourceSnapshotServiceTest { detail.setCurrency("CNY"); detail.setReviewStatus("APPROVED"); detail.setSourceVersion(2); - when(containerFees.find(41L, 51L, 2)).thenReturn(detail); + when(containerFees.findByContainerBookingId(61L, 51L, 2)).thenReturn(detail); - FinanceFeeSourceSnapshot snapshot = service.container(41L, 51L, 2); + FinanceFeeSourceSnapshot snapshot = service.container(61L, 51L, 2); + assertThat(snapshot.containerId()).isEqualTo(61L); + assertThat(snapshot.containerBookingId()).isEqualTo(61L); assertThat(snapshot.supplierId()).isEqualTo(71L); assertThat(snapshot.supplierName()).isEqualTo("现有供应商"); assertThat(snapshot.supplierCategory()).isEqualTo("TRUCKING");