From 55c36cf290d9c7d2b732bd77bcd93a3fe3bf457a Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 8 Sep 2026 21:24:49 +0800 Subject: [PATCH 1/2] fix(firstleg): publish fees by container booking --- .../repository/ContainerFeeDetailRepository.java | 4 ++-- .../ContainerFeeSourceSnapshotRepository.java | 6 +++--- .../service/ContainerFeeDetailService.java | 11 +++++++++-- .../service/FinanceFeeSourceCommandService.java | 7 ++++--- .../service/FinanceFeeSourceOutboxService.java | 6 +++--- .../service/FinanceFeeSourceSnapshotService.java | 10 ++++++---- .../src/main/resources/openapi/firstleg-v1.yaml | 6 +++--- .../service/ContainerFeeDetailServiceTest.java | 5 ++++- .../FinanceFeeSourceCommandServiceTest.java | 5 +++-- .../service/FinanceFeeSourceOutboxServiceTest.java | 14 ++++++++++++++ .../FinanceFeeSourceSnapshotServiceTest.java | 6 ++++-- 11 files changed, 55 insertions(+), 25 deletions(-) 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"); From 1cf6722d6e8e41612803a456c280eead487e1381 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 8 Sep 2026 21:27:23 +0800 Subject: [PATCH 2/2] fix(finance): key container finance by booking --- ...mFinanceContainerSettlementController.java | 4 +- .../controller/PlatformFinanceController.java | 8 +++- .../dto/FinanceContainerSettlementDtos.java | 4 +- ...FinanceContainerSettlementQueryMapper.java | 27 +++++++------- ...inanceContainerSettlementQueryService.java | 14 +++---- .../service/FinanceFeeSourceEventService.java | 15 ++++++-- finance/src/main/resources/db/finance.sql | 2 +- .../finance-container-settlement-v1.yaml | 14 +++---- ...ontainerSettlementOpenApiContractTest.java | 4 +- ...inerSettlementQueryMapperContractTest.java | 3 +- ...ceContainerSettlementQueryServiceTest.java | 6 +-- .../FinanceFeeSourceEventServiceTest.java | 37 ++++++++++++++++--- ...PlatformFinanceControllerBehaviorTest.java | 11 ++++-- 13 files changed, 99 insertions(+), 50 deletions(-) diff --git a/finance/src/main/java/com/baiyue/finance/controller/PlatformFinanceContainerSettlementController.java b/finance/src/main/java/com/baiyue/finance/controller/PlatformFinanceContainerSettlementController.java index 651b704da..0e10ae51a 100644 --- a/finance/src/main/java/com/baiyue/finance/controller/PlatformFinanceContainerSettlementController.java +++ b/finance/src/main/java/com/baiyue/finance/controller/PlatformFinanceContainerSettlementController.java @@ -27,7 +27,7 @@ public class PlatformFinanceContainerSettlementController { + "hasAuthority('platform:finance:settlement-ap:list')") @Operation(summary = "按客户和柜次批量查询已结算柜费及成本分摊") public ApiResponse details(@RequestParam long customerId, - @RequestParam long containerId) { - return FinanceResponses.success(service.details(customerId, containerId)); + @RequestParam long containerBookingId) { + return FinanceResponses.success(service.details(customerId, containerBookingId)); } } diff --git a/finance/src/main/java/com/baiyue/finance/controller/PlatformFinanceController.java b/finance/src/main/java/com/baiyue/finance/controller/PlatformFinanceController.java index 6cead789a..be239533d 100644 --- a/finance/src/main/java/com/baiyue/finance/controller/PlatformFinanceController.java +++ b/finance/src/main/java/com/baiyue/finance/controller/PlatformFinanceController.java @@ -164,8 +164,8 @@ public class PlatformFinanceController { @BusinessAudit(event = "FINANCE_COST_CREATE", resourceType = "FINANCE_COST") public ApiResponse cost(@AuthenticationPrincipal AuthenticatedUser user, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody CostRequest request) { if (request.currency() != null && !"CNY".equals(request.currency())) throw new IllegalArgumentException("首期仅支持CNY"); - var internal = new FinanceService.CostRequest(null, request.costType(), request.orderId(), null, request.containerId(), null, null, "MANUAL", request.expenseName(), request.amount(), request.voucherFileIds(), request.remark()); java.util.List volumes = java.util.List.of(); + Long containerBookingId = null; if ("CONTAINER_SHARED".equals(request.costType())) { if (request.containerId() == null) throw new IllegalArgumentException("整柜成本必须指定柜次"); var response = costSnapshotClient.loadingPlan(fileToken, request.customerId(), request.containerId()); @@ -180,9 +180,13 @@ public class PlatformFinanceController { || snapshot.orders() == null || snapshot.orders().isEmpty()) { throw new IllegalArgumentException("Firstleg未返回有效柜内订单体积快照"); } + containerBookingId = snapshot.containerBookingId(); volumes = snapshot.orders().stream().map(v -> new FinanceService.ContainerOrderVolume(v.orderId(), - v.inboundId(), snapshot.loadingPlanId(), snapshot.customerId(), v.volumeCbm())).toList(); + v.inboundId(), snapshot.containerBookingId(), snapshot.customerId(), v.volumeCbm())).toList(); } + var internal = new FinanceService.CostRequest(null, request.costType(), request.orderId(), null, + containerBookingId, null, null, "MANUAL", request.expenseName(), request.amount(), + request.voucherFileIds(), request.remark()); return FinanceResponses.success(resourceView(service.createCost(request.customerId(), user.userId(), internal, volumes, key))); } diff --git a/finance/src/main/java/com/baiyue/finance/dto/FinanceContainerSettlementDtos.java b/finance/src/main/java/com/baiyue/finance/dto/FinanceContainerSettlementDtos.java index c6b7ce5b9..1ccc7c162 100644 --- a/finance/src/main/java/com/baiyue/finance/dto/FinanceContainerSettlementDtos.java +++ b/finance/src/main/java/com/baiyue/finance/dto/FinanceContainerSettlementDtos.java @@ -9,7 +9,7 @@ public final class FinanceContainerSettlementDtos { public record ContainerSettlementDetails( long customerId, - long containerId, + long containerBookingId, List settledSourceFeeIds, List costs) {} @@ -20,7 +20,7 @@ public final class FinanceContainerSettlementDtos { String costOrigin, Long sourceFeeId, Integer sourceFeeVersion, - long containerId, + long containerBookingId, String supplierName, String feeItemName, BigDecimal amount, diff --git a/finance/src/main/java/com/baiyue/finance/mapper/FinanceContainerSettlementQueryMapper.java b/finance/src/main/java/com/baiyue/finance/mapper/FinanceContainerSettlementQueryMapper.java index 0844c3247..4e3d05723 100644 --- a/finance/src/main/java/com/baiyue/finance/mapper/FinanceContainerSettlementQueryMapper.java +++ b/finance/src/main/java/com/baiyue/finance/mapper/FinanceContainerSettlementQueryMapper.java @@ -13,14 +13,15 @@ public interface FinanceContainerSettlementQueryMapper { @Select(""" SELECT COUNT(*) AS customerLinkCount FROM ( - SELECT customer_id, container_id FROM fin_fee_source - WHERE customer_id = #{customerId} AND container_id = #{containerId} + SELECT customer_id, container_booking_id AS container_id FROM fin_fee_source + WHERE customer_id = #{customerId} AND container_booking_id = #{containerBookingId} UNION ALL SELECT tenant_id AS customer_id, container_id FROM fin_cost - WHERE tenant_id = #{customerId} AND container_id = #{containerId} + WHERE tenant_id = #{customerId} AND container_id = #{containerBookingId} ) links """) - OwnershipRow ownership(@Param("customerId") long customerId, @Param("containerId") long containerId); + OwnershipRow ownership(@Param("customerId") long customerId, + @Param("containerBookingId") long containerBookingId); @Select(""" SELECT DISTINCT settled.source_fee_id @@ -32,14 +33,14 @@ public interface FinanceContainerSettlementQueryMapper { WHERE s.tenant_id = #{customerId} AND s.status = 'PAID' AND f.customer_id = #{customerId} - AND f.container_id = #{containerId} + AND f.container_booking_id = #{containerBookingId} AND f.source_system = 'FIRSTLEG' AND f.source_type = 'CONTAINER_FEE' UNION ALL SELECT c.source_fee_id FROM fin_cost c WHERE c.tenant_id = #{customerId} - AND c.container_id = #{containerId} + AND c.container_id = #{containerBookingId} AND c.cost_origin = 'FIRSTLEG_DIRECT_PAID' AND c.source_system = 'FIRSTLEG' AND c.source_type = 'CONTAINER_FEE' @@ -49,12 +50,12 @@ public interface FinanceContainerSettlementQueryMapper { ORDER BY settled.source_fee_id """) List settledSourceFeeIds(@Param("customerId") long customerId, - @Param("containerId") long containerId); + @Param("containerBookingId") long containerBookingId); @Select(""" SELECT c.id AS costId, c.cost_no AS costNo, c.cost_type AS costType, c.cost_origin AS costOrigin, c.source_fee_id AS sourceFeeId, c.source_fee_version AS sourceFeeVersion, - c.container_id AS containerId, c.supplier_name_snapshot AS supplierName, + c.container_id AS containerBookingId, c.supplier_name_snapshot AS supplierName, c.fee_item_name AS feeItemName, c.cost_amount AS amount, c.currency_code AS currency, c.status, a.id AS allocationId, a.order_id AS orderId, a.inbound_id AS inboundId, @@ -63,11 +64,11 @@ public interface FinanceContainerSettlementQueryMapper { a.rounding_adjustment AS roundingAdjustment FROM fin_cost c LEFT JOIN fin_cost_allocation a ON a.tenant_id = c.tenant_id AND a.cost_id = c.id - WHERE c.tenant_id = #{customerId} AND c.container_id = #{containerId} + WHERE c.tenant_id = #{customerId} AND c.container_id = #{containerBookingId} ORDER BY c.created_at DESC, c.id DESC, a.id """) List costs(@Param("customerId") long customerId, - @Param("containerId") long containerId); + @Param("containerBookingId") long containerBookingId); final class OwnershipRow { private long customerLinkCount; @@ -83,7 +84,7 @@ public interface FinanceContainerSettlementQueryMapper { private String costOrigin; private Long sourceFeeId; private Integer sourceFeeVersion; - private long containerId; + private long containerBookingId; private String supplierName; private String feeItemName; private BigDecimal amount; @@ -110,8 +111,8 @@ public interface FinanceContainerSettlementQueryMapper { public void setSourceFeeId(Long value) { this.sourceFeeId = value; } public Integer getSourceFeeVersion() { return sourceFeeVersion; } public void setSourceFeeVersion(Integer value) { this.sourceFeeVersion = value; } - public long getContainerId() { return containerId; } - public void setContainerId(long value) { this.containerId = value; } + public long getContainerBookingId() { return containerBookingId; } + public void setContainerBookingId(long value) { this.containerBookingId = value; } public String getSupplierName() { return supplierName; } public void setSupplierName(String value) { this.supplierName = value; } public String getFeeItemName() { return feeItemName; } diff --git a/finance/src/main/java/com/baiyue/finance/service/FinanceContainerSettlementQueryService.java b/finance/src/main/java/com/baiyue/finance/service/FinanceContainerSettlementQueryService.java index a0a14b188..beea4c0c2 100644 --- a/finance/src/main/java/com/baiyue/finance/service/FinanceContainerSettlementQueryService.java +++ b/finance/src/main/java/com/baiyue/finance/service/FinanceContainerSettlementQueryService.java @@ -22,21 +22,21 @@ public class FinanceContainerSettlementQueryService { this.mapper = mapper; } - public ContainerSettlementDetails details(long customerId, long containerId) { + public ContainerSettlementDetails details(long customerId, long containerBookingId) { if (customerId <= 0) throw new IllegalArgumentException("客户范围无效"); - if (containerId <= 0) throw new IllegalArgumentException("柜次范围无效"); + if (containerBookingId <= 0) throw new IllegalArgumentException("货柜范围无效"); - var ownership = mapper.ownership(customerId, containerId); + var ownership = mapper.ownership(customerId, containerBookingId); if (ownership == null || ownership.getCustomerLinkCount() == 0) { throw new BizException(ResultCode.NOT_FOUND, "柜次不存在或不属于客户"); } - List settledSourceFeeIds = mapper.settledSourceFeeIds(customerId, containerId).stream() + List settledSourceFeeIds = mapper.settledSourceFeeIds(customerId, containerBookingId).stream() .filter(value -> value != null && value > 0) .distinct() .toList(); - List costs = groupCosts(mapper.costs(customerId, containerId)); - return new ContainerSettlementDetails(customerId, containerId, settledSourceFeeIds, costs); + List costs = groupCosts(mapper.costs(customerId, containerBookingId)); + return new ContainerSettlementDetails(customerId, containerBookingId, settledSourceFeeIds, costs); } private static List groupCosts(List rows) { @@ -60,7 +60,7 @@ public class FinanceContainerSettlementQueryService { private ContainerCostDetail view() { return new ContainerCostDetail(cost.getCostId(), cost.getCostNo(), cost.getCostType(), - cost.getCostOrigin(), cost.getSourceFeeId(), cost.getSourceFeeVersion(), cost.getContainerId(), + cost.getCostOrigin(), cost.getSourceFeeId(), cost.getSourceFeeVersion(), cost.getContainerBookingId(), cost.getSupplierName(), cost.getFeeItemName(), cost.getAmount(), cost.getCurrency(), cost.getStatus(), List.copyOf(allocations)); } diff --git a/finance/src/main/java/com/baiyue/finance/service/FinanceFeeSourceEventService.java b/finance/src/main/java/com/baiyue/finance/service/FinanceFeeSourceEventService.java index 6687e7d3f..44580404f 100644 --- a/finance/src/main/java/com/baiyue/finance/service/FinanceFeeSourceEventService.java +++ b/finance/src/main/java/com/baiyue/finance/service/FinanceFeeSourceEventService.java @@ -86,10 +86,11 @@ public class FinanceFeeSourceEventService { snapshot.sourceType(), snapshot.feeId()); jdbc.update("UPDATE fin_cost SET status='VOIDED',voided_at=CURRENT_TIMESTAMP(3),void_reason='来源版本已替代',version=version+1,updated_by=0 WHERE cost_origin='FIRSTLEG_DIRECT_PAID' AND source_system='FIRSTLEG' AND source_type=? AND source_fee_id=? AND status='CONFIRMED'", snapshot.sourceType(), snapshot.feeId()); - jdbc.update("INSERT INTO fin_fee_source(source_system,source_fee_id,source_fee_version,customer_id,source_type,source_no,direction,order_id,inbound_id,container_id,fee_item_code,fee_item_name,amount,currency_code,settlement_mode,paid_at,payment_method,payment_reference_no,payment_voucher_file_ids,source_hash,source_snapshot,status) VALUES ('FIRSTLEG',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'AVAILABLE')", + jdbc.update("INSERT INTO fin_fee_source(source_system,source_fee_id,source_fee_version,customer_id,source_type,source_no,direction,order_id,inbound_id,container_id,container_booking_id,fee_item_code,fee_item_name,amount,currency_code,settlement_mode,paid_at,payment_method,payment_reference_no,payment_voucher_file_ids,source_hash,source_snapshot,status) VALUES ('FIRSTLEG',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'AVAILABLE')", snapshot.feeId(), snapshot.feeVersion(), snapshot.customerId(), snapshot.sourceType(), snapshot.inboundCode() == null || snapshot.inboundCode().isBlank() ? snapshot.containerNo() : snapshot.inboundCode(), snapshot.direction(), snapshot.orderId(), snapshot.inboundId(), snapshot.containerId(), + financeContainerBookingId(snapshot), snapshot.feeItemCode(), snapshot.feeItemName(), snapshot.totalAmount(), snapshot.currency(), snapshot.settlementMode(), snapshot.paidAt(), snapshot.paymentMethod(), snapshot.paymentReferenceNo(), voucherJson(snapshot.paymentVoucherFileIds()), @@ -116,7 +117,7 @@ public class FinanceFeeSourceEventService { String costNo = "DIRECT-" + snapshot.sourceType() + "-" + snapshot.feeId() + "-V" + snapshot.feeVersion(); jdbc.update("INSERT INTO fin_cost(tenant_id,cost_no,cost_type,cost_origin,source_system,source_type,source_fee_id,source_fee_version,source_hash,order_id,inbound_id,container_id,supplier_id,supplier_name_snapshot,fee_item_code,fee_item_name,currency_code,cost_amount,voucher_file_ids,paid_at,payment_method,payment_reference_no,status,confirmed_at,created_by,created_by_name,updated_by) VALUES(?,?,'SOURCE_DIRECT_PAID','FIRSTLEG_DIRECT_PAID','FIRSTLEG',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'CONFIRMED',CURRENT_TIMESTAMP(3),0,'系统',0)", snapshot.customerId(), costNo, snapshot.sourceType(), snapshot.feeId(), snapshot.feeVersion(), - snapshot.sourceHash(), snapshot.orderId(), snapshot.inboundId(), snapshot.containerId(), + snapshot.sourceHash(), snapshot.orderId(), snapshot.inboundId(), financeContainerBookingId(snapshot), snapshot.supplierId(), snapshot.supplierName(), snapshot.feeItemCode(), snapshot.feeItemName(), snapshot.currency(), snapshot.totalAmount(), voucherJsonOrEmpty(snapshot.paymentVoucherFileIds()), snapshot.paidAt(), snapshot.paymentMethod(), snapshot.paymentReferenceNo()); @@ -184,7 +185,11 @@ public class FinanceFeeSourceEventService { throw error("FIN-EVT-012", "应收费用不得携带结算方式或已付快照"); } if ("INBOUND_FEE".equals(value.sourceType()) && (value.inboundId() == null || !String.valueOf(value.inboundId()).equals(event.payload().inboundId()))) throw error("FIN-EVT-013", "入库单归属不一致"); - if ("CONTAINER_FEE".equals(value.sourceType()) && (value.containerId() == null || !String.valueOf(value.containerId()).equals(event.payload().containerId()))) throw error("FIN-EVT-014", "柜子归属不一致"); + if ("CONTAINER_FEE".equals(value.sourceType()) && (value.containerId() == null + || !String.valueOf(value.containerId()).equals(event.payload().containerId()) + || value.containerBookingId() == null || value.containerBookingId() <= 0)) { + throw error("FIN-EVT-014", "柜子归属不一致"); + } } private void validatePayableSettlement(FirstlegFeeSourceClient.Snapshot value) { @@ -216,6 +221,10 @@ public class FinanceFeeSourceEventService { return values == null || values.isEmpty() ? "[]" : json(values); } + private static Long financeContainerBookingId(FirstlegFeeSourceClient.Snapshot snapshot) { + return "CONTAINER_FEE".equals(snapshot.sourceType()) ? snapshot.containerBookingId() : null; + } + private String json(Object value) { try { return objectMapper.writeValueAsString(value); } catch (JsonProcessingException e) { throw error("FIN-EVT-001", "事件序列化失败"); } diff --git a/finance/src/main/resources/db/finance.sql b/finance/src/main/resources/db/finance.sql index b4c86dab1..9ce08b473 100644 --- a/finance/src/main/resources/db/finance.sql +++ b/finance/src/main/resources/db/finance.sql @@ -229,7 +229,7 @@ CREATE TABLE IF NOT EXISTS fin_cost ( source_hash VARCHAR(71) NULL COMMENT '来源不可变快照SHA-256,兼容sha256前缀格式', order_id BIGINT UNSIGNED NULL COMMENT '成本归属的头程订单ID,单票成本必填', inbound_id BIGINT UNSIGNED NULL COMMENT '成本关联的入库单ID,可为空', - container_id BIGINT UNSIGNED NULL COMMENT 'Firstleg装柜计划ID,整柜公共成本必填', + container_id BIGINT UNSIGNED NULL COMMENT 'Firstleg真实货柜containerBookingId,整柜公共成本必填', warehouse_id BIGINT UNSIGNED NULL COMMENT '业务直接已付成本关联仓库ID,可为空', supplier_id BIGINT UNSIGNED NULL COMMENT 'Warehouse公共往来单位或供应商引用ID', supplier_name_snapshot VARCHAR(200) NULL COMMENT '登记成本时供应商或成本对象名称快照', diff --git a/finance/src/main/resources/openapi/finance-container-settlement-v1.yaml b/finance/src/main/resources/openapi/finance-container-settlement-v1.yaml index 067491414..c7b0a8f0b 100644 --- a/finance/src/main/resources/openapi/finance-container-settlement-v1.yaml +++ b/finance/src/main/resources/openapi/finance-container-settlement-v1.yaml @@ -10,7 +10,7 @@ paths: get: summary: 按客户和柜次查询已结算柜费及成本分摊 description: >- - customerId 与 containerId 必须在 Finance 的费用来源或成本快照中存在归属关系。 + customerId 与 containerBookingId 必须在 Finance 的费用来源或成本快照中存在归属关系。 服务端固定执行三条集合 SQL,不按结算单或成本逐项查询;响应不包含整张结算单、 其他客户数据、付款凭证或内部备注。 x-permissions-all-of: @@ -25,10 +25,10 @@ paths: in: query required: true schema: {$ref: '#/components/schemas/Id'} - - name: containerId + - name: containerBookingId in: query required: true - description: Firstleg 装柜计划 ID。 + description: Firstleg 真实货柜 containerBookingId,不是装柜计划 loadingPlanId。 schema: {$ref: '#/components/schemas/Id'} responses: '200': @@ -63,10 +63,10 @@ components: timestamp: {type: string, format: date-time} ContainerSettlementDetails: type: object - required: [customerId, containerId, settledSourceFeeIds, costs] + required: [customerId, containerBookingId, settledSourceFeeIds, costs] properties: customerId: {$ref: '#/components/schemas/Id'} - containerId: {$ref: '#/components/schemas/Id'} + containerBookingId: {$ref: '#/components/schemas/Id'} settledSourceFeeIds: type: array uniqueItems: true @@ -76,7 +76,7 @@ components: items: {$ref: '#/components/schemas/ContainerCostDetail'} ContainerCostDetail: type: object - required: [costId, costNo, costType, costOrigin, containerId, feeItemName, amount, currency, status, allocations] + required: [costId, costNo, costType, costOrigin, containerBookingId, feeItemName, amount, currency, status, allocations] properties: costId: {$ref: '#/components/schemas/Id'} costNo: {type: string} @@ -84,7 +84,7 @@ components: costOrigin: {type: string, enum: [MANUAL, FIRSTLEG_DIRECT_PAID, BUSINESS_DIRECT_PAID]} sourceFeeId: {$ref: '#/components/schemas/Id'} sourceFeeVersion: {type: integer, minimum: 1} - containerId: {$ref: '#/components/schemas/Id'} + containerBookingId: {$ref: '#/components/schemas/Id'} supplierName: {type: string, nullable: true} feeItemName: {type: string} amount: {$ref: '#/components/schemas/Money'} diff --git a/finance/src/test/java/com/baiyue/finance/FinanceContainerSettlementOpenApiContractTest.java b/finance/src/test/java/com/baiyue/finance/FinanceContainerSettlementOpenApiContractTest.java index 9cacd2197..09c7b29dd 100644 --- a/finance/src/test/java/com/baiyue/finance/FinanceContainerSettlementOpenApiContractTest.java +++ b/finance/src/test/java/com/baiyue/finance/FinanceContainerSettlementOpenApiContractTest.java @@ -22,6 +22,8 @@ class FinanceContainerSettlementOpenApiContractTest { .contains("platform:finance:settlement-ar:list") .contains("platform:finance:settlement-ap:list") .contains("x-query-count: 3 fixed set-based SQL statements") - .contains("settledSourceFeeIds:", "costs:", "allocations:"); + .contains("containerBookingId:", "不是装柜计划 loadingPlanId", + "settledSourceFeeIds:", "costs:", "allocations:") + .doesNotContain("- name: containerId"); } } diff --git a/finance/src/test/java/com/baiyue/finance/FinanceContainerSettlementQueryMapperContractTest.java b/finance/src/test/java/com/baiyue/finance/FinanceContainerSettlementQueryMapperContractTest.java index 1d0e0c574..db1107348 100644 --- a/finance/src/test/java/com/baiyue/finance/FinanceContainerSettlementQueryMapperContractTest.java +++ b/finance/src/test/java/com/baiyue/finance/FinanceContainerSettlementQueryMapperContractTest.java @@ -23,7 +23,8 @@ class FinanceContainerSettlementQueryMapperContractTest { .reduce("", (left, right) -> left + "\n" + right); assertThat(sql).contains("JOIN fin_settlement_line", "JOIN fin_fee_source", "LEFT JOIN fin_cost_allocation", "c.tenant_id = #{customerId}", - "c.container_id = #{containerId}"); + "f.container_booking_id = #{containerBookingId}", + "c.container_id = #{containerBookingId}"); assertThat(sql).doesNotContain("${"); } } diff --git a/finance/src/test/java/com/baiyue/finance/FinanceContainerSettlementQueryServiceTest.java b/finance/src/test/java/com/baiyue/finance/FinanceContainerSettlementQueryServiceTest.java index 5514a6f31..3dc4f94ff 100644 --- a/finance/src/test/java/com/baiyue/finance/FinanceContainerSettlementQueryServiceTest.java +++ b/finance/src/test/java/com/baiyue/finance/FinanceContainerSettlementQueryServiceTest.java @@ -33,7 +33,7 @@ class FinanceContainerSettlementQueryServiceTest { var result = new FinanceContainerSettlementQueryService(mapper).details(101L, 301L); assertThat(result.customerId()).isEqualTo(101L); - assertThat(result.containerId()).isEqualTo(301L); + assertThat(result.containerBookingId()).isEqualTo(301L); assertThat(result.settledSourceFeeIds()).containsExactly(901L, 902L); assertThat(result.costs()).hasSize(2); assertThat(result.costs().get(0).costId()).isEqualTo(501L); @@ -79,14 +79,14 @@ class FinanceContainerSettlementQueryServiceTest { return row; } - private static CostAllocationRow row(long costId, long containerId, Long allocationId, + private static CostAllocationRow row(long costId, long containerBookingId, Long allocationId, Long orderId, boolean roundingAdjustment) { var row = new CostAllocationRow(); row.setCostId(costId); row.setCostNo("COST-" + costId); row.setCostType("CONTAINER_SHARED"); row.setCostOrigin("MANUAL"); - row.setContainerId(containerId); + row.setContainerBookingId(containerBookingId); row.setSupplierName("供应商"); row.setFeeItemName("柜费"); row.setAmount(new BigDecimal("100.00")); diff --git a/finance/src/test/java/com/baiyue/finance/FinanceFeeSourceEventServiceTest.java b/finance/src/test/java/com/baiyue/finance/FinanceFeeSourceEventServiceTest.java index 3f44aba12..4bee25015 100644 --- a/finance/src/test/java/com/baiyue/finance/FinanceFeeSourceEventServiceTest.java +++ b/finance/src/test/java/com/baiyue/finance/FinanceFeeSourceEventServiceTest.java @@ -62,6 +62,31 @@ class FinanceFeeSourceEventServiceTest { assertThat(service.consume(event)).isEqualTo(101L); verify(firstleg).container("test-token", 41, 31, 1); verify(jdbc).update(contains("status='SUPERSEDED'"), eq("CONTAINER_FEE"), eq(31L)); + ArgumentCaptor values = ArgumentCaptor.forClass(Object[].class); + verify(jdbc).update(startsWith("INSERT INTO fin_fee_source"), values.capture()); + assertThat(values.getValue()[8]).as("fin_fee_source.container_id keeps loadingPlanId") + .isEqualTo(41L); + assertThat(values.getValue()[9]).as("fin_fee_source.container_booking_id stores physical container") + .isEqualTo(61L); + } + + @Test + void rejectsContainerFeeSnapshotWithoutPhysicalContainerBookingId() { + FinanceFeeSourceEvent event = containerEvent("container-missing-booking", "FIRSTLEG_FEE_PUBLISHED", 1, 7); + when(jdbc.queryForList(startsWith("SELECT payload_hash,status"), eq("FIRSTLEG"), + eq("container-missing-booking"))).thenReturn(List.of()); + var snapshot = new FirstlegFeeSourceClient.Snapshot(7, "CONTAINER_FEE", 31, 1, + null, null, null, 41L, null, "C-41", 7, "客户", null, null, + null, null, null, null, null, List.of(), 61L, "CUSTOMS", "清关费", + "RECEIVABLE", LocalDate.of(2026, 8, 11), "CNY", new BigDecimal("100.00"), + "APPROVED", "PUBLISHED", LocalDateTime.of(2026, 8, 11, 1, 0), + BigDecimal.ONE, "SHIPMENT", BigDecimal.TEN, BigDecimal.ONE, "source-hash", 1); + when(firstleg.container("test-token", 41, 31, 1)).thenReturn(ApiResponse.success(snapshot)); + + assertThatThrownBy(() -> service.consume(event)) + .isInstanceOf(FinanceEventException.class) + .extracting(error -> ((FinanceEventException) error).code()).isEqualTo("FIN-EVT-014"); + verify(jdbc, never()).update(startsWith("INSERT INTO fin_fee_source"), any(Object[].class)); } @Test @@ -83,7 +108,7 @@ class FinanceFeeSourceEventServiceTest { verify(jdbc, never()).update(startsWith("INSERT INTO fin_cost"), any(Object[].class)); ArgumentCaptor values = ArgumentCaptor.forClass(Object[].class); verify(jdbc).update(startsWith("INSERT INTO fin_fee_source"), values.capture()); - assertThat(values.getValue()[19].toString()) + assertThat(values.getValue()[20].toString()) .contains("\"supplierId\":71", "\"supplierName\":\"现有供应商\"", "\"supplierCategory\":\"TRUCKING\""); } @@ -121,9 +146,9 @@ class FinanceFeeSourceEventServiceTest { ArgumentCaptor values = ArgumentCaptor.forClass(Object[].class); verify(jdbc).update(startsWith("INSERT INTO fin_fee_source"), values.capture()); - assertThat(values.getValue()[13]).isEqualTo("DIRECT_PAID"); - assertThat(values.getValue()[15]).isEqualTo("BANK_TRANSFER"); - assertThat(values.getValue()[17]).isEqualTo("[901]"); + assertThat(values.getValue()[14]).isEqualTo("DIRECT_PAID"); + assertThat(values.getValue()[16]).isEqualTo("BANK_TRANSFER"); + assertThat(values.getValue()[18]).isEqualTo("[901]"); ArgumentCaptor costValues = ArgumentCaptor.forClass(Object[].class); verify(jdbc).update(startsWith("INSERT INTO fin_cost"), costValues.capture()); assertThat(costValues.getValue()[0]).isEqualTo(7L); @@ -131,6 +156,8 @@ class FinanceFeeSourceEventServiceTest { assertThat(costValues.getValue()[2]).isEqualTo("CONTAINER_FEE"); assertThat(costValues.getValue()[3]).isEqualTo(31L); assertThat(costValues.getValue()[4]).isEqualTo(1); + assertThat(costValues.getValue()[8]).as("fin_cost.container_id stores containerBookingId") + .isEqualTo(61L); assertThat(costValues.getValue()[15]).isEqualTo("[901]"); assertThat(costValues.getValue()[17]).isEqualTo("BANK_TRANSFER"); assertThat(costValues.getValue()[18]).isEqualTo("BANK-001"); @@ -284,7 +311,7 @@ class FinanceFeeSourceEventServiceTest { Long inboundId, Long containerId, String publicationStatus, BigDecimal amount) { return new FirstlegFeeSourceClient.Snapshot(customerId, sourceType, 31, version, inboundId, - inboundId == null ? null : "IB-21", 51L, containerId, null, + inboundId == null ? null : "IB-21", 51L, containerId, containerId == null ? null : 61L, containerId == null ? null : "C-41", customerId, "客户", null, null, null, null, null, null, null, List.of(), 61L, "CUSTOMS", "清关费", "RECEIVABLE", LocalDate.of(2026, 8, 11), "CNY", amount, diff --git a/finance/src/test/java/com/baiyue/finance/PlatformFinanceControllerBehaviorTest.java b/finance/src/test/java/com/baiyue/finance/PlatformFinanceControllerBehaviorTest.java index bd04c047b..242a99628 100644 --- a/finance/src/test/java/com/baiyue/finance/PlatformFinanceControllerBehaviorTest.java +++ b/finance/src/test/java/com/baiyue/finance/PlatformFinanceControllerBehaviorTest.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -55,9 +56,13 @@ class PlatformFinanceControllerBehaviorTest { controller.cost(user, "cost-key", request); verify(snapshotClient).loadingPlan("internal-token", 10L, 88L); - verify(service).createCost(eq(10L), eq(7L), any(), eq(List.of( - new FinanceService.ContainerOrderVolume(101L, 1001L, 88L, 10L, new BigDecimal("1.25")), - new FinanceService.ContainerOrderVolume(102L, 1002L, 88L, 10L, new BigDecimal("2.75")))), eq("cost-key")); + org.mockito.ArgumentCaptor cost = + org.mockito.ArgumentCaptor.forClass(FinanceService.CostRequest.class); + verify(service).createCost(eq(10L), eq(7L), cost.capture(), eq(List.of( + new FinanceService.ContainerOrderVolume(101L, 1001L, 99L, 10L, new BigDecimal("1.25")), + new FinanceService.ContainerOrderVolume(102L, 1002L, 99L, 10L, new BigDecimal("2.75")))), eq("cost-key")); + assertThat(cost.getValue().containerId()).as("Finance persists the physical containerBookingId") + .isEqualTo(99L); } @Test