Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -46,6 +47,8 @@ public class TransferFeeChargeForLoansConfig {
private AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
@Autowired
private AccountTransfersWritePlatformService accountTransfersWritePlatformService;
@Autowired
private LoanChargeRepository loanChargeRepository;

@Bean
protected Step transferFeeChargeForLoansStep() {
Expand All @@ -62,6 +65,6 @@ public Job transferFeeChargeForLoansJob() {
@Bean
public TransferFeeChargeForLoansTasklet transferFeeChargeForLoansTasklet() {
return new TransferFeeChargeForLoansTasklet(loanChargeReadPlatformService, accountAssociationsReadPlatformService,
accountTransfersWritePlatformService);
accountTransfersWritePlatformService, loanChargeRepository);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 {
Expand All @@ -60,6 +65,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon
List<Throwable> 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<LoanInstallmentChargeData> chargePerInstallments = loanChargeReadPlatformService
.retrieveInstallmentLoanCharges(chargeData.getId(), true);
Expand All @@ -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);
Expand All @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -836,7 +849,19 @@ public Map<String, Object> 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();
Expand Down
Loading