overdue charge lock issue fix

This commit is contained in:
Pramod Nuthakki
2015-03-19 13:28:06 +05:30
parent 44e08dd2e6
commit 9813e097c3
3 changed files with 112 additions and 106 deletions
@@ -3652,10 +3652,10 @@ public class ClientLoanIntegrationTest {
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "252.89");
addRepaymentValues(expectedvalues, todaysDate, 1, "2492.89", "36.02", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2517.23", "11.68", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "252.89");
addRepaymentValues(expectedvalues, todaysDate, 1, "2493.05", "35.86", "0.0", "252.89");
addRepaymentValues(expectedvalues, todaysDate, 1, "2491.72", "37.19", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2517.22", "11.69", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
repaymentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
@@ -3667,10 +3667,10 @@ public class ClientLoanIntegrationTest {
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "252.89");
addRepaymentValues(expectedvalues, todaysDate, 1, "2498.39", "30.52", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2517.26", "11.65", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "252.89");
addRepaymentValues(expectedvalues, todaysDate, 1, "2493.05", "35.86", "0.0", "252.89");
addRepaymentValues(expectedvalues, todaysDate, 1, "2491.72", "37.19", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2517.22", "11.69", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
}
@@ -502,8 +502,7 @@ public class Loan extends AbstractPersistable<Long> {
this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration;
}
public ChangedTransactionDetail addLoanCharge(final LoanCharge loanCharge) {
ChangedTransactionDetail changedTransactionDetail = null;
public void addLoanCharge(final LoanCharge loanCharge) {
validateLoanIsNotClosed(loanCharge);
@@ -554,27 +553,29 @@ public class Loan extends AbstractPersistable<Long> {
}
this.charges.add(loanCharge);
this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
// store Id's of existing loan transactions and existing reversed loan
// transactions
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(),
getLastUserTransactionForChargeCalc());
if (!loanCharge.isDueAtDisbursement()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(),
getLastUserTransactionForChargeCalc());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this);
}
// this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
}
updateLoanSummaryDerivedFields();
}
public ChangedTransactionDetail reprocessTransactions() {
ChangedTransactionDetail changedTransactionDetail = null;
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(),
getLastUserTransactionForChargeCalc());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this);
}
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
updateLoanSummaryDerivedFields();
return changedTransactionDetail;
}
@@ -700,7 +700,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
}
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance);
@@ -790,9 +790,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail);
}
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
LoanTransaction loanTransaction = this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, bulkRepaymentCommand.getTransactionDate(),
singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail, bulkRepaymentCommand.getNote(), null,
isRecoveryRepayment, isAccountTransfer);
LoanTransaction loanTransaction = this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder,
bulkRepaymentCommand.getTransactionDate(), singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail,
bulkRepaymentCommand.getNote(), null, isRecoveryRepayment, isAccountTransfer);
transactionIds.add(loanTransaction.getId());
}
@@ -1305,13 +1305,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
boolean isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge);
boolean reprocessRequired = true;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
if (isAppliedOnBackDate && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
runScheduleRecalculation(loan);
reprocessRequired = false;
}
updateOriginalSchedule(loan);
}
if (reprocessRequired) {
ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions();
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
// update loan with references to the newly created
// transactions
loan.getLoanTransactions().add(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
saveLoanWithDataIntegrityViolationChecks(loan);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
@@ -1420,7 +1434,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
}
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance);
createLoanScheduleArchive(loan, scheduleGeneratorDTO);
@@ -1447,36 +1461,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanChargeRepository.save(loanCharge);
final ChangedTransactionDetail changedTransactionDetail = loan.addLoanCharge(loanCharge);
loan.addLoanCharge(loanCharge);
/**
* we want to apply charge transactions only for those loans charges
* that are applied when a loan is active and the loan product uses
* Upfront Accruals
**/
if (loan.status().isActive()) {
if (loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null, currentUser);
this.loanTransactionRepository.save(applyLoanChargeTransaction);
}
/***
* TODO Vishwas Batch save is giving me a
* HibernateOptimisticLockingFailureException, looping and saving
* for the time being, not a major issue for now as this loop is
* entered only in edge cases (when a payment is made before the
* latest payment recorded against the loan)
***/
saveLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
// update loan with references to the newly created
// transactions
loan.getLoanTransactions().add(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
if (loan.status().isActive() && loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null, currentUser);
this.loanTransactionRepository.save(applyLoanChargeTransaction);
}
boolean isAppliedOnBackDate = false;
if (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate())) {
@@ -2403,13 +2397,29 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
runInterestRecalculation = runInterestRecalculation || overdueDTO.isRunInterestRecalculation();
}
if (loan != null) {
boolean reprocessRequired = true;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
if (runInterestRecalculation && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
runScheduleRecalculation(loan);
reprocessRequired = false;
}
updateOriginalSchedule(loan);
}
if (reprocessRequired) {
ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions();
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
// update loan with references to the newly created
// transactions
loan.getLoanTransactions().add(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
saveLoanWithDataIntegrityViolationChecks(loan);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && runInterestRecalculation
@@ -2556,59 +2566,54 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.build();
}
private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate){
final String json = command.json();
final JsonElement element = this.fromApiJsonHelper.parse(json);
private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate) {
final String json = command.json();
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
if(disbursementDataArray == null || disbursementDataArray.size() == 0){
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("approvedLoanAmount", element);
loanApplicationCommandFromApiJsonHelper.validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal);
loanApplicationCommandFromApiJsonHelper.validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate,
principal);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
@Override
@Override
@Transactional
public CommandProcessingResult addAndDeleteLoanDisburseDetails(Long loanId, JsonCommand command) {
public CommandProcessingResult addAndDeleteLoanDisburseDetails(Long loanId, JsonCommand command) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
final Map<String, Object> actualChanges = new LinkedHashMap<>();
LocalDate expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate();
LocalDate expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate();
validateMultiDisbursementData(command, expectedDisbursementDate);
loan.updateDisbursementDetails(command, actualChanges);
if (loan.getDisbursementDetails().isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(
LoanApiConstants.disbursementDataParameterName,
errorMessage);
}
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
if (loan.getDisbursementDetails().size() > loan.loanProduct()
.maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than "
+ loan.loanProduct().maxTrancheCount();
throw new ExceedingTrancheCountException(
LoanApiConstants.disbursementDataParameterName,
errorMessage, loan.loanProduct().maxTrancheCount(),
loan.getDisbursementDetails().size());
}
LoanDisbursementDetails updateDetails = null;
return processLoanDisbursementDetail(loan, loanId, command, updateDetails);
}
private CommandProcessingResult processLoanDisbursementDetail(final Loan loan, Long loanId, JsonCommand command,
LoanDisbursementDetails loanDisbursementDetails){
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
if (loan.getDisbursementDetails().size() > loan.loanProduct().maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than " + loan.loanProduct().maxTrancheCount();
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage, loan.loanProduct()
.maxTrancheCount(), loan.getDisbursementDetails().size());
}
LoanDisbursementDetails updateDetails = null;
return processLoanDisbursementDetail(loan, loanId, command, updateDetails);
}
private CommandProcessingResult processLoanDisbursementDetail(final Loan loan, Long loanId, JsonCommand command,
LoanDisbursementDetails loanDisbursementDetails) {
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance);
@@ -2637,23 +2642,23 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate);
ChangedTransactionDetail changedTransactionDetail = null;
ChangedTransactionDetail changedTransactionDetail = null;
AppUser currentUser = getAppUserIfPresent();
if(command.entityId() != null){
changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command,
if (command.entityId() != null) {
changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command,
existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO, currentUser);
}else{
Collection<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
BigDecimal setAmount = BigDecimal.ZERO;
} else {
Collection<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
BigDecimal setAmount = BigDecimal.ZERO;
for (LoanDisbursementDetails details : loanDisburseDetails) {
setAmount = setAmount.add(details.principal());
setAmount = setAmount.add(details.principal());
}
loan.repaymentScheduleDetail().setPrincipal(setAmount);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);
} else {
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
@@ -2677,21 +2682,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.with(changes).build();
}
.with(changes).build();
}
@Override
@Transactional
public CommandProcessingResult updateDisbursementDateAndAmountForTranche(final Long loanId, final Long disbursementId, final JsonCommand command) {
public CommandProcessingResult updateDisbursementDateAndAmountForTranche(final Long loanId, final Long disbursementId,
final JsonCommand command) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId);
this.loanEventApiJsonValidator.validateUpdateDisbursementDateAndAmount(command.json(), loanDisbursementDetails);
return processLoanDisbursementDetail(loan, loanId, command, loanDisbursementDetails);
return processLoanDisbursementDetail(loan, loanId, command, loanDisbursementDetails);
}
public LoanTransaction disburseLoanAmountToSavings(final Long loanId, Long loanChargeId, final JsonCommand command,