Merge pull request #1043 from pramodn02/develop

MIFOSX-1421 : corrected interest calculation for different interest comp...
This commit is contained in:
Vishwas Babu A J
2014-08-07 15:11:09 -07:00
11 changed files with 372 additions and 235 deletions
@@ -964,7 +964,6 @@ public class Loan extends AbstractPersistable<Long> {
}
public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule) {
clearInstallmentLoanCharge();
this.repaymentScheduleInstallments.clear();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
@@ -984,14 +983,6 @@ public class Loan extends AbstractPersistable<Long> {
}
private void clearInstallmentLoanCharge() {
for (final LoanCharge loanCharge : charges()) {
if (loanCharge.isInstalmentFee()) {
loanCharge.clearLoanInstallmentCharges();
}
}
}
private void updateLoanScheduleDependentDerivedFields() {
this.expectedMaturityDate = determineExpectedMaturityDate().toDate();
this.actualMaturityDate = determineExpectedMaturityDate().toDate();
@@ -1203,14 +1194,8 @@ public class Loan extends AbstractPersistable<Long> {
final String chargesParamName = "charges";
if (isChargesModified) {
actualChanges.put(chargesParamName, getLoanCharges(possiblyModifedLoanCharges));
actualChanges.put(chargesParamName, getLoanCharges(possiblyModifedLoanCharges));
actualChanges.put("recalculateLoanSchedule", true);
for (final LoanCharge loanCharge : possiblyModifedLoanCharges) {
recalculateLoanCharge(loanCharge);
}
}
final String collateralParamName = "collateral";
@@ -1269,6 +1254,13 @@ public class Loan extends AbstractPersistable<Long> {
return actualChanges;
}
public void recalculateAllCharges() {
Set<LoanCharge> charges = this.charges();
for (final LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge);
}
}
private boolean isInterestRecalculationEnabledForProduct() {
return this.loanProduct.isInterestRecalculationEnabled();
}
@@ -1723,12 +1715,12 @@ public class Loan extends AbstractPersistable<Long> {
return ids;
}
public void disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser, final JsonCommand command,
final ApplicationCurrency currency, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final Map<String, Object> actualChanges, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final boolean allowTransactionsOnHoliday,
final boolean allowTransactionsOnNonWorkingDay, final boolean recalculateSchedule,
final CalendarInstance calendarInstanceForInterestRecalculation) {
public ChangedTransactionDetail disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser,
final JsonCommand command, final ApplicationCurrency currency, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final Map<String, Object> actualChanges,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final boolean allowTransactionsOnHoliday, final boolean allowTransactionsOnNonWorkingDay,
final boolean recalculateSchedule, final CalendarInstance calendarInstanceForInterestRecalculation) {
final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED,
LoanStatus.fromInt(this.loanStatus));
@@ -1763,13 +1755,7 @@ public class Loan extends AbstractPersistable<Long> {
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged) {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
if (recalculateSchedule) {
Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge);
}
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now())) {
@@ -1794,8 +1780,7 @@ public class Loan extends AbstractPersistable<Long> {
this.loanTransactions.add(interestAppliedTransaction);
}
// changedTransactionDetail =
// reprocessTransactionForDisbursement(changedTransactionDetail);
return reprocessTransactionForDisbursement();
}
@@ -1860,14 +1845,8 @@ public class Loan extends AbstractPersistable<Long> {
return disburseAmount;
}
public ChangedTransactionDetail reprocessTransactionForDisbursement() {
private ChangedTransactionDetail reprocessTransactionForDisbursement() {
ChangedTransactionDetail changedTransactionDetail = null;
for (LoanCharge loanCharge : this.charges) {
if (loanCharge.isInstalmentFee() && loanCharge.hasNoLoanInstallmentCharges()) {
final Set<LoanInstallmentCharge> chargePerInstallments = generateInstallmentLoanCharges(loanCharge);
loanCharge.addLoanInstallmentCharges(chargePerInstallments);
}
}
if (this.loanProduct.isMultiDisburseLoan()) {
if (!this.actualDisbursementDate.equals(fetchLastDisburseDetail().getDisbursementDate())) {
@@ -2015,6 +1994,10 @@ public class Loan extends AbstractPersistable<Long> {
isHolidayEnabled, holidays, workingDays);
updateLoanSchedule(loanSchedule);
Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge);
}
}
private void handleDisbursementTransaction(final LocalDate disbursedOn) {
@@ -2146,10 +2129,6 @@ public class Loan extends AbstractPersistable<Long> {
regenerateRepaymentSchedule(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays);
if (isDisbueseAmtChanged) {
Set<LoanCharge> charges = charges();
for (LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge);
}
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
}
@@ -4149,4 +4128,8 @@ public class Loan extends AbstractPersistable<Long> {
}
return cumulativeOurstanding;
}
public List<LoanRepaymentScheduleInstallment> fetchRepaymentScheduleInstallments() {
return this.repaymentScheduleInstallments;
}
}
@@ -67,6 +67,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
@Autowired
public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository,
@@ -77,7 +78,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final JournalEntryWritePlatformService journalEntryWritePlatformService,
final AccountTransferRepository accountTransferRepository, final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final CalendarInstanceRepository calendarInstanceRepository) {
final CalendarInstanceRepository calendarInstanceRepository,
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository) {
this.loanAccountAssembler = loanAccountAssembler;
this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository;
@@ -91,6 +93,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.loanScheduleFactory = loanScheduleFactory;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.calendarInstanceRepository = calendarInstanceRepository;
this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
}
@Transactional
@@ -156,19 +159,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
holidays, workingDays, allowTransactionsOnNonWorkingDay, isHolidayEnabled, isRecoveryRepayment, this.loanScheduleFactory,
applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
try {
this.loanTransactionRepository.save(newRepaymentTransaction);
} catch (DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").value(newRepaymentTransaction.getExternalId())
.failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
/***
* TODO Vishwas Batch save is giving me a
@@ -178,11 +169,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
* recorded against the loan)
***/
this.loanRepository.saveAndFlush(loan);
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) {
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
// update loan with references to the newly created transactions
loan.getLoanTransactions().add(mapEntry.getValue());
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
@@ -204,6 +195,43 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
return newRepaymentTransaction;
}
private void saveLoanTransactionWithDataIntegrityViolationChecks(LoanTransaction newRepaymentTransaction) {
try {
this.loanTransactionRepository.save(newRepaymentTransaction);
} catch (DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").value(newRepaymentTransaction.getExternalId())
.failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
try {
List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.getId() == null) {
this.repaymentScheduleInstallmentRepository.save(installment);
}
}
this.loanRepository.saveAndFlush(loan);
} catch (final DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
@Override
@Transactional
public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, final LocalDate transactionDate,
@@ -233,8 +261,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, newPaymentTransaction,
installmentNumber);
}
this.loanTransactionRepository.save(newPaymentTransaction);
this.loanRepository.saveAndFlush(loan);
saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction);
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanTransactionNote(loan, newPaymentTransaction, noteText);
@@ -295,7 +323,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
loan.makeRefund(newRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds,
allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay);
this.loanTransactionRepository.save(newRefundTransaction);
saveLoanTransactionWithDataIntegrityViolationChecks(newRefundTransaction);
this.loanRepository.save(loan);
if (StringUtils.isNotBlank(noteText)) {
@@ -327,20 +355,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
txnExternalId);
disbursementTransaction.updateLoan(loan);
loan.getLoanTransactions().add(disbursementTransaction);
try {
this.loanTransactionRepository.save(disbursementTransaction);
} catch (DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").value(disbursementTransaction.getExternalId())
.failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
this.loanRepository.saveAndFlush(loan);
saveLoanTransactionWithDataIntegrityViolationChecks(disbursementTransaction);
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanTransactionNote(loan, disbursementTransaction, noteText);
@@ -355,7 +371,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Override
public void reverseTransfer(final LoanTransaction loanTransaction) {
loanTransaction.reverse();
this.loanTransactionRepository.save(loanTransaction);
saveLoanTransactionWithDataIntegrityViolationChecks(loanTransaction);
}
@Override
@@ -280,17 +280,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
}
this.amountOrPercentage = chargeAmount;
if (this.loan != null && isInstalmentFee()) {
final Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this);
if (this.loanInstallmentCharge.isEmpty()) {
this.loanInstallmentCharge.addAll(chargePerInstallments);
} else {
int index = 0;
final LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()];
final LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments);
for (final LoanInstallmentCharge chargePerInstallment : this.loanInstallmentCharge) {
chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]);
}
}
updateInstallmentCharges();
}
}
@@ -386,17 +376,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.amountOrPercentage = amount;
this.amountOutstanding = calculateOutstanding();
if (this.loan != null && isInstalmentFee()) {
final Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this);
if (this.loanInstallmentCharge.isEmpty()) {
this.loanInstallmentCharge.addAll(chargePerInstallments);
} else {
int index = 0;
final LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()];
final LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments);
for (final LoanInstallmentCharge chargePerInstallment : this.loanInstallmentCharge) {
chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]);
}
}
updateInstallmentCharges();
}
}
}
@@ -474,22 +454,36 @@ public class LoanCharge extends AbstractPersistable<Long> {
}
this.amountOrPercentage = newValue;
if (isInstalmentFee()) {
final Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this);
if (this.loanInstallmentCharge.isEmpty()) {
this.loanInstallmentCharge.addAll(chargePerInstallments);
} else {
int index = 0;
final LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()];
final LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments);
for (final LoanInstallmentCharge chargePerInstallment : this.loanInstallmentCharge) {
chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]);
}
}
updateInstallmentCharges();
}
}
return actualChanges;
}
private void updateInstallmentCharges() {
final Collection<LoanInstallmentCharge> remove = new HashSet<>();
final Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this);
if (this.loanInstallmentCharge.isEmpty()) {
this.loanInstallmentCharge.addAll(chargePerInstallments);
} else {
int index = 0;
final LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()];
final LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments);
for (final LoanInstallmentCharge chargePerInstallment : this.loanInstallmentCharge) {
if (index == loanChargePerInstallmentArray.length) {
remove.add(chargePerInstallment);
chargePerInstallment.updateInstallment(null);
} else {
chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]);
}
}
this.loanInstallmentCharge.removeAll(remove);
/*while (index < loanChargePerInstallmentArray.length - 1) {
chargePerInstallments.add(loanChargePerInstallmentArray[index++]);
}*/
}
}
public boolean isDueAtDisbursement() {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT);
}
@@ -811,7 +805,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
public List<LoanChargePaidDetail> fetchRepaymentInstallment(final MonetaryCurrency currency) {
List<LoanChargePaidDetail> chargePaidDetails = new ArrayList<>();
for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (loanChargePerInstallment.isPending()
if (!loanChargePerInstallment.isChargeAmountpaid(currency)
&& loanChargePerInstallment.getAmountThroughChargePayment(currency).isGreaterThanZero()) {
LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail(
loanChargePerInstallment.getAmountThroughChargePayment(currency),
@@ -857,11 +851,9 @@ public class LoanCharge extends AbstractPersistable<Long> {
public void updateWaivedAmount(MonetaryCurrency currency) {
if (isInstalmentFee()) {
this.amountWaived = BigDecimal.ZERO;
for (final LoanInstallmentCharge chargePerInstallment : this.loanInstallmentCharge) {
final Money amountWaived = chargePerInstallment.updateWaivedAmount(currency);
if (this.amountWaived == null) {
this.amountWaived = BigDecimal.ZERO;
}
final Money amountWaived = chargePerInstallment.updateWaivedAndAmountPaidThroughChargePaymentAmount(currency);
this.amountWaived = this.amountWaived.add(amountWaived.getAmount());
this.amountOutstanding = this.amountOutstanding.subtract(amountWaived.getAmount());
if (determineIfFullyPaid()) {
@@ -145,6 +145,12 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
return !(isPaid() || isWaived());
}
public boolean isChargeAmountpaid(MonetaryCurrency currency) {
Money amounPaidThroughChargePayment = Money.of(currency, this.amountThroughChargePayment);
Money paid = Money.of(currency, this.amountPaid);
return amounPaidThroughChargePayment.isEqualTo(paid);
}
public LoanRepaymentScheduleInstallment getRepaymentInstallment() {
return this.installment;
}
@@ -199,16 +205,35 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
this.amountPaid = BigDecimal.ZERO;
this.amountWaived = BigDecimal.ZERO;
this.amountWrittenOff = BigDecimal.ZERO;
this.amountThroughChargePayment = BigDecimal.ZERO;
this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false;
this.waived = false;
}
public Money getAmountThroughChargePayment(final MonetaryCurrency currency) {
return Money.of(currency, this.amountThroughChargePayment);
}
public Money updateWaivedAmount(final MonetaryCurrency currency) {
public Money getUnpaidAmountThroughChargePayment(final MonetaryCurrency currency) {
return Money.of(currency, this.amountThroughChargePayment).minus(this.amountPaid);
}
private void updateAmountThroughChargePayment(final MonetaryCurrency currency) {
Money amountThroughChargePayment = getAmountThroughChargePayment(currency);
if (amountThroughChargePayment.isGreaterThanZero() && amountThroughChargePayment.isGreaterThan(this.getAmount(currency))) {
this.amountThroughChargePayment = this.getAmount();
}
}
public Money updateWaivedAndAmountPaidThroughChargePaymentAmount(final MonetaryCurrency currency) {
updateWaivedAmount(currency);
updateAmountThroughChargePayment(currency);
return getAmountWaived(currency);
}
private void updateWaivedAmount(final MonetaryCurrency currency) {
Money waivedAmount = getAmountWaived(currency);
if (waivedAmount.isGreaterThanZero()) {
if (waivedAmount.isGreaterThan(this.getAmount(currency))) {
@@ -221,7 +246,11 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
this.waived = false;
}
}
return getAmountWaived(currency);
}
public void updateInstallment(LoanRepaymentScheduleInstallment installment) {
this.installment = installment;
}
}
@@ -0,0 +1,9 @@
package org.mifosplatform.portfolio.loanaccount.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface LoanRepaymentScheduleInstallmentRepository extends JpaRepository<LoanRepaymentScheduleInstallment, Long>,
JpaSpecificationExecutor<LoanRepaymentScheduleInstallment> {
}
@@ -263,6 +263,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
this.interestPortion = null;
this.feeChargesPortion = null;
this.penaltyChargesPortion = null;
this.overPaymentPortion = null;
}
public void updateLoan(final Loan loan) {
@@ -86,8 +86,12 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
for (final LoanRepaymentScheduleInstallment installment : installments) {
for (final LoanCharge loanCharge : transferCharges) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) {
LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail(
installment.getFeeChargesOutstanding(currency), installment, loanCharge.isFeeCharge());
Money amountForProcess = loanCharge.getAmount(currency);
if (amountForProcess.isGreaterThan(loanTransaction.getAmount(currency))) {
amountForProcess = loanTransaction.getAmount(currency);
}
LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail(amountForProcess, installment,
loanCharge.isFeeCharge());
chargePaidDetails.add(chargePaidDetail);
break;
}
@@ -98,11 +102,13 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
Money unprocessed = loanTransaction.getAmount(currency);
for (LoanChargePaidDetail chargePaidDetail : chargePaidDetails) {
final List<LoanRepaymentScheduleInstallment> processInstallments = new ArrayList<>(1);
unprocessed = unprocessed.minus(chargePaidDetail.getAmount());
processInstallments.add(chargePaidDetail.getInstallment());
Money unprocessedAfterProcess = handleTransactionAndCharges(loanTransaction, currency, processInstallments,
transferCharges, chargePaidDetail.getAmount(), chargePaidDetail.isFeeCharge());
unprocessed = unprocessed.plus(unprocessedAfterProcess);
Money processAmt = chargePaidDetail.getAmount();
if(processAmt.isGreaterThan(unprocessed)){
processAmt = unprocessed;
}
unprocessed = handleTransactionAndCharges(loanTransaction, currency, processInstallments,
transferCharges, processAmt, chargePaidDetail.isFeeCharge());
if (!unprocessed.isGreaterThanZero()) {
break;
}
@@ -206,7 +212,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
if (loanTransaction.isNotWaiver()) {
Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
if (chargeAmountToProcess != null) {
if (chargeAmountToProcess != null && feeCharges.isGreaterThan(chargeAmountToProcess)) {
if (isFeeCharge) {
feeCharges = chargeAmountToProcess;
} else {
@@ -235,23 +241,26 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
}
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
if (transactionAmountUnprocessed.isGreaterThanZero()) {
if (currentInstallment.isNotFullyPaidOff()) {
if (currentInstallment.isNotFullyPaidOff()) {
// is this transaction early/late/on-time with respect to the
// current installment?
if (isTransactionInAdvanceOfInstallment(installmentIndex, installments, transactionDate, transactionAmountUnprocessed)) {
transactionAmountUnprocessed = handleTransactionThatIsPaymentInAdvanceOfInstallment(currentInstallment, installments,
loanTransaction, transactionDate, transactionAmountUnprocessed);
} else if (isTransactionALateRepaymentOnInstallment(installmentIndex, installments, loanTransaction.getTransactionDate())) {
// does this result in a late payment of existing
// installment?
transactionAmountUnprocessed = handleTransactionThatIsALateRepaymentOfInstallment(currentInstallment, installments,
loanTransaction, transactionAmountUnprocessed);
} else {
// standard transaction
transactionAmountUnprocessed = handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction,
transactionAmountUnprocessed);
// is this transaction early/late/on-time with respect to
// the
// current installment?
if (isTransactionInAdvanceOfInstallment(installmentIndex, installments, transactionDate, transactionAmountUnprocessed)) {
transactionAmountUnprocessed = handleTransactionThatIsPaymentInAdvanceOfInstallment(currentInstallment,
installments, loanTransaction, transactionDate, transactionAmountUnprocessed);
} else if (isTransactionALateRepaymentOnInstallment(installmentIndex, installments,
loanTransaction.getTransactionDate())) {
// does this result in a late payment of existing
// installment?
transactionAmountUnprocessed = handleTransactionThatIsALateRepaymentOfInstallment(currentInstallment, installments,
loanTransaction, transactionAmountUnprocessed);
} else {
// standard transaction
transactionAmountUnprocessed = handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment,
loanTransaction, transactionAmountUnprocessed);
}
}
}
@@ -292,10 +301,18 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
}
final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining, installmentNumber, feeAmount);
if (!amountPaidTowardsCharge.isZero()) {
if (!loanTransaction.isChargePayment()) {
Set<LoanChargePaidBy> chargesPaidBies = loanTransaction.getLoanChargesPaid();
if (loanTransaction.isChargePayment()) {
for (final LoanChargePaidBy chargePaidBy : chargesPaidBies) {
LoanCharge loanCharge = chargePaidBy.getLoanCharge();
if (loanCharge.getId().equals(unpaidCharge.getId())) {
chargePaidBy.setAmount(amountPaidTowardsCharge.getAmount());
}
}
} else {
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, unpaidCharge,
amountPaidTowardsCharge.getAmount());
loanTransaction.getLoanChargesPaid().add(loanChargePaidBy);
chargesPaidBies.add(loanChargePaidBy);
}
amountRemaining = amountRemaining.minus(amountPaidTowardsCharge);
}
@@ -231,15 +231,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding);
}
}
interestReducedDueToEarlyPayment = interestReducedDueToEarlyPayment.plus(loanApplicationTerms.interestRateFor(
this.paymentPeriodsInOneYearCalculator, mc, diffDays, detail.getAmount()));
this.paymentPeriodsInOneYearCalculator, mc, detail.getAmount(), detail.getStartDate(), scheduledDueDate));
} else if (detail.isLatePayment() && detail.isOverlapping(periodStartDate, scheduledDueDate)
&& periodStartDate.isBefore(LocalDate.now())) {
LocalDate fromDate = periodStartDate;
LocalDate toDate = scheduledDueDate;
if (!detail.getStartDate().isAfter(periodStartDate)) {
if (!detail.getStartDate().isBefore(periodStartDate)) {
fromDate = detail.getStartDate();
}
if (!detail.getToDate().isAfter(scheduledDueDate)) {
@@ -248,9 +247,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (toDate.isAfter(LocalDate.now())) {
toDate = LocalDate.now();
}
int diffDays = Days.daysBetween(fromDate, toDate).getDays();
interestDueToLatePayment = interestDueToLatePayment.plus(loanApplicationTerms.interestRateFor(
this.paymentPeriodsInOneYearCalculator, mc, diffDays, detail.getAmount()));
this.paymentPeriodsInOneYearCalculator, mc, detail.getAmount(), fromDate, toDate));
}
}
@@ -333,18 +331,19 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
for (RecalculationDetail detail : processDetails.values()) {
LocalDate fromDate = detail.getStartDate();
LocalDate toDate = detail.getToDate();
int diffDays = Days.daysBetween(fromDate, toDate).getDays();
Money interestDueToLatePayment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, diffDays,
detail.getAmount());
if (!toDate.isAfter(LocalDate.now())) {
Money interestDueToLatePayment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc,
detail.getAmount(), fromDate, toDate);
totalInterestCharged = totalInterestCharged.add(interestDueToLatePayment.getAmount());
totalRepaymentExpected = totalRepaymentExpected.add(interestDueToLatePayment.getAmount());
totalInterestCharged = totalInterestCharged.add(interestDueToLatePayment.getAmount());
totalRepaymentExpected = totalRepaymentExpected.add(interestDueToLatePayment.getAmount());
final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, toDate,
interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment,
interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment);
periods.add(installment);
periodNumber++;
final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, toDate,
interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment,
interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment);
periods.add(installment);
periodNumber++;
}
}
}
@@ -711,8 +710,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
}
int days = Days.daysBetween(calculateInterestFrom, LocalDate.now()).getDays();
Money interest = applicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, days, prepaymentAmount);
Money interest = applicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, prepaymentAmount,
calculateInterestFrom, LocalDate.now());
prepaymentAmount = prepaymentAmount.plus(interest).plus(amount);
return prepaymentAmount;
}
@@ -13,6 +13,8 @@ import java.util.List;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.Period;
import org.joda.time.PeriodType;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
@@ -661,15 +663,21 @@ public final class LoanApplicationTerms {
return periodicInterestRate;
}
public Money interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, int days,
final Money outstandingBalance) {
final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator);
public Money interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, final Money outstandingBalance,
final LocalDate fromDate, final LocalDate toDate) {
long loanTermPeriodsInOneYear = calculator.calculate(PeriodFrequencyType.DAYS).longValue();
int repaymentEvery = Days.daysBetween(fromDate, toDate).getDays();
if (isFallingInRepaymentPeriod(fromDate, toDate)) {
loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator);
repaymentEvery = getPeriodsBetween(fromDate, toDate);
}
final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0"));
final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(
divisor, mc);
BigDecimal interestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(days), mc);
BigDecimal interestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(repaymentEvery), mc);
return outstandingBalance.multiplyRetainScale(interestRate, mc.getRoundingMode());
}
@@ -967,4 +975,59 @@ public final class LoanApplicationTerms {
public CalendarInstance getRestCalendarInstance() {
return this.restCalendarInstance;
}
private boolean isFallingInRepaymentPeriod(LocalDate fromDate, LocalDate toDate) {
boolean isSameAsRepaymentPeriod = false;
if (this.interestCalculationPeriodMethod.getValue().equals(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue())) {
switch (this.repaymentPeriodFrequencyType) {
case WEEKS:
int days = getPeriodsBetween(fromDate, toDate);
isSameAsRepaymentPeriod = (days % 7) == 0;
break;
case MONTHS:
boolean isFromDateOnEndDate = false;
if (fromDate.getDayOfMonth() > fromDate.plusDays(1).getDayOfMonth()) {
isFromDateOnEndDate = true;
}
boolean isToDateOnEndDate = false;
if (toDate.getDayOfMonth() > toDate.plusDays(1).getDayOfMonth()) {
isToDateOnEndDate = true;
}
if (isFromDateOnEndDate && isToDateOnEndDate) {
isSameAsRepaymentPeriod = true;
} else {
int months = getPeriodsBetween(fromDate, toDate);
fromDate = fromDate.plusMonths(months);
isSameAsRepaymentPeriod = fromDate.isEqual(toDate);
}
break;
default:
break;
}
}
return isSameAsRepaymentPeriod;
}
private Integer getPeriodsBetween(LocalDate fromDate, LocalDate toDate) {
Integer numberOfPeriods = 0;
PeriodType periodType = PeriodType.yearMonthDay();
Period difference = new Period(fromDate, toDate, periodType);
switch (this.repaymentPeriodFrequencyType) {
case WEEKS:
numberOfPeriods = difference.getDays();
break;
case MONTHS:
numberOfPeriods = difference.getMonths();
break;
case YEARS:
numberOfPeriods = difference.getYears();
break;
default:
break;
}
return numberOfPeriods;
}
}
@@ -54,9 +54,9 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
@@ -119,8 +119,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final CalendarInstanceRepository calendarInstanceRepository;
private final SavingsAccountAssembler savingsAccountAssembler;
private final AccountAssociationsRepository accountAssociationsRepository;
private final LoanChargeRepository loanChargeRepository;
private final LoanReadPlatformService loanReadPlatformService;
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
@Autowired
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
@@ -135,7 +135,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository,
final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository,
final LoanChargeRepository loanChargeRepository, final LoanReadPlatformService loanReadPlatformService) {
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
final LoanReadPlatformService loanReadPlatformService) {
this.context = context;
this.fromJsonHelper = fromJsonHelper;
this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
@@ -158,7 +159,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.calendarInstanceRepository = calendarInstanceRepository;
this.savingsAccountAssembler = savingsAccountAssembler;
this.accountAssociationsRepository = accountAssociationsRepository;
this.loanChargeRepository = loanChargeRepository;
this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
this.loanReadPlatformService = loanReadPlatformService;
}
@@ -517,36 +518,22 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query, false);
existingLoanApplication.updateLoanSchedule(loanSchedule);
existingLoanApplication.recalculateAllCharges();
}
this.loanRepository.saveAndFlush(existingLoanApplication);
// FIXME: adding loan installment charges along with the other
// charges,
// is trying to add extra entries with null values. Because of that
// persisting charges separately
final String chargesParamName = "charges";
if (changes.containsKey(chargesParamName)) {
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson());
existingLoanApplication.updateLoanCharges(loanCharges);
this.loanRepository.saveAndFlush(existingLoanApplication);
existingLoanApplication.updateLoanCharges(possiblyModifedLoanCharges);
}
saveAndFlushLoanWithDataIntegrityViolationChecks(existingLoanApplication);
final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote");
if (StringUtils.isNotBlank(submittedOnNote)) {
final Note note = Note.loanNote(existingLoanApplication, submittedOnNote);
this.noteRepository.save(note);
}
for (final LoanCharge loanCharge : existingLoanApplication.charges()) {
if (loanCharge.isInstalmentFee() && loanCharge.hasNoLoanInstallmentCharges()) {
final Set<LoanInstallmentCharge> chargePerInstallments = existingLoanApplication
.generateInstallmentLoanCharges(loanCharge);
loanCharge.addLoanInstallmentCharges(chargePerInstallments);
this.loanChargeRepository.save(loanCharge);
}
}
final Long calendarId = command.longValueOfParameterNamed("calendarId");
Calendar calendar = null;
if (calendarId != null && calendarId != 0) {
@@ -896,4 +883,27 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
}
}
private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
try {
List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.getId() == null) {
this.repaymentScheduleInstallmentRepository.save(installment);
} else {
break;
}
}
this.loanRepository.saveAndFlush(loan);
} catch (final DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.application");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
}
@@ -98,6 +98,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
@@ -166,6 +167,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final FromJsonHelper fromApiJsonHelper;
private final AccountTransferRepository accountTransferRepository;
private final CalendarRepository calendarRepository;
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
@Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -185,7 +187,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
final LoanChargeReadPlatformService loanChargeReadPlatformService, final LoanReadPlatformService loanReadPlatformService,
final FromJsonHelper fromApiJsonHelper, final AccountTransferRepository accountTransferRepository,
final CalendarRepository calendarRepository) {
final CalendarRepository calendarRepository,
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository) {
this.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanAssembler = loanAssembler;
@@ -213,6 +216,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.fromApiJsonHelper = fromApiJsonHelper;
this.accountTransferRepository = accountTransferRepository;
this.calendarRepository = calendarRepository;
this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -267,6 +271,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Money amountBeforeAdjust = loan.getPrincpal();
loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
boolean canDisburse = loan.canDisburse(actualDisbursementDate);
ChangedTransactionDetail changedTransactionDetail = null;
if (canDisburse) {
Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
boolean recalcualteSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal());
@@ -286,9 +291,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
}
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
existingReversedTransactionIds, changes, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays,
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule, restCalendarInstance);
changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency,
existingTransactionIds, existingReversedTransactionIds, changes, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay,
recalcualteSchedule, restCalendarInstance);
}
if (!changes.isEmpty()) {
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -299,11 +305,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note);
}
// fix me
// adding loan installment charges along with the actual
// disbursement, is trying to add 2 extra entries with null values.
// which is causing issues
ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactionForDisbursement();
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) {
@@ -367,6 +368,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
try {
List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.getId() == null) {
this.repaymentScheduleInstallmentRepository.save(installment);
} else {
break;
}
}
this.loanRepository.saveAndFlush(loan);
} catch (final DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
@@ -380,6 +389,29 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
private void saveLoanWithDataIntegrityViolationChecks(final Loan loan) {
try {
List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.getId() == null) {
this.repaymentScheduleInstallmentRepository.save(installment);
} else {
break;
}
}
this.loanRepository.save(loan);
} catch (final DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
/****
* TODO Vishwas: Pair with Ashok and re-factor collection sheet code-base
*
@@ -426,6 +458,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
updateLoanCounters(loan, actualDisbursementDate);
boolean canDisburse = loan.canDisburse(actualDisbursementDate);
ChangedTransactionDetail changedTransactionDetail = null;
if (canDisburse) {
Money amountBeforeAdjust = loan.getPrincpal();
Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
@@ -445,24 +478,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
}
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
existingReversedTransactionIds, changes, firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays,
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule, restCalendarInstance);
changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency,
existingTransactionIds, existingReversedTransactionIds, changes, firstRepaymentOnDate, isHolidayEnabled, holidays,
workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule,
restCalendarInstance);
}
if (!changes.isEmpty()) {
try {
this.loanRepository.save(loan);
} catch (final DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); }
}
saveLoanWithDataIntegrityViolationChecks(loan);
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
@@ -470,11 +493,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note);
}
// fix me
// adding loan installment charges along with the actual
// disbursement, is trying to add 2 extra entries with null
// values. which is causing issues
ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactionForDisbursement();
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -542,7 +560,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
existingReversedTransactionIds, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
if (!changes.isEmpty()) {
this.loanRepository.saveAndFlush(loan);
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
this.accountTransfersWritePlatformService.reverseAllTransactions(loanId, PortfolioAccountType.LOAN);
String noteText = null;
if (command.hasParameter("note")) {
@@ -719,7 +737,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
* only in edge cases (when a adjustment is made before the latest
* payment recorded against the loan)
***/
this.loanRepository.saveAndFlush(loan);
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
@@ -821,7 +839,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
* only in edge cases (when a waiver is made before the latest payment
* recorded against the loan)
***/
this.loanRepository.saveAndFlush(loan);
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
@@ -898,7 +916,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
@@ -968,7 +986,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
@@ -1026,7 +1044,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.closeAsMarkedForReschedule(command, defaultLoanLifecycleStateMachine(), changes);
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
@@ -1120,7 +1138,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
* entered only in edge cases (when a payment is made before the
* latest payment recorded against the loan)
***/
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
@@ -1155,7 +1173,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Map<String, Object> changes = loan.updateLoanCharge(loanCharge, command);
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
@@ -1220,7 +1238,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber);
this.loanTransactionRepository.save(waiveTransaction);
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
@@ -1251,7 +1269,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
LOAN_CHARGE_CANNOT_BE_DELETED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); }
loan.removeLoanCharge(loanCharge);
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
@@ -1448,7 +1466,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.setLoanStatus(LoanStatus.TRANSFER_IN_PROGRESS.getValue());
this.loanTransactionRepository.save(newTransferTransaction);
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
@@ -1477,7 +1495,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
this.loanTransactionRepository.save(newTransferAcceptanceTransaction);
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
@@ -1497,7 +1515,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.setLoanStatus(LoanStatus.ACTIVE.getValue());
this.loanTransactionRepository.save(newTransferAcceptanceTransaction);
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
@@ -1509,7 +1527,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public void rejectLoanTransfer(final Long accountId) {
final Loan loan = this.loanAssembler.assembleFrom(accountId);
loan.setLoanStatus(LoanStatus.TRANSFER_ON_HOLD.getValue());
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
}
@Transactional
@@ -1534,7 +1552,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment);
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
@@ -1570,7 +1588,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); }
loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment);
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
}
this.loanRepository.flush();
@@ -1598,7 +1616,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.removeLoanOfficer(dateOfLoanOfficerunAssigned);
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
@@ -1644,7 +1662,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
holidays, workingDays);
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
}
}
}
@@ -2022,7 +2040,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
return new CommandProcessingResultBuilder() //
@@ -2064,7 +2082,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
existingTransactionIds, existingReversedTransactionIds, changes, this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance);
this.loanRepository.saveAndFlush(loan);
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) {
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -2191,7 +2209,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance,
existingTransactionIds, existingReversedTransactionIds);
this.loanRepository.save(loan);
saveLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {