优化
This commit is contained in:
+14
-1
@@ -366,7 +366,20 @@ public class FinancePhase1OperationService {
|
||||
private String json(Object value) { try { return objectMapper.writeValueAsString(value); } catch (Exception e) { throw error("FIN-DATA-500", "财务数据序列化失败"); } }
|
||||
private String hash(Object value) { try { return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(json(value).getBytes(StandardCharsets.UTF_8))); } catch (Exception e) { throw error("FIN-DATA-500", "幂等请求哈希失败"); } }
|
||||
private static String text(JsonNode node, String field) { JsonNode value = node.path(field); return value.isMissingNode() || value.isNull() ? null : value.asText(); }
|
||||
private static Long jsonLong(JsonNode node, String field) { JsonNode value = node.path(field); return value.isIntegralNumber() && value.asLong() > 0 ? value.asLong() : null; }
|
||||
private static Long jsonLong(JsonNode node, String field) {
|
||||
JsonNode value = node.path(field);
|
||||
if (value.isIntegralNumber() && value.canConvertToLong()) {
|
||||
long parsed = value.longValue();
|
||||
return parsed > 0 ? parsed : null;
|
||||
}
|
||||
if (!value.isTextual()) return null;
|
||||
try {
|
||||
long parsed = Long.parseLong(value.textValue());
|
||||
return parsed > 0 ? parsed : null;
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private static Long firstLong(Long first, Long second) { return first != null ? first : second; }
|
||||
private static String firstText(String first, String second) { return StringUtils.hasText(first) ? first : second; }
|
||||
private static String defaultText(JsonNode node, String field, String fallback) { String value = text(node, field); return value == null || value.isBlank() ? fallback : value; }
|
||||
|
||||
@@ -462,7 +462,9 @@ public class FinanceSettlementService {
|
||||
BigDecimal unitPrice = quantity == null || quantity.signum() == 0 ? null
|
||||
: amount.divide(quantity, 6, java.math.RoundingMode.HALF_UP);
|
||||
Long supplierId = jsonLong(snapshot, "supplierId");
|
||||
if (supplierId == null) throw error("FIN_409", "应付结算缺少供应商快照");
|
||||
if ("PAYABLE".equals(text(row, "direction")) && supplierId == null) {
|
||||
throw error("FIN_409", "应付结算缺少供应商快照");
|
||||
}
|
||||
return new SettlementLine(number(row, "fee_source_id").longValue(), text(row, "source_type"),
|
||||
number(row, "source_fee_id").longValue(), number(row, "source_fee_version").intValue(),
|
||||
text(row, "direction"), number(row, "customer_id").longValue(), jsonText(snapshot, "customerName"),
|
||||
@@ -594,7 +596,17 @@ public class FinanceSettlementService {
|
||||
}
|
||||
private static Long jsonLong(JsonNode node, String key) {
|
||||
JsonNode value = node.path(key);
|
||||
return value.isIntegralNumber() ? value.longValue() : null;
|
||||
if (value.isIntegralNumber() && value.canConvertToLong()) {
|
||||
long parsed = value.longValue();
|
||||
return parsed > 0 ? parsed : null;
|
||||
}
|
||||
if (!value.isTextual()) return null;
|
||||
try {
|
||||
long parsed = Long.parseLong(value.textValue());
|
||||
return parsed > 0 ? parsed : null;
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private static LocalDate jsonDate(JsonNode node, String key) {
|
||||
String value = jsonText(node, key);
|
||||
|
||||
@@ -144,7 +144,7 @@ class FinancePhase1OperationBehaviorTest {
|
||||
when(rs.getString("currency_code")).thenReturn("CNY");
|
||||
when(rs.getString("review_status")).thenReturn("APPROVED");
|
||||
when(rs.getString("effective_status")).thenReturn("AVAILABLE");
|
||||
when(rs.getString("source_snapshot")).thenReturn("{\"customerName\":\"客户9\",\"supplierId\":79,\"supplierName\":\"供应商A\",\"inboundCode\":\"IN-9\",\"containerNo\":\"CONT-9\",\"businessDate\":\"2026-08-11\",\"chargeableQuantity\":\"10\"}");
|
||||
when(rs.getString("source_snapshot")).thenReturn("{\"customerName\":\"客户9\",\"supplierId\":\"79\",\"supplierName\":\"供应商A\",\"inboundCode\":\"IN-9\",\"containerNo\":\"CONT-9\",\"businessDate\":\"2026-08-11\",\"chargeableQuantity\":\"10\"}");
|
||||
when(rs.getBigDecimal("amount")).thenReturn(new BigDecimal("120.00"));
|
||||
when(rs.getBoolean("statement_generated")).thenReturn(false);
|
||||
return List.of(mapper.mapRow(rs, 0));
|
||||
@@ -164,6 +164,7 @@ class FinancePhase1OperationBehaviorTest {
|
||||
assertThat(source.sourceBusinessId()).isEqualTo("IN-9");
|
||||
assertThat(source.inboundCode()).isEqualTo("IN-9");
|
||||
assertThat(source.containerNo()).isEqualTo("CONT-9");
|
||||
assertThat(source.supplierId()).isEqualTo(79L);
|
||||
assertThat(source.supplierName()).isEqualTo("供应商A");
|
||||
assertThat(source.reviewStatus()).isEqualTo("APPROVED");
|
||||
assertThat(source.businessDate()).isEqualTo(LocalDate.of(2026, 8, 11));
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.baiyue.finance.dto.FinanceSettlementDtos.CreatePayableSettlementReque
|
||||
import com.baiyue.finance.dto.FinanceSettlementDtos.CreateReceivableSettlementRequest;
|
||||
import com.baiyue.finance.dto.FinanceSettlementDtos.CreateSettlementPaymentRequest;
|
||||
import com.baiyue.finance.dto.FinanceSettlementDtos.SettlementDetail;
|
||||
import com.baiyue.finance.dto.FinanceSettlementDtos.SettlementLine;
|
||||
import com.baiyue.finance.dto.FinanceSettlementDtos.SettlementSummary;
|
||||
import com.baiyue.finance.dto.FinanceSettlementDtos.SettlementActionRequest;
|
||||
import com.baiyue.finance.service.FinanceEventException;
|
||||
@@ -286,6 +287,66 @@ class FinanceSettlementBehaviorTest {
|
||||
verify(jdbc).update(startsWith("INSERT INTO fin_settlement_line"), eq(88L), eq(10L), eq(501L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void receivableSettlementLineDoesNotRequireSupplierSnapshot() {
|
||||
Map<String, Object> row = new java.util.HashMap<>(Map.ofEntries(
|
||||
Map.entry("fee_source_id", 31L), Map.entry("source_type", "FBA_PRICING"),
|
||||
Map.entry("source_fee_id", 41L), Map.entry("source_fee_version", 1),
|
||||
Map.entry("direction", "RECEIVABLE"), Map.entry("customer_id", 10L),
|
||||
Map.entry("source_snapshot", "{\"customerName\":\"客户10\",\"businessDate\":\"2026-08-11\"}"),
|
||||
Map.entry("fee_item_name", "头程运费"), Map.entry("amount", new BigDecimal("100.00")),
|
||||
Map.entry("currency_code", "CNY"), Map.entry("review_status", "APPROVED"),
|
||||
Map.entry("status", "GENERATED")));
|
||||
row.put("order_id", null);
|
||||
row.put("inbound_id", null);
|
||||
row.put("container_id", null);
|
||||
|
||||
SettlementLine line = ReflectionTestUtils.invokeMethod(
|
||||
service(org.mockito.Mockito.mock(JdbcTemplate.class), org.mockito.Mockito.mock(FinanceFileClient.class)),
|
||||
"line", row, detail("RECEIVABLE", "ISSUED", BigDecimal.ZERO,
|
||||
new BigDecimal("100.00"), 0).summary());
|
||||
|
||||
assertThat(line.direction()).isEqualTo("RECEIVABLE");
|
||||
assertThat(line.supplierId()).isNull();
|
||||
assertThat(line.customerName()).isEqualTo("客户10");
|
||||
}
|
||||
|
||||
@Test
|
||||
void payableSettlementLineStillRequiresSupplierSnapshot() {
|
||||
Map<String, Object> row = new java.util.HashMap<>(Map.ofEntries(
|
||||
Map.entry("direction", "PAYABLE"),
|
||||
Map.entry("source_snapshot", "{\"customerName\":\"客户10\",\"businessDate\":\"2026-08-11\"}"),
|
||||
Map.entry("amount", new BigDecimal("100.00"))));
|
||||
|
||||
assertCode(() -> ReflectionTestUtils.invokeMethod(
|
||||
service(org.mockito.Mockito.mock(JdbcTemplate.class), org.mockito.Mockito.mock(FinanceFileClient.class)),
|
||||
"line", row, detail("PAYABLE", "ISSUED", BigDecimal.ZERO,
|
||||
new BigDecimal("100.00"), 0).summary()), "FIN_409");
|
||||
}
|
||||
|
||||
@Test
|
||||
void payableSettlementLineAcceptsStringEncodedSupplierIdSnapshot() {
|
||||
Map<String, Object> row = new java.util.HashMap<>(Map.ofEntries(
|
||||
Map.entry("fee_source_id", 31L), Map.entry("source_type", "INBOUND_FEE"),
|
||||
Map.entry("source_fee_id", 41L), Map.entry("source_fee_version", 1),
|
||||
Map.entry("direction", "PAYABLE"), Map.entry("customer_id", 10L),
|
||||
Map.entry("source_snapshot", "{\"customerName\":\"客户10\",\"supplierId\":\"2090349552523644929\",\"supplierName\":\"供应商A\",\"businessDate\":\"2026-08-11\"}"),
|
||||
Map.entry("fee_item_name", "装车费"), Map.entry("amount", new BigDecimal("80.00")),
|
||||
Map.entry("currency_code", "CNY"), Map.entry("review_status", "APPROVED"),
|
||||
Map.entry("status", "AVAILABLE")));
|
||||
row.put("order_id", null);
|
||||
row.put("inbound_id", null);
|
||||
row.put("container_id", null);
|
||||
|
||||
SettlementLine line = ReflectionTestUtils.invokeMethod(
|
||||
service(org.mockito.Mockito.mock(JdbcTemplate.class), org.mockito.Mockito.mock(FinanceFileClient.class)),
|
||||
"line", row, detail("PAYABLE", "ISSUED", BigDecimal.ZERO,
|
||||
new BigDecimal("80.00"), 0).summary());
|
||||
|
||||
assertThat(line.supplierId()).isEqualTo(2090349552523644929L);
|
||||
assertThat(line.supplierName()).isEqualTo("供应商A");
|
||||
}
|
||||
|
||||
@Test
|
||||
void receivableCreationRejectsAlreadySettledStatement() {
|
||||
JdbcTemplate jdbc = org.mockito.Mockito.mock(JdbcTemplate.class);
|
||||
|
||||
Reference in New Issue
Block a user