fix(finance): key container finance by booking

This commit is contained in:
2026-09-08 21:27:23 +08:00
parent 55c36cf290
commit 1cf6722d6e
13 changed files with 99 additions and 50 deletions
@@ -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");
}
}
@@ -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("${");
}
}
@@ -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"));
@@ -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<Object[]> 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<Object[]> 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<Object[]> 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<Object[]> 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,
@@ -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<FinanceService.CostRequest> 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