Merge remote-tracking branch 'origin/test' into dev
This commit is contained in:
+7
-2
@@ -43,8 +43,13 @@ public final class OverseasFeeSourceDtos {
|
||||
@Size(max = 128) String paymentReferenceNo,
|
||||
@Size(max = 20) List<@NotBlank String> paymentVoucherFileIds,
|
||||
@NotNull LocalDate businessDate,
|
||||
@NotBlank @Pattern(regexp = "CNY") String currency,
|
||||
@NotNull @DecimalMin(value = "0.00", inclusive = false) BigDecimal amount
|
||||
@NotBlank @Pattern(regexp = "CNY|USD") String currency,
|
||||
@NotNull @DecimalMin(value = "0.00", inclusive = false) BigDecimal amount,
|
||||
@DecimalMin(value = "0.00", inclusive = false) BigDecimal chargeableQuantity,
|
||||
@Size(max = 32) String quantityUnit,
|
||||
@DecimalMin(value = "0.00", inclusive = false) BigDecimal unitPrice,
|
||||
LocalDate periodStart,
|
||||
LocalDate periodEnd
|
||||
) { }
|
||||
|
||||
public record ActionRequest(@NotBlank @Size(max = 128) String idempotencyKey) { }
|
||||
|
||||
+9
@@ -46,6 +46,15 @@ public class OverseasFeeSource {
|
||||
private LocalDate businessDate;
|
||||
private String currency;
|
||||
private BigDecimal amount;
|
||||
private String originalCurrency;
|
||||
private BigDecimal originalAmount;
|
||||
private BigDecimal exchangeRate;
|
||||
private String exchangeRateMonth;
|
||||
private BigDecimal chargeableQuantity;
|
||||
private String quantityUnit;
|
||||
private BigDecimal unitPrice;
|
||||
private LocalDate periodStart;
|
||||
private LocalDate periodEnd;
|
||||
private String reviewStatus;
|
||||
private String publicationStatus;
|
||||
private String sourceStatus;
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.baiyue.lastleg.financefee.service;
|
||||
|
||||
import com.baiyue.lastleg.masterdata.exchange.entity.MonthlyExchangeRate;
|
||||
import com.baiyue.lastleg.masterdata.exchange.mapper.MonthlyExchangeRateMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class OverseasFeeCurrencyConverter {
|
||||
private static final DateTimeFormatter MONTH = DateTimeFormatter.ofPattern("yyyy-MM");
|
||||
private final MonthlyExchangeRateMapper rates;
|
||||
|
||||
public Conversion convert(String currency, BigDecimal amount, LocalDate businessDate) {
|
||||
String originalCurrency = currency == null ? null : currency.trim().toUpperCase();
|
||||
BigDecimal originalAmount = amount == null ? null : amount.setScale(2, RoundingMode.HALF_UP);
|
||||
if ("CNY".equals(originalCurrency)) {
|
||||
return new Conversion(originalCurrency, originalAmount, BigDecimal.ONE.setScale(6),
|
||||
businessDate.format(MONTH), originalAmount);
|
||||
}
|
||||
if (!"USD".equals(originalCurrency)) throw bad("海外费用原币只支持CNY或USD");
|
||||
String month = businessDate.format(MONTH);
|
||||
MonthlyExchangeRate rate = rates.findMonth(month);
|
||||
if (rate == null || !"ACTIVE".equals(rate.getStatus()) || rate.getUsdToCnyAverage() == null
|
||||
|| rate.getUsdToCnyAverage().signum() <= 0) {
|
||||
throw bad("业务月份缺少有效USD兑CNY汇率,不能发布财务费用");
|
||||
}
|
||||
BigDecimal exchangeRate = rate.getUsdToCnyAverage().setScale(6, RoundingMode.HALF_UP);
|
||||
return new Conversion(originalCurrency, originalAmount, exchangeRate, month,
|
||||
originalAmount.multiply(exchangeRate).setScale(2, RoundingMode.HALF_UP));
|
||||
}
|
||||
|
||||
private static ResponseStatusException bad(String message) {
|
||||
return new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
|
||||
}
|
||||
|
||||
public record Conversion(String originalCurrency, BigDecimal originalAmount,
|
||||
BigDecimal exchangeRate, String exchangeRateMonth,
|
||||
BigDecimal settlementAmount) { }
|
||||
}
|
||||
+4
-1
@@ -66,7 +66,10 @@ public class OverseasFeeOutboxService {
|
||||
text(value.getSupplierId()), value.getSupplierName(), value.getSupplierCategory(),
|
||||
value.getSettlementMode(), instant(value.getPaidAt()), value.getPaymentMethod(),
|
||||
value.getPaymentReferenceNo(), value.getPaymentVoucherFileIds() == null ? List.of() : value.getPaymentVoucherFileIds(),
|
||||
value.getBusinessDate(), value.getCurrency(), value.getAmount(), value.getReviewStatus(),
|
||||
value.getBusinessDate(), value.getCurrency(), value.getAmount(),
|
||||
value.getOriginalCurrency(), value.getOriginalAmount(), value.getExchangeRate(), value.getExchangeRateMonth(),
|
||||
value.getChargeableQuantity(), value.getQuantityUnit(), value.getUnitPrice(), value.getPeriodStart(), value.getPeriodEnd(),
|
||||
value.getReviewStatus(),
|
||||
"PUBLISHED", action == null && hash == null ? "ACTIVE" : value.getSourceStatus(), hash);
|
||||
}
|
||||
|
||||
|
||||
+15
-3
@@ -19,7 +19,6 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@@ -40,6 +39,7 @@ public class OverseasFeeSourceService {
|
||||
private final OverseasFeeCommandMapper commands;
|
||||
private final OverseasFeeBusinessGuard businessGuard;
|
||||
private final OverseasFeeOutboxService outbox;
|
||||
private final OverseasFeeCurrencyConverter currencyConverter;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public List<OverseasFeeSource> list(String customerId, String businessType, String businessNo,
|
||||
@@ -151,8 +151,14 @@ public class OverseasFeeSourceService {
|
||||
value.setDirection(request.direction()); value.setSupplierId(request.supplierId()); value.setSupplierName(text(request.supplierName()));
|
||||
value.setSupplierCategory(text(request.supplierCategory())); value.setSettlementMode(text(request.settlementMode()));
|
||||
value.setPaidAt(request.paidAt()); value.setPaymentMethod(text(request.paymentMethod())); value.setPaymentReferenceNo(text(request.paymentReferenceNo()));
|
||||
value.setPaymentVoucherFileIds(request.paymentVoucherFileIds()); value.setBusinessDate(request.businessDate()); value.setCurrency(request.currency());
|
||||
value.setAmount(request.amount().setScale(2, RoundingMode.HALF_UP)); value.setReviewStatus("DRAFT");
|
||||
value.setPaymentVoucherFileIds(request.paymentVoucherFileIds()); value.setBusinessDate(request.businessDate());
|
||||
OverseasFeeCurrencyConverter.Conversion conversion = currencyConverter.convert(request.currency(), request.amount(), request.businessDate());
|
||||
value.setCurrency("CNY"); value.setAmount(conversion.settlementAmount());
|
||||
value.setOriginalCurrency(conversion.originalCurrency()); value.setOriginalAmount(conversion.originalAmount());
|
||||
value.setExchangeRate(conversion.exchangeRate()); value.setExchangeRateMonth(conversion.exchangeRateMonth());
|
||||
value.setChargeableQuantity(request.chargeableQuantity()); value.setQuantityUnit(text(request.quantityUnit()));
|
||||
value.setUnitPrice(request.unitPrice()); value.setPeriodStart(request.periodStart()); value.setPeriodEnd(request.periodEnd());
|
||||
value.setReviewStatus("DRAFT");
|
||||
value.setPublicationStatus("UNPUBLISHED"); value.setSourceStatus("ACTIVE"); value.setCreatedBy(operatorId); value.setUpdatedBy(operatorId);
|
||||
value.setCreatedAt(now()); value.setUpdatedAt(now());
|
||||
return value;
|
||||
@@ -172,6 +178,12 @@ public class OverseasFeeSourceService {
|
||||
if ("OVERSEAS_CONTAINER".equals(value.businessType())
|
||||
&& !OVERSEAS_CONTAINER_FEE_ITEMS.contains(value.feeItemCode().trim()))
|
||||
throw bad("海外货柜费用项目不受支持");
|
||||
if ((value.periodStart() == null) != (value.periodEnd() == null)
|
||||
|| value.periodStart() != null && value.periodStart().isAfter(value.periodEnd()))
|
||||
throw bad("计费期间必须同时填写且开始日期不能晚于结束日期");
|
||||
if ((value.chargeableQuantity() == null) != (text(value.quantityUnit()) == null)
|
||||
|| value.unitPrice() != null && value.chargeableQuantity() == null)
|
||||
throw bad("计费数量、单位和单价信息不完整");
|
||||
boolean payable = "PAYABLE".equals(value.direction());
|
||||
boolean files = value.paymentVoucherFileIds() != null && !value.paymentVoucherFileIds().isEmpty();
|
||||
if (!payable) {
|
||||
|
||||
+4
@@ -83,6 +83,10 @@ public class DestuffingPalletCostService {
|
||||
v.setBusinessId(costId); v.setBusinessNo("DESTUFF-"+bookingId); v.setContainerBookingId(parseLong(bookingId)); v.setContainerNo(containerNo);
|
||||
v.setWarehouseId(warehouseId); v.setFeeItemCode("DESTUFF_PALLET"); v.setFeeItemName("拆柜打托费"); v.setDirection("RECEIVABLE");
|
||||
v.setPaymentVoucherFileIds(List.of()); v.setBusinessDate(LocalDate.now()); v.setCurrency("CNY"); v.setAmount(amount);
|
||||
v.setOriginalCurrency("CNY"); v.setOriginalAmount(amount); v.setExchangeRate(BigDecimal.ONE.setScale(6));
|
||||
v.setExchangeRateMonth(LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM")));
|
||||
v.setChargeableQuantity(volume.volume()); v.setQuantityUnit("CBM");
|
||||
v.setUnitPrice(volume.volume().signum() > 0 ? amount.divide(volume.volume(), 6, RoundingMode.HALF_UP) : null);
|
||||
v.setReviewStatus("APPROVED"); v.setPublicationStatus("PUBLISHED"); v.setSourceStatus("ACTIVE"); v.setReviewerId(operator);
|
||||
v.setReviewedAt(now); v.setCreatedBy(operator); v.setUpdatedBy(operator); v.setCreatedAt(now); v.setUpdatedAt(now); return v;
|
||||
}
|
||||
|
||||
@@ -832,8 +832,13 @@ CREATE TABLE IF NOT EXISTS ll_finance_fee_source (
|
||||
supplier_category VARCHAR(32) NULL COMMENT '应付供应商类别约束', settlement_mode VARCHAR(16) NULL COMMENT 'ON_ACCOUNT账期应付或DIRECT_PAID业务已付',
|
||||
paid_at DATETIME(3) NULL COMMENT '直接已付时间', payment_method VARCHAR(32) NULL COMMENT '直接已付方式',
|
||||
payment_reference_no VARCHAR(128) NULL COMMENT '直接已付流水号', payment_voucher_file_ids JSON NULL COMMENT '直接已付凭证文件ID快照',
|
||||
business_date DATE NOT NULL COMMENT '费用业务日期', currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '财务结算币种,当前固定CNY',
|
||||
amount DECIMAL(20,6) NOT NULL COMMENT '费用金额', review_status VARCHAR(16) NOT NULL COMMENT 'DRAFT、PENDING、APPROVED或REJECTED',
|
||||
business_date DATE NOT NULL COMMENT '费用业务日期', currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '财务结算币种,固定CNY',
|
||||
amount DECIMAL(20,6) NOT NULL COMMENT '人民币结算金额', original_currency CHAR(3) NOT NULL COMMENT '业务原币种,支持CNY或USD',
|
||||
original_amount DECIMAL(20,6) NOT NULL COMMENT '业务原币金额', exchange_rate DECIMAL(20,6) NOT NULL COMMENT '原币折算人民币汇率,人民币固定为1',
|
||||
exchange_rate_month CHAR(7) NOT NULL COMMENT '汇率月份yyyy-MM', chargeable_quantity DECIMAL(20,6) NULL COMMENT '计费数量,如箱数、托数、体积或天数',
|
||||
quantity_unit VARCHAR(32) NULL COMMENT '计费数量单位', unit_price DECIMAL(20,6) NULL COMMENT '原币计费单价',
|
||||
period_start DATE NULL COMMENT '仓租等期间费用开始日期', period_end DATE NULL COMMENT '仓租等期间费用结束日期',
|
||||
review_status VARCHAR(16) NOT NULL COMMENT 'DRAFT、PENDING、APPROVED或REJECTED',
|
||||
publication_status VARCHAR(16) NOT NULL COMMENT 'UNPUBLISHED或PUBLISHED', source_status VARCHAR(16) NOT NULL COMMENT 'ACTIVE、SUPERSEDED或VOIDED',
|
||||
review_reason VARCHAR(500) NULL COMMENT '审核或作废原因', reviewer_id VARCHAR(128) NULL COMMENT '审核人ID', reviewed_at DATETIME(3) NULL COMMENT '审核时间',
|
||||
created_by VARCHAR(128) NOT NULL COMMENT '创建人ID', updated_by VARCHAR(128) NOT NULL COMMENT '最后更新人ID',
|
||||
@@ -842,6 +847,8 @@ CREATE TABLE IF NOT EXISTS ll_finance_fee_source (
|
||||
KEY idx_ll_finance_fee_customer(customer_id,source_status,created_at), KEY idx_ll_finance_fee_business(business_type,business_no,created_at),
|
||||
KEY idx_ll_finance_fee_container_customer(container_booking_id,customer_id,source_status,created_at),
|
||||
CONSTRAINT chk_ll_finance_fee_amount CHECK(amount>0), CONSTRAINT chk_ll_finance_fee_currency CHECK(currency='CNY'),
|
||||
CONSTRAINT chk_ll_finance_fee_original_currency CHECK(original_currency IN('CNY','USD')),
|
||||
CONSTRAINT chk_ll_finance_fee_period CHECK((period_start IS NULL AND period_end IS NULL) OR (period_start IS NOT NULL AND period_end IS NOT NULL AND period_start<=period_end)),
|
||||
CONSTRAINT chk_ll_finance_fee_direction CHECK(direction IN('RECEIVABLE','PAYABLE')),
|
||||
CONSTRAINT chk_ll_finance_fee_review CHECK(review_status IN('DRAFT','PENDING','APPROVED','REJECTED')),
|
||||
CONSTRAINT chk_ll_finance_fee_publication CHECK(publication_status IN('UNPUBLISHED','PUBLISHED')),
|
||||
|
||||
@@ -85,6 +85,11 @@ components:
|
||||
feeItemName: { type: string }
|
||||
direction: { type: string, enum: [RECEIVABLE, PAYABLE] }
|
||||
businessDate: { type: string, format: date }
|
||||
currency: { type: string, enum: [CNY] }
|
||||
amount: { type: number, format: decimal, minimum: 0, exclusiveMinimum: true }
|
||||
currency: { type: string, enum: [CNY, USD], description: 业务原币;USD由服务端按业务月份有效汇率折算为CNY入池 }
|
||||
amount: { type: number, format: decimal, minimum: 0, exclusiveMinimum: true, description: 业务原币金额 }
|
||||
chargeableQuantity: { type: number, format: decimal, minimum: 0, exclusiveMinimum: true, nullable: true }
|
||||
quantityUnit: { type: string, maxLength: 32, nullable: true }
|
||||
unitPrice: { type: number, format: decimal, minimum: 0, exclusiveMinimum: true, nullable: true }
|
||||
periodStart: { type: string, format: date, nullable: true }
|
||||
periodEnd: { type: string, format: date, nullable: true }
|
||||
securitySchemes: { bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT } }
|
||||
|
||||
+6
-2
@@ -4,6 +4,7 @@ import com.baiyue.lastleg.financefee.dto.OverseasFeeSourceDtos.SaveRequest;
|
||||
import com.baiyue.lastleg.financefee.entity.OverseasFeeSource;
|
||||
import com.baiyue.lastleg.financefee.mapper.OverseasFeeCommandMapper;
|
||||
import com.baiyue.lastleg.financefee.mapper.OverseasFeeSourceMapper;
|
||||
import com.baiyue.lastleg.masterdata.exchange.mapper.MonthlyExchangeRateMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -22,7 +23,9 @@ class OverseasContainerFeeSourceServiceTest {
|
||||
private final OverseasFeeBusinessGuard guard = mock(OverseasFeeBusinessGuard.class);
|
||||
private final OverseasFeeOutboxService outbox = mock(OverseasFeeOutboxService.class);
|
||||
private final OverseasFeeSourceService service = new OverseasFeeSourceService(
|
||||
mapper, commands, guard, outbox, new ObjectMapper().findAndRegisterModules());
|
||||
mapper, commands, guard, outbox,
|
||||
new OverseasFeeCurrencyConverter(mock(MonthlyExchangeRateMapper.class)),
|
||||
new ObjectMapper().findAndRegisterModules());
|
||||
|
||||
@Test
|
||||
void createsContainerFeeUsingTheRealContainerAsItsStableBusinessReference() {
|
||||
@@ -50,7 +53,8 @@ class OverseasContainerFeeSourceServiceTest {
|
||||
source.warehouseId(), "CUSTOM_FEE", "自定义费", source.direction(), source.supplierId(),
|
||||
source.supplierName(), source.supplierCategory(), source.settlementMode(), source.paidAt(),
|
||||
source.paymentMethod(), source.paymentReferenceNo(), source.paymentVoucherFileIds(),
|
||||
source.businessDate(), source.currency(), source.amount());
|
||||
source.businessDate(), source.currency(), source.amount(), source.chargeableQuantity(),
|
||||
source.quantityUnit(), source.unitPrice(), source.periodStart(), source.periodEnd());
|
||||
when(commands.find("operator-a", "CREATE", "bad-container-fee")).thenReturn(null);
|
||||
|
||||
assertThatThrownBy(() -> service.create("operator-a", "bad-container-fee", request))
|
||||
|
||||
+4
-2
@@ -57,7 +57,8 @@ class OverseasFeeBusinessGuardTest {
|
||||
source.warehouseId(), source.feeItemCode(), source.feeItemName(), source.direction(), source.supplierId(),
|
||||
source.supplierName(), source.supplierCategory(), source.settlementMode(), source.paidAt(),
|
||||
source.paymentMethod(), source.paymentReferenceNo(), source.paymentVoucherFileIds(),
|
||||
source.businessDate(), source.currency(), source.amount());
|
||||
source.businessDate(), source.currency(), source.amount(), source.chargeableQuantity(),
|
||||
source.quantityUnit(), source.unitPrice(), source.periodStart(), source.periodEnd());
|
||||
StubJdbc jdbc = new StubJdbc();
|
||||
|
||||
assertThatThrownBy(() -> new OverseasFeeBusinessGuard(jdbc).requireOwned(inconsistent))
|
||||
@@ -83,7 +84,8 @@ class OverseasFeeBusinessGuardTest {
|
||||
return new SaveRequest(customerId, "客户A", "OVERSEAS_CONTAINER_FEE", "OVERSEAS_CONTAINER",
|
||||
bookingId, containerNo, null, null, null, null, bookingId, containerNo, 91L,
|
||||
"PORT_FEE", "港口费", "RECEIVABLE", null, null, null, null, null, null, null,
|
||||
List.of(), LocalDate.of(2026, 9, 8), "CNY", new BigDecimal("88.00"));
|
||||
List.of(), LocalDate.of(2026, 9, 8), "CNY", new BigDecimal("88.00"),
|
||||
null, null, null, null, null);
|
||||
}
|
||||
|
||||
private static final class StubJdbc extends JdbcTemplate {
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.baiyue.lastleg.financefee.service;
|
||||
|
||||
import com.baiyue.lastleg.masterdata.exchange.entity.MonthlyExchangeRate;
|
||||
import com.baiyue.lastleg.masterdata.exchange.mapper.MonthlyExchangeRateMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
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.when;
|
||||
|
||||
class OverseasFeeCurrencyConverterTest {
|
||||
private final MonthlyExchangeRateMapper rates = mock(MonthlyExchangeRateMapper.class);
|
||||
private final OverseasFeeCurrencyConverter converter = new OverseasFeeCurrencyConverter(rates);
|
||||
|
||||
@Test
|
||||
void convertsUsdUsingTheActiveRateForTheBusinessMonth() {
|
||||
MonthlyExchangeRate rate = new MonthlyExchangeRate();
|
||||
rate.setStatus("ACTIVE"); rate.setUsdToCnyAverage(new BigDecimal("7.120000"));
|
||||
when(rates.findMonth("2026-09")).thenReturn(rate);
|
||||
|
||||
var result = converter.convert("USD", new BigDecimal("12.34"), LocalDate.of(2026, 9, 9));
|
||||
|
||||
assertThat(result.originalAmount()).isEqualByComparingTo("12.34");
|
||||
assertThat(result.exchangeRate()).isEqualByComparingTo("7.120000");
|
||||
assertThat(result.settlementAmount()).isEqualByComparingTo("87.86");
|
||||
}
|
||||
|
||||
@Test
|
||||
void refusesUsdWhenTheBusinessMonthHasNoActiveRate() {
|
||||
assertThatThrownBy(() -> converter.convert("USD", BigDecimal.TEN, LocalDate.of(2026, 9, 9)))
|
||||
.hasMessageContaining("缺少有效USD兑CNY汇率");
|
||||
}
|
||||
}
|
||||
+4
@@ -38,6 +38,8 @@ class OverseasFeeOutboxServiceTest {
|
||||
source.setFeeItemCode("PROCUREMENT_SERVICE"); source.setFeeItemName("采购服务费");
|
||||
source.setDirection("RECEIVABLE"); source.setBusinessDate(LocalDate.of(2026, 9, 2));
|
||||
source.setCurrency("CNY"); source.setAmount(new BigDecimal("120.00"));
|
||||
source.setOriginalCurrency("USD"); source.setOriginalAmount(new BigDecimal("16.85"));
|
||||
source.setExchangeRate(new BigDecimal("7.122000")); source.setExchangeRateMonth("2026-09");
|
||||
source.setReviewStatus("APPROVED"); source.setPublicationStatus("PUBLISHED");
|
||||
source.setSourceStatus("ACTIVE"); source.setPaymentVoucherFileIds(List.of());
|
||||
|
||||
@@ -52,5 +54,7 @@ class OverseasFeeOutboxServiceTest {
|
||||
assertThat(payload.inboundId()).isNull();
|
||||
assertThat(payload.inboundCode()).isNull();
|
||||
assertThat(payload.sourceHash()).startsWith("sha256:").hasSize(71);
|
||||
assertThat(payload.originalCurrency()).isEqualTo("USD");
|
||||
assertThat(payload.exchangeRateMonth()).isEqualTo("2026-09");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user