diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 0b7814337c4..c4988a4a802 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -118,6 +118,7 @@ import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksStatus; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; +import org.apache.fineract.portfolio.tax.service.TaxUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -448,10 +449,22 @@ private void handlePayDisbursementTransaction(final Loan loan, final Long charge charge = loanCharge; } } - final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount(), null); + if (charge != null && charge.getCharge().getTaxGroup() != null && log.isInfoEnabled()) { + log.info( + "Repayment-at-disbursement charge tax evaluation: loanId={}, loanChargeId={}, txDate={}, baseAmount={}, applicableTaxComponents={}", + loan.getId(), charge.getId(), chargesPayment.getTransactionDate(), charge.amountOutstanding(), + TaxUtils.getApplicableTaxComponentSummaries(charge.getCharge().getTaxGroup(), chargesPayment.getTransactionDate())); + } + final BigDecimal chargeAmountWithTax = TaxUtils.calculateChargeAmountWithTax(charge.amountOutstanding(), + charge.getCharge().getTaxGroup(), chargesPayment.getTransactionDate(), loan.getCurrency().getDigitsAfterDecimal()); + if (charge != null && charge.getCharge().getTaxGroup() != null && log.isInfoEnabled()) { + log.info("Repayment-at-disbursement charge tax result: loanId={}, loanChargeId={}, txDate={}, amountAfterTax={}", loan.getId(), + charge.getId(), chargesPayment.getTransactionDate(), chargeAmountWithTax); + } + final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, chargeAmountWithTax, null); chargesPayment.getLoanChargesPaid().add(loanChargePaidBy); final Money zero = Money.zero(loan.getCurrency()); - chargesPayment.updateComponents(zero, zero, charge.getAmount(loan.getCurrency()), zero); + chargesPayment.updateComponents(zero, zero, Money.of(loan.getCurrency(), chargeAmountWithTax), zero); chargesPayment.updateLoan(loan); loan.addLoanTransaction(chargesPayment); loanBalanceService.updateLoanOutstandingBalances(loan); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/transferfeechargeforloans/TransferFeeChargeForLoansConfig.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/transferfeechargeforloans/TransferFeeChargeForLoansConfig.java index 76d3fc9cc8a..b45f8392512 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/transferfeechargeforloans/TransferFeeChargeForLoansConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/transferfeechargeforloans/TransferFeeChargeForLoansConfig.java @@ -21,6 +21,7 @@ import org.apache.fineract.infrastructure.jobs.service.JobName; import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService; import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; @@ -46,6 +47,8 @@ public class TransferFeeChargeForLoansConfig { private AccountAssociationsReadPlatformService accountAssociationsReadPlatformService; @Autowired private AccountTransfersWritePlatformService accountTransfersWritePlatformService; + @Autowired + private LoanChargeRepository loanChargeRepository; @Bean protected Step transferFeeChargeForLoansStep() { @@ -62,6 +65,6 @@ public Job transferFeeChargeForLoansJob() { @Bean public TransferFeeChargeForLoansTasklet transferFeeChargeForLoansTasklet() { return new TransferFeeChargeForLoansTasklet(loanChargeReadPlatformService, accountAssociationsReadPlatformService, - accountTransfersWritePlatformService); + accountTransfersWritePlatformService, loanChargeRepository); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/transferfeechargeforloans/TransferFeeChargeForLoansTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/transferfeechargeforloans/TransferFeeChargeForLoansTasklet.java index b0c30566979..0e63dbb4d9a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/transferfeechargeforloans/TransferFeeChargeForLoansTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/transferfeechargeforloans/TransferFeeChargeForLoansTasklet.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.jobs.transferfeechargeforloans; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -35,10 +36,13 @@ import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode; import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData; import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService; import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException; +import org.apache.fineract.portfolio.tax.service.TaxUtils; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; @@ -51,6 +55,7 @@ public class TransferFeeChargeForLoansTasklet implements Tasklet { private final LoanChargeReadPlatformService loanChargeReadPlatformService; private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService; private final AccountTransfersWritePlatformService accountTransfersWritePlatformService; + private final LoanChargeRepository loanChargeRepository; @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { @@ -60,6 +65,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon List errors = new ArrayList<>(); if (chargeDatas != null) { for (final LoanChargeData chargeData : chargeDatas) { + final LoanCharge loanCharge = loanChargeRepository.findById(chargeData.getId()).orElse(null); if (chargeData.isInstallmentFee()) { final Collection chargePerInstallments = loanChargeReadPlatformService .retrieveInstallmentLoanCharges(chargeData.getId(), true); @@ -78,9 +84,27 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon break; } final boolean isExceptionForBalanceCheck = false; + BigDecimal amountWithTax = installmentChargeData.getAmountOutstanding(); + if (loanCharge != null) { + if (loanCharge.getCharge().getTaxGroup() != null && log.isInfoEnabled()) { + log.info( + "Scheduled charge payment tax evaluation: loanId={}, loanChargeId={}, installmentNumber={}, txDate={}, baseAmount={}, applicableTaxComponents={}", + chargeData.getLoanId(), chargeData.getId(), installmentChargeData.getInstallmentNumber(), + DateUtils.getBusinessLocalDate(), amountWithTax, TaxUtils.getApplicableTaxComponentSummaries( + loanCharge.getCharge().getTaxGroup(), DateUtils.getBusinessLocalDate())); + } + amountWithTax = TaxUtils.calculateChargeAmountWithTax(amountWithTax, loanCharge.getCharge().getTaxGroup(), + DateUtils.getBusinessLocalDate(), loanCharge.getLoan().getCurrency().getDigitsAfterDecimal()); + if (loanCharge.getCharge().getTaxGroup() != null && log.isInfoEnabled()) { + log.info( + "Scheduled charge payment tax result: loanId={}, loanChargeId={}, installmentNumber={}, txDate={}, amountAfterTax={}", + chargeData.getLoanId(), chargeData.getId(), installmentChargeData.getInstallmentNumber(), + DateUtils.getBusinessLocalDate(), amountWithTax); + } + } final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(DateUtils.getBusinessLocalDate(), - installmentChargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, - portfolioAccountData.getId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null, + amountWithTax, PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, portfolioAccountData.getId(), + chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null, LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), installmentChargeData.getInstallmentNumber(), AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, ExternalId.empty(), null, null, null, isRegularTransaction, isExceptionForBalanceCheck); @@ -98,12 +122,28 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon continue; } final boolean isExceptionForBalanceCheck = false; - final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(DateUtils.getBusinessLocalDate(), - chargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, - portfolioAccountData.getId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null, - LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), null, - AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, ExternalId.empty(), null, null, null, - isRegularTransaction, isExceptionForBalanceCheck); + BigDecimal amountWithTax = chargeData.getAmountOutstanding(); + if (loanCharge != null) { + if (loanCharge.getCharge().getTaxGroup() != null && log.isInfoEnabled()) { + log.info( + "Scheduled charge payment tax evaluation: loanId={}, loanChargeId={}, installmentNumber={}, txDate={}, baseAmount={}, applicableTaxComponents={}", + chargeData.getLoanId(), chargeData.getId(), null, DateUtils.getBusinessLocalDate(), amountWithTax, + TaxUtils.getApplicableTaxComponentSummaries(loanCharge.getCharge().getTaxGroup(), + DateUtils.getBusinessLocalDate())); + } + amountWithTax = TaxUtils.calculateChargeAmountWithTax(amountWithTax, loanCharge.getCharge().getTaxGroup(), + DateUtils.getBusinessLocalDate(), loanCharge.getLoan().getCurrency().getDigitsAfterDecimal()); + if (loanCharge.getCharge().getTaxGroup() != null && log.isInfoEnabled()) { + log.info( + "Scheduled charge payment tax result: loanId={}, loanChargeId={}, installmentNumber={}, txDate={}, amountAfterTax={}", + chargeData.getLoanId(), chargeData.getId(), null, DateUtils.getBusinessLocalDate(), amountWithTax); + } + } + final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(DateUtils.getBusinessLocalDate(), amountWithTax, + PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, portfolioAccountData.getId(), chargeData.getLoanId(), + "Loan Charge Payment", null, null, null, null, LoanTransactionType.CHARGE_PAYMENT.getValue(), + chargeData.getId(), null, AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, ExternalId.empty(), null, + null, null, isRegularTransaction, isExceptionForBalanceCheck); transferFeeCharge(accountTransferDTO, errors); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java index d9523f2d00e..021adf45690 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java @@ -135,6 +135,8 @@ import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; +import org.apache.fineract.portfolio.tax.service.TaxUtils; import org.springframework.transaction.annotation.Transactional; @Slf4j @@ -667,6 +669,23 @@ public CommandProcessingResult payLoanCharge(final Long loanId, Long loanChargeI amount = chargePerInstallment.getAmountOutstanding(); } + // Loan-charge payments should transfer the tax-inclusive amount, the same way disbursement-time + // charge collection and charge application transactions do. + final LocalDate effectiveTransactionDate = transactionDate != null ? transactionDate : DateUtils.getBusinessLocalDate(); + final TaxGroup taxGroup = loanCharge.getCharge().getTaxGroup(); + if (taxGroup != null && log.isInfoEnabled()) { + log.info( + "Charge payment tax evaluation: loanId={}, loanChargeId={}, installmentNumber={}, txDate={}, baseAmount={}, applicableTaxComponents={}", + loanId, loanChargeId, loanInstallmentNumber, effectiveTransactionDate, amount, + TaxUtils.getApplicableTaxComponentSummaries(taxGroup, effectiveTransactionDate)); + } + amount = TaxUtils.calculateChargeAmountWithTax(amount, taxGroup, effectiveTransactionDate, + loan.getCurrency().getDigitsAfterDecimal()); + if (taxGroup != null && log.isInfoEnabled()) { + log.info("Charge payment tax result: loanId={}, loanChargeId={}, installmentNumber={}, txDate={}, amountAfterTax={}", loanId, + loanChargeId, loanInstallmentNumber, effectiveTransactionDate, amount); + } + final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId); if (portfolioAccountData == null) { final String errorMessage = "Charge with id:" + loanChargeId + " requires linked savings account for payment"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 72335613cbe..85daa849313 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -223,6 +223,7 @@ import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; +import org.apache.fineract.portfolio.tax.service.TaxUtils; import org.apache.fineract.portfolio.transfer.api.TransferApiConstants; import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.dao.DataIntegrityViolationException; @@ -487,7 +488,19 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer() && loanCharge.isChargePending()) { - disBuLoanCharges.put(loanCharge.getId(), loanCharge.amountOutstanding()); + if (loanCharge.getCharge().getTaxGroup() != null && log.isInfoEnabled()) { + log.info( + "Disbursement charge tax evaluation: loanId={}, loanChargeId={}, txDate={}, baseAmount={}, applicableTaxComponents={}", + loanId, loanCharge.getId(), actualDisbursementDate, loanCharge.amountOutstanding(), + TaxUtils.getApplicableTaxComponentSummaries(loanCharge.getCharge().getTaxGroup(), actualDisbursementDate)); + } + final BigDecimal chargeAmountWithTax = TaxUtils.calculateChargeAmountWithTax(loanCharge.amountOutstanding(), + loanCharge.getCharge().getTaxGroup(), actualDisbursementDate, loan.getCurrency().getDigitsAfterDecimal()); + if (loanCharge.getCharge().getTaxGroup() != null && log.isInfoEnabled()) { + log.info("Disbursement charge tax result: loanId={}, loanChargeId={}, txDate={}, amountAfterTax={}", loanId, + loanCharge.getId(), actualDisbursementDate, chargeAmountWithTax); + } + disBuLoanCharges.put(loanCharge.getId(), chargeAmountWithTax); } } @@ -836,7 +849,19 @@ public Map bulkLoanDisbursal(final JsonCommand command, final Co for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer() && loanCharge.isChargePending()) { - disBuLoanCharges.put(loanCharge.getId(), loanCharge.amountOutstanding()); + if (loanCharge.getCharge().getTaxGroup() != null && log.isInfoEnabled()) { + log.info( + "Disbursement charge tax evaluation: loanId={}, loanChargeId={}, txDate={}, baseAmount={}, applicableTaxComponents={}", + loan.getId(), loanCharge.getId(), actualDisbursementDate, loanCharge.amountOutstanding(), + TaxUtils.getApplicableTaxComponentSummaries(loanCharge.getCharge().getTaxGroup(), actualDisbursementDate)); + } + final BigDecimal chargeAmountWithTax = TaxUtils.calculateChargeAmountWithTax(loanCharge.amountOutstanding(), + loanCharge.getCharge().getTaxGroup(), actualDisbursementDate, loan.getCurrency().getDigitsAfterDecimal()); + if (loanCharge.getCharge().getTaxGroup() != null && log.isInfoEnabled()) { + log.info("Disbursement charge tax result: loanId={}, loanChargeId={}, txDate={}, amountAfterTax={}", loan.getId(), + loanCharge.getId(), actualDisbursementDate, chargeAmountWithTax); + } + disBuLoanCharges.put(loanCharge.getId(), chargeAmountWithTax); } } final Locale locale = command.extractLocale(); diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java index 26ac217f990..79408a09932 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImplTest.java @@ -65,6 +65,7 @@ import org.apache.fineract.portfolio.account.data.PortfolioAccountData; import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService; import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; +import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode; import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; @@ -352,6 +353,8 @@ public void disburseLoan_withAccountTransferDisbursementCharge_shouldRefreshLoan // Charges with this mode are collected from the linked savings account via transferFunds(). // After that loop, the LoanSummary must be refreshed — that's the bug fix under test. LoanCharge disbursementCharge = mock(LoanCharge.class); + Charge charge = mock(Charge.class); + when(disbursementCharge.getCharge()).thenReturn(charge); when(disbursementCharge.isDueAtDisbursement()).thenReturn(true); when(disbursementCharge.getChargePaymentMode()).thenReturn(ChargePaymentMode.ACCOUNT_TRANSFER); when(disbursementCharge.isChargePending()).thenReturn(true); @@ -436,6 +439,8 @@ public void disburseLoan_withAccountTransferDisbursementChargeButNoLinkedAccount // A disbursement charge payable by ACCOUNT_TRANSFER — requires a linked savings account. LoanCharge disbursementCharge = mock(LoanCharge.class); + Charge charge = mock(Charge.class); + when(disbursementCharge.getCharge()).thenReturn(charge); when(disbursementCharge.isDueAtDisbursement()).thenReturn(true); when(disbursementCharge.getChargePaymentMode()).thenReturn(ChargePaymentMode.ACCOUNT_TRANSFER); when(disbursementCharge.isChargePending()).thenReturn(true); diff --git a/fineract-tax/src/main/java/org/apache/fineract/portfolio/tax/service/TaxUtils.java b/fineract-tax/src/main/java/org/apache/fineract/portfolio/tax/service/TaxUtils.java index ee5b0cd1052..871164f70a5 100644 --- a/fineract-tax/src/main/java/org/apache/fineract/portfolio/tax/service/TaxUtils.java +++ b/fineract-tax/src/main/java/org/apache/fineract/portfolio/tax/service/TaxUtils.java @@ -20,14 +20,17 @@ import java.math.BigDecimal; import java.time.LocalDate; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.tax.data.TaxComponentData; import org.apache.fineract.portfolio.tax.data.TaxGroupMappingsData; import org.apache.fineract.portfolio.tax.domain.TaxComponent; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; import org.apache.fineract.portfolio.tax.domain.TaxGroupMappings; public final class TaxUtils { @@ -126,4 +129,50 @@ public static BigDecimal addTax(final BigDecimal amount, final LocalDate date, f } return totalAmount; } + + public static BigDecimal calculateChargeAmountWithTax(final BigDecimal baseAmount, final TaxGroup taxGroup, + final LocalDate transactionDate, final int scale) { + if (baseAmount == null) { + return null; + } + if (taxGroup == null) { + return baseAmount; + } + final Set taxGroupMappings = taxGroup.getTaxGroupMappings(); + if (taxGroupMappings == null || taxGroupMappings.isEmpty()) { + return baseAmount; + } + final List mappingsList = new ArrayList<>(taxGroupMappings); + final BigDecimal totalAmount = addTax(baseAmount, transactionDate, mappingsList, scale); + return totalAmount != null ? totalAmount : baseAmount; + } + + /** + * Returns applicable tax components (name, id, percentage) for a transaction date after filtering by mapping + * start/end date and effective occurrence rules. + */ + public static List getApplicableTaxComponentSummaries(final TaxGroup taxGroup, final LocalDate transactionDate) { + if (taxGroup == null || transactionDate == null || taxGroup.getTaxGroupMappings() == null + || taxGroup.getTaxGroupMappings().isEmpty()) { + return List.of(); + } + return taxGroup.getTaxGroupMappings().stream().filter(mapping -> isApplicableOnDate(mapping, transactionDate)).map(mapping -> { + final TaxComponent component = mapping.getTaxComponent(); + final BigDecimal percentage = component != null ? component.getApplicablePercentage(transactionDate) : null; + if (component == null || percentage == null) { + return null; + } + return "id=" + component.getId() + ",name=" + component.getName() + ",percentage=" + percentage.toPlainString(); + }).filter(value -> value != null && !value.isBlank()).collect(Collectors.toList()); + } + + private static boolean isApplicableOnDate(final TaxGroupMappings groupMappings, final LocalDate transactionDate) { + if (groupMappings.startDate() != null && transactionDate.isBefore(groupMappings.startDate())) { + return false; + } + if (groupMappings.endDate() != null && transactionDate.isAfter(groupMappings.endDate())) { + return false; + } + return groupMappings.occursOnDayFromAndUpToAndIncluding(transactionDate); + } }