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) { public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule) {
clearInstallmentLoanCharge();
this.repaymentScheduleInstallments.clear(); this.repaymentScheduleInstallments.clear();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) { 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() { private void updateLoanScheduleDependentDerivedFields() {
this.expectedMaturityDate = determineExpectedMaturityDate().toDate(); this.expectedMaturityDate = determineExpectedMaturityDate().toDate();
this.actualMaturityDate = determineExpectedMaturityDate().toDate(); this.actualMaturityDate = determineExpectedMaturityDate().toDate();
@@ -1203,14 +1194,8 @@ public class Loan extends AbstractPersistable<Long> {
final String chargesParamName = "charges"; final String chargesParamName = "charges";
if (isChargesModified) { if (isChargesModified) {
actualChanges.put(chargesParamName, getLoanCharges(possiblyModifedLoanCharges));
actualChanges.put(chargesParamName, getLoanCharges(possiblyModifedLoanCharges)); actualChanges.put(chargesParamName, getLoanCharges(possiblyModifedLoanCharges));
actualChanges.put("recalculateLoanSchedule", true); actualChanges.put("recalculateLoanSchedule", true);
for (final LoanCharge loanCharge : possiblyModifedLoanCharges) {
recalculateLoanCharge(loanCharge);
}
} }
final String collateralParamName = "collateral"; final String collateralParamName = "collateral";
@@ -1269,6 +1254,13 @@ public class Loan extends AbstractPersistable<Long> {
return actualChanges; return actualChanges;
} }
public void recalculateAllCharges() {
Set<LoanCharge> charges = this.charges();
for (final LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge);
}
}
private boolean isInterestRecalculationEnabledForProduct() { private boolean isInterestRecalculationEnabledForProduct() {
return this.loanProduct.isInterestRecalculationEnabled(); return this.loanProduct.isInterestRecalculationEnabled();
} }
@@ -1723,12 +1715,12 @@ public class Loan extends AbstractPersistable<Long> {
return ids; return ids;
} }
public void disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser, final JsonCommand command, public ChangedTransactionDetail disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser,
final ApplicationCurrency currency, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final JsonCommand command, final ApplicationCurrency currency, final List<Long> existingTransactionIds,
final Map<String, Object> actualChanges, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Long> existingReversedTransactionIds, final Map<String, Object> actualChanges,
final List<Holiday> holidays, final WorkingDays workingDays, final boolean allowTransactionsOnHoliday, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final boolean allowTransactionsOnNonWorkingDay, final boolean recalculateSchedule, final WorkingDays workingDays, final boolean allowTransactionsOnHoliday, final boolean allowTransactionsOnNonWorkingDay,
final CalendarInstance calendarInstanceForInterestRecalculation) { final boolean recalculateSchedule, final CalendarInstance calendarInstanceForInterestRecalculation) {
final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED,
LoanStatus.fromInt(this.loanStatus)); LoanStatus.fromInt(this.loanStatus));
@@ -1763,13 +1755,7 @@ public class Loan extends AbstractPersistable<Long> {
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged) { if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged) {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays); workingDays);
if (recalculateSchedule) { updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge);
}
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
} }
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now())) { && fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now())) {
@@ -1794,8 +1780,7 @@ public class Loan extends AbstractPersistable<Long> {
this.loanTransactions.add(interestAppliedTransaction); this.loanTransactions.add(interestAppliedTransaction);
} }
// changedTransactionDetail = return reprocessTransactionForDisbursement();
// reprocessTransactionForDisbursement(changedTransactionDetail);
} }
@@ -1860,14 +1845,8 @@ public class Loan extends AbstractPersistable<Long> {
return disburseAmount; return disburseAmount;
} }
public ChangedTransactionDetail reprocessTransactionForDisbursement() { private ChangedTransactionDetail reprocessTransactionForDisbursement() {
ChangedTransactionDetail changedTransactionDetail = null; 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.loanProduct.isMultiDisburseLoan()) {
if (!this.actualDisbursementDate.equals(fetchLastDisburseDetail().getDisbursementDate())) { if (!this.actualDisbursementDate.equals(fetchLastDisburseDetail().getDisbursementDate())) {
@@ -2015,6 +1994,10 @@ public class Loan extends AbstractPersistable<Long> {
isHolidayEnabled, holidays, workingDays); isHolidayEnabled, holidays, workingDays);
updateLoanSchedule(loanSchedule); updateLoanSchedule(loanSchedule);
Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge);
}
} }
private void handleDisbursementTransaction(final LocalDate disbursedOn) { private void handleDisbursementTransaction(final LocalDate disbursedOn) {
@@ -2146,10 +2129,6 @@ public class Loan extends AbstractPersistable<Long> {
regenerateRepaymentSchedule(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, regenerateRepaymentSchedule(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays); isHolidayEnabled, holidays, workingDays);
if (isDisbueseAmtChanged) { if (isDisbueseAmtChanged) {
Set<LoanCharge> charges = charges();
for (LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge);
}
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
} }
} }
@@ -4149,4 +4128,8 @@ public class Loan extends AbstractPersistable<Long> {
} }
return cumulativeOurstanding; return cumulativeOurstanding;
} }
public List<LoanRepaymentScheduleInstallment> fetchRepaymentScheduleInstallments() {
return this.repaymentScheduleInstallments;
}
} }
@@ -67,6 +67,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final LoanScheduleGeneratorFactory loanScheduleFactory; private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final CalendarInstanceRepository calendarInstanceRepository; private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
@Autowired @Autowired
public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository, public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository,
@@ -77,7 +78,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final JournalEntryWritePlatformService journalEntryWritePlatformService, final JournalEntryWritePlatformService journalEntryWritePlatformService,
final AccountTransferRepository accountTransferRepository, final LoanScheduleGeneratorFactory loanScheduleFactory, final AccountTransferRepository accountTransferRepository, final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final CalendarInstanceRepository calendarInstanceRepository) { final CalendarInstanceRepository calendarInstanceRepository,
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository) {
this.loanAccountAssembler = loanAccountAssembler; this.loanAccountAssembler = loanAccountAssembler;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository; this.loanTransactionRepository = loanTransactionRepository;
@@ -91,6 +93,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.loanScheduleFactory = loanScheduleFactory; this.loanScheduleFactory = loanScheduleFactory;
this.applicationCurrencyRepository = applicationCurrencyRepository; this.applicationCurrencyRepository = applicationCurrencyRepository;
this.calendarInstanceRepository = calendarInstanceRepository; this.calendarInstanceRepository = calendarInstanceRepository;
this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
} }
@Transactional @Transactional
@@ -156,19 +159,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
holidays, workingDays, allowTransactionsOnNonWorkingDay, isHolidayEnabled, isRecoveryRepayment, this.loanScheduleFactory, holidays, workingDays, allowTransactionsOnNonWorkingDay, isHolidayEnabled, isRecoveryRepayment, this.loanScheduleFactory,
applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance); applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
try { saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
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); }
}
/*** /***
* TODO Vishwas Batch save is giving me a * TODO Vishwas Batch save is giving me a
@@ -178,11 +169,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
* recorded against the loan) * recorded against the loan)
***/ ***/
this.loanRepository.saveAndFlush(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { 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 // update loan with references to the newly created transactions
loan.getLoanTransactions().add(mapEntry.getValue()); loan.getLoanTransactions().add(mapEntry.getValue());
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
@@ -204,6 +195,43 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
return newRepaymentTransaction; 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 @Override
@Transactional @Transactional
public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, final LocalDate transactionDate, 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, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, newPaymentTransaction,
installmentNumber); installmentNumber);
} }
this.loanTransactionRepository.save(newPaymentTransaction); saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction);
this.loanRepository.saveAndFlush(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanTransactionNote(loan, newPaymentTransaction, noteText); final Note note = Note.loanTransactionNote(loan, newPaymentTransaction, noteText);
@@ -295,7 +323,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
loan.makeRefund(newRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, loan.makeRefund(newRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds,
allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay); allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay);
this.loanTransactionRepository.save(newRefundTransaction); saveLoanTransactionWithDataIntegrityViolationChecks(newRefundTransaction);
this.loanRepository.save(loan); this.loanRepository.save(loan);
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
@@ -327,20 +355,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
txnExternalId); txnExternalId);
disbursementTransaction.updateLoan(loan); disbursementTransaction.updateLoan(loan);
loan.getLoanTransactions().add(disbursementTransaction); loan.getLoanTransactions().add(disbursementTransaction);
try { saveLoanTransactionWithDataIntegrityViolationChecks(disbursementTransaction);
this.loanTransactionRepository.save(disbursementTransaction); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
} 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);
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanTransactionNote(loan, disbursementTransaction, noteText); final Note note = Note.loanTransactionNote(loan, disbursementTransaction, noteText);
@@ -355,7 +371,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Override @Override
public void reverseTransfer(final LoanTransaction loanTransaction) { public void reverseTransfer(final LoanTransaction loanTransaction) {
loanTransaction.reverse(); loanTransaction.reverse();
this.loanTransactionRepository.save(loanTransaction); saveLoanTransactionWithDataIntegrityViolationChecks(loanTransaction);
} }
@Override @Override
@@ -280,17 +280,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
this.amountOrPercentage = chargeAmount; this.amountOrPercentage = chargeAmount;
if (this.loan != null && isInstalmentFee()) { if (this.loan != null && isInstalmentFee()) {
final Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this); updateInstallmentCharges();
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++]);
}
}
} }
} }
@@ -386,17 +376,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.amountOrPercentage = amount; this.amountOrPercentage = amount;
this.amountOutstanding = calculateOutstanding(); this.amountOutstanding = calculateOutstanding();
if (this.loan != null && isInstalmentFee()) { if (this.loan != null && isInstalmentFee()) {
final Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this); updateInstallmentCharges();
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++]);
}
}
} }
} }
} }
@@ -474,22 +454,36 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
this.amountOrPercentage = newValue; this.amountOrPercentage = newValue;
if (isInstalmentFee()) { if (isInstalmentFee()) {
final Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this); updateInstallmentCharges();
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++]);
}
}
} }
} }
return actualChanges; 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() { public boolean isDueAtDisbursement() {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT); 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) { public List<LoanChargePaidDetail> fetchRepaymentInstallment(final MonetaryCurrency currency) {
List<LoanChargePaidDetail> chargePaidDetails = new ArrayList<>(); List<LoanChargePaidDetail> chargePaidDetails = new ArrayList<>();
for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) { for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (loanChargePerInstallment.isPending() if (!loanChargePerInstallment.isChargeAmountpaid(currency)
&& loanChargePerInstallment.getAmountThroughChargePayment(currency).isGreaterThanZero()) { && loanChargePerInstallment.getAmountThroughChargePayment(currency).isGreaterThanZero()) {
LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail( LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail(
loanChargePerInstallment.getAmountThroughChargePayment(currency), loanChargePerInstallment.getAmountThroughChargePayment(currency),
@@ -857,11 +851,9 @@ public class LoanCharge extends AbstractPersistable<Long> {
public void updateWaivedAmount(MonetaryCurrency currency) { public void updateWaivedAmount(MonetaryCurrency currency) {
if (isInstalmentFee()) { if (isInstalmentFee()) {
this.amountWaived = BigDecimal.ZERO;
for (final LoanInstallmentCharge chargePerInstallment : this.loanInstallmentCharge) { for (final LoanInstallmentCharge chargePerInstallment : this.loanInstallmentCharge) {
final Money amountWaived = chargePerInstallment.updateWaivedAmount(currency); final Money amountWaived = chargePerInstallment.updateWaivedAndAmountPaidThroughChargePaymentAmount(currency);
if (this.amountWaived == null) {
this.amountWaived = BigDecimal.ZERO;
}
this.amountWaived = this.amountWaived.add(amountWaived.getAmount()); this.amountWaived = this.amountWaived.add(amountWaived.getAmount());
this.amountOutstanding = this.amountOutstanding.subtract(amountWaived.getAmount()); this.amountOutstanding = this.amountOutstanding.subtract(amountWaived.getAmount());
if (determineIfFullyPaid()) { if (determineIfFullyPaid()) {
@@ -145,6 +145,12 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
return !(isPaid() || isWaived()); 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() { public LoanRepaymentScheduleInstallment getRepaymentInstallment() {
return this.installment; return this.installment;
} }
@@ -199,16 +205,35 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
this.amountPaid = BigDecimal.ZERO; this.amountPaid = BigDecimal.ZERO;
this.amountWaived = BigDecimal.ZERO; this.amountWaived = BigDecimal.ZERO;
this.amountWrittenOff = BigDecimal.ZERO; this.amountWrittenOff = BigDecimal.ZERO;
this.amountThroughChargePayment = BigDecimal.ZERO;
this.amountOutstanding = calculateAmountOutstanding(currency); this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false; this.paid = false;
this.waived = false; this.waived = false;
} }
public Money getAmountThroughChargePayment(final MonetaryCurrency currency) { public Money getAmountThroughChargePayment(final MonetaryCurrency currency) {
return Money.of(currency, this.amountThroughChargePayment); 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); Money waivedAmount = getAmountWaived(currency);
if (waivedAmount.isGreaterThanZero()) { if (waivedAmount.isGreaterThanZero()) {
if (waivedAmount.isGreaterThan(this.getAmount(currency))) { if (waivedAmount.isGreaterThan(this.getAmount(currency))) {
@@ -221,7 +246,11 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
this.waived = false; 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.interestPortion = null;
this.feeChargesPortion = null; this.feeChargesPortion = null;
this.penaltyChargesPortion = null; this.penaltyChargesPortion = null;
this.overPaymentPortion = null;
} }
public void updateLoan(final Loan loan) { public void updateLoan(final Loan loan) {
@@ -86,8 +86,12 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
for (final LoanRepaymentScheduleInstallment installment : installments) { for (final LoanRepaymentScheduleInstallment installment : installments) {
for (final LoanCharge loanCharge : transferCharges) { for (final LoanCharge loanCharge : transferCharges) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) { if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) {
LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail( Money amountForProcess = loanCharge.getAmount(currency);
installment.getFeeChargesOutstanding(currency), installment, loanCharge.isFeeCharge()); if (amountForProcess.isGreaterThan(loanTransaction.getAmount(currency))) {
amountForProcess = loanTransaction.getAmount(currency);
}
LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail(amountForProcess, installment,
loanCharge.isFeeCharge());
chargePaidDetails.add(chargePaidDetail); chargePaidDetails.add(chargePaidDetail);
break; break;
} }
@@ -98,11 +102,13 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
Money unprocessed = loanTransaction.getAmount(currency); Money unprocessed = loanTransaction.getAmount(currency);
for (LoanChargePaidDetail chargePaidDetail : chargePaidDetails) { for (LoanChargePaidDetail chargePaidDetail : chargePaidDetails) {
final List<LoanRepaymentScheduleInstallment> processInstallments = new ArrayList<>(1); final List<LoanRepaymentScheduleInstallment> processInstallments = new ArrayList<>(1);
unprocessed = unprocessed.minus(chargePaidDetail.getAmount());
processInstallments.add(chargePaidDetail.getInstallment()); processInstallments.add(chargePaidDetail.getInstallment());
Money unprocessedAfterProcess = handleTransactionAndCharges(loanTransaction, currency, processInstallments, Money processAmt = chargePaidDetail.getAmount();
transferCharges, chargePaidDetail.getAmount(), chargePaidDetail.isFeeCharge()); if(processAmt.isGreaterThan(unprocessed)){
unprocessed = unprocessed.plus(unprocessedAfterProcess); processAmt = unprocessed;
}
unprocessed = handleTransactionAndCharges(loanTransaction, currency, processInstallments,
transferCharges, processAmt, chargePaidDetail.isFeeCharge());
if (!unprocessed.isGreaterThanZero()) { if (!unprocessed.isGreaterThanZero()) {
break; break;
} }
@@ -206,7 +212,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
if (loanTransaction.isNotWaiver()) { if (loanTransaction.isNotWaiver()) {
Money feeCharges = loanTransaction.getFeeChargesPortion(currency); Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency); Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
if (chargeAmountToProcess != null) { if (chargeAmountToProcess != null && feeCharges.isGreaterThan(chargeAmountToProcess)) {
if (isFeeCharge) { if (isFeeCharge) {
feeCharges = chargeAmountToProcess; feeCharges = chargeAmountToProcess;
} else { } else {
@@ -235,23 +241,26 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
} }
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { 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
// is this transaction early/late/on-time with respect to the // current installment?
// current installment? if (isTransactionInAdvanceOfInstallment(installmentIndex, installments, transactionDate, transactionAmountUnprocessed)) {
if (isTransactionInAdvanceOfInstallment(installmentIndex, installments, transactionDate, transactionAmountUnprocessed)) { transactionAmountUnprocessed = handleTransactionThatIsPaymentInAdvanceOfInstallment(currentInstallment,
transactionAmountUnprocessed = handleTransactionThatIsPaymentInAdvanceOfInstallment(currentInstallment, installments, installments, loanTransaction, transactionDate, transactionAmountUnprocessed);
loanTransaction, transactionDate, transactionAmountUnprocessed); } else if (isTransactionALateRepaymentOnInstallment(installmentIndex, installments,
} else if (isTransactionALateRepaymentOnInstallment(installmentIndex, installments, loanTransaction.getTransactionDate())) { loanTransaction.getTransactionDate())) {
// does this result in a late payment of existing // does this result in a late payment of existing
// installment? // installment?
transactionAmountUnprocessed = handleTransactionThatIsALateRepaymentOfInstallment(currentInstallment, installments, transactionAmountUnprocessed = handleTransactionThatIsALateRepaymentOfInstallment(currentInstallment, installments,
loanTransaction, transactionAmountUnprocessed); loanTransaction, transactionAmountUnprocessed);
} else { } else {
// standard transaction // standard transaction
transactionAmountUnprocessed = handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed = handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment,
transactionAmountUnprocessed); loanTransaction, transactionAmountUnprocessed);
}
} }
} }
@@ -292,10 +301,18 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
} }
final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining, installmentNumber, feeAmount); final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining, installmentNumber, feeAmount);
if (!amountPaidTowardsCharge.isZero()) { 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, final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, unpaidCharge,
amountPaidTowardsCharge.getAmount()); amountPaidTowardsCharge.getAmount());
loanTransaction.getLoanChargesPaid().add(loanChargePaidBy); chargesPaidBies.add(loanChargePaidBy);
} }
amountRemaining = amountRemaining.minus(amountPaidTowardsCharge); amountRemaining = amountRemaining.minus(amountPaidTowardsCharge);
} }
@@ -231,15 +231,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding); principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding);
} }
} }
interestReducedDueToEarlyPayment = interestReducedDueToEarlyPayment.plus(loanApplicationTerms.interestRateFor( 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) } else if (detail.isLatePayment() && detail.isOverlapping(periodStartDate, scheduledDueDate)
&& periodStartDate.isBefore(LocalDate.now())) { && periodStartDate.isBefore(LocalDate.now())) {
LocalDate fromDate = periodStartDate; LocalDate fromDate = periodStartDate;
LocalDate toDate = scheduledDueDate; LocalDate toDate = scheduledDueDate;
if (!detail.getStartDate().isAfter(periodStartDate)) { if (!detail.getStartDate().isBefore(periodStartDate)) {
fromDate = detail.getStartDate(); fromDate = detail.getStartDate();
} }
if (!detail.getToDate().isAfter(scheduledDueDate)) { if (!detail.getToDate().isAfter(scheduledDueDate)) {
@@ -248,9 +247,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (toDate.isAfter(LocalDate.now())) { if (toDate.isAfter(LocalDate.now())) {
toDate = LocalDate.now(); toDate = LocalDate.now();
} }
int diffDays = Days.daysBetween(fromDate, toDate).getDays();
interestDueToLatePayment = interestDueToLatePayment.plus(loanApplicationTerms.interestRateFor( 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()) { for (RecalculationDetail detail : processDetails.values()) {
LocalDate fromDate = detail.getStartDate(); LocalDate fromDate = detail.getStartDate();
LocalDate toDate = detail.getToDate(); LocalDate toDate = detail.getToDate();
int diffDays = Days.daysBetween(fromDate, toDate).getDays(); if (!toDate.isAfter(LocalDate.now())) {
Money interestDueToLatePayment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, diffDays, Money interestDueToLatePayment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc,
detail.getAmount()); detail.getAmount(), fromDate, toDate);
totalInterestCharged = totalInterestCharged.add(interestDueToLatePayment.getAmount()); totalInterestCharged = totalInterestCharged.add(interestDueToLatePayment.getAmount());
totalRepaymentExpected = totalRepaymentExpected.add(interestDueToLatePayment.getAmount()); totalRepaymentExpected = totalRepaymentExpected.add(interestDueToLatePayment.getAmount());
final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, toDate, final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, toDate,
interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment, interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment,
interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment); interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment);
periods.add(installment); periods.add(installment);
periodNumber++; 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, prepaymentAmount,
Money interest = applicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, days, prepaymentAmount); calculateInterestFrom, LocalDate.now());
prepaymentAmount = prepaymentAmount.plus(interest).plus(amount); prepaymentAmount = prepaymentAmount.plus(interest).plus(amount);
return prepaymentAmount; return prepaymentAmount;
} }
@@ -13,6 +13,8 @@ import java.util.List;
import org.joda.time.Days; import org.joda.time.Days;
import org.joda.time.LocalDate; 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.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.monetary.domain.Money;
@@ -661,15 +663,21 @@ public final class LoanApplicationTerms {
return periodicInterestRate; return periodicInterestRate;
} }
public Money interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, int days, public Money interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, final Money outstandingBalance,
final Money outstandingBalance) { final LocalDate fromDate, final LocalDate toDate) {
final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator);
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 divisor = BigDecimal.valueOf(Double.valueOf("100.0"));
final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide( final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(
divisor, mc); divisor, mc);
BigDecimal interestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(days), mc); BigDecimal interestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(repaymentEvery), mc);
return outstandingBalance.multiplyRetainScale(interestRate, mc.getRoundingMode()); return outstandingBalance.multiplyRetainScale(interestRate, mc.getRoundingMode());
} }
@@ -967,4 +975,59 @@ public final class LoanApplicationTerms {
public CalendarInstance getRestCalendarInstance() { public CalendarInstance getRestCalendarInstance() {
return this.restCalendarInstance; 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.DefaultLoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; 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.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.LoanRepaymentScheduleTransactionProcessorFactory;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
@@ -119,8 +119,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final CalendarInstanceRepository calendarInstanceRepository; private final CalendarInstanceRepository calendarInstanceRepository;
private final SavingsAccountAssembler savingsAccountAssembler; private final SavingsAccountAssembler savingsAccountAssembler;
private final AccountAssociationsRepository accountAssociationsRepository; private final AccountAssociationsRepository accountAssociationsRepository;
private final LoanChargeRepository loanChargeRepository;
private final LoanReadPlatformService loanReadPlatformService; private final LoanReadPlatformService loanReadPlatformService;
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
@Autowired @Autowired
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
@@ -135,7 +135,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository, final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository,
final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository, final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository,
final LoanChargeRepository loanChargeRepository, final LoanReadPlatformService loanReadPlatformService) { final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
final LoanReadPlatformService loanReadPlatformService) {
this.context = context; this.context = context;
this.fromJsonHelper = fromJsonHelper; this.fromJsonHelper = fromJsonHelper;
this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator; this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
@@ -158,7 +159,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.calendarInstanceRepository = calendarInstanceRepository; this.calendarInstanceRepository = calendarInstanceRepository;
this.savingsAccountAssembler = savingsAccountAssembler; this.savingsAccountAssembler = savingsAccountAssembler;
this.accountAssociationsRepository = accountAssociationsRepository; this.accountAssociationsRepository = accountAssociationsRepository;
this.loanChargeRepository = loanChargeRepository; this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
this.loanReadPlatformService = loanReadPlatformService; this.loanReadPlatformService = loanReadPlatformService;
} }
@@ -517,36 +518,22 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query, false); final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query, false);
existingLoanApplication.updateLoanSchedule(loanSchedule); 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"; final String chargesParamName = "charges";
if (changes.containsKey(chargesParamName)) { if (changes.containsKey(chargesParamName)) {
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson()); existingLoanApplication.updateLoanCharges(possiblyModifedLoanCharges);
existingLoanApplication.updateLoanCharges(loanCharges);
this.loanRepository.saveAndFlush(existingLoanApplication);
} }
saveAndFlushLoanWithDataIntegrityViolationChecks(existingLoanApplication);
final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote"); final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote");
if (StringUtils.isNotBlank(submittedOnNote)) { if (StringUtils.isNotBlank(submittedOnNote)) {
final Note note = Note.loanNote(existingLoanApplication, submittedOnNote); final Note note = Note.loanNote(existingLoanApplication, submittedOnNote);
this.noteRepository.save(note); 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"); final Long calendarId = command.longValueOfParameterNamed("calendarId");
Calendar calendar = null; Calendar calendar = null;
if (calendarId != null && calendarId != 0) { 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.LoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; 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.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
@@ -166,6 +167,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
private final AccountTransferRepository accountTransferRepository; private final AccountTransferRepository accountTransferRepository;
private final CalendarRepository calendarRepository; private final CalendarRepository calendarRepository;
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
@Autowired @Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -185,7 +187,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
final LoanChargeReadPlatformService loanChargeReadPlatformService, final LoanReadPlatformService loanReadPlatformService, final LoanChargeReadPlatformService loanChargeReadPlatformService, final LoanReadPlatformService loanReadPlatformService,
final FromJsonHelper fromApiJsonHelper, final AccountTransferRepository accountTransferRepository, final FromJsonHelper fromApiJsonHelper, final AccountTransferRepository accountTransferRepository,
final CalendarRepository calendarRepository) { final CalendarRepository calendarRepository,
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository) {
this.context = context; this.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator; this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanAssembler = loanAssembler; this.loanAssembler = loanAssembler;
@@ -213,6 +216,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.fromApiJsonHelper = fromApiJsonHelper; this.fromApiJsonHelper = fromApiJsonHelper;
this.accountTransferRepository = accountTransferRepository; this.accountTransferRepository = accountTransferRepository;
this.calendarRepository = calendarRepository; this.calendarRepository = calendarRepository;
this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
} }
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -267,6 +271,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Money amountBeforeAdjust = loan.getPrincpal(); Money amountBeforeAdjust = loan.getPrincpal();
loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED); loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
boolean canDisburse = loan.canDisburse(actualDisbursementDate); boolean canDisburse = loan.canDisburse(actualDisbursementDate);
ChangedTransactionDetail changedTransactionDetail = null;
if (canDisburse) { if (canDisburse) {
Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate); Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
boolean recalcualteSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal()); boolean recalcualteSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal());
@@ -286,9 +291,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
} }
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds, changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency,
existingReversedTransactionIds, changes, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, existingTransactionIds, existingReversedTransactionIds, changes, calculatedRepaymentsStartingFromDate,
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule, restCalendarInstance); isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay,
recalcualteSchedule, restCalendarInstance);
} }
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
saveAndFlushLoanWithDataIntegrityViolationChecks(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -299,11 +305,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); 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); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
@@ -367,6 +368,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) { private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
try { try {
List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.getId() == null) {
this.repaymentScheduleInstallmentRepository.save(installment);
} else {
break;
}
}
this.loanRepository.saveAndFlush(loan); this.loanRepository.saveAndFlush(loan);
} catch (final DataIntegrityViolationException e) { } catch (final DataIntegrityViolationException e) {
final Throwable realCause = e.getCause(); 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 * 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); loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
updateLoanCounters(loan, actualDisbursementDate); updateLoanCounters(loan, actualDisbursementDate);
boolean canDisburse = loan.canDisburse(actualDisbursementDate); boolean canDisburse = loan.canDisburse(actualDisbursementDate);
ChangedTransactionDetail changedTransactionDetail = null;
if (canDisburse) { if (canDisburse) {
Money amountBeforeAdjust = loan.getPrincpal(); Money amountBeforeAdjust = loan.getPrincpal();
Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate); Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
@@ -445,24 +478,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
} }
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds, changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency,
existingReversedTransactionIds, changes, firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays, existingTransactionIds, existingReversedTransactionIds, changes, firstRepaymentOnDate, isHolidayEnabled, holidays,
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule, restCalendarInstance); workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule,
restCalendarInstance);
} }
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
try {
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(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); }
}
final String noteText = command.stringValueOfParameterNamed("note"); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
@@ -470,11 +493,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); 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); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -542,7 +560,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
existingReversedTransactionIds, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); existingReversedTransactionIds, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
this.loanRepository.saveAndFlush(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
this.accountTransfersWritePlatformService.reverseAllTransactions(loanId, PortfolioAccountType.LOAN); this.accountTransfersWritePlatformService.reverseAllTransactions(loanId, PortfolioAccountType.LOAN);
String noteText = null; String noteText = null;
if (command.hasParameter("note")) { 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 * only in edge cases (when a adjustment is made before the latest
* payment recorded against the loan) * payment recorded against the loan)
***/ ***/
this.loanRepository.saveAndFlush(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue()); 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 * only in edge cases (when a waiver is made before the latest payment
* recorded against the loan) * recorded against the loan)
***/ ***/
this.loanRepository.saveAndFlush(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue()); this.loanTransactionRepository.save(mapEntry.getValue());
@@ -898,7 +916,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanTransactionRepository.save(mapEntry.getValue()); this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
} }
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
final String noteText = command.stringValueOfParameterNamed("note"); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText); changes.put("note", noteText);
@@ -968,7 +986,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanTransactionRepository.save(mapEntry.getValue()); this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
} }
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
final String noteText = command.stringValueOfParameterNamed("note"); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
@@ -1026,7 +1044,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.closeAsMarkedForReschedule(command, defaultLoanLifecycleStateMachine(), changes); loan.closeAsMarkedForReschedule(command, defaultLoanLifecycleStateMachine(), changes);
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
final String noteText = command.stringValueOfParameterNamed("note"); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { 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 * entered only in edge cases (when a payment is made before the
* latest payment recorded against the loan) * latest payment recorded against the loan)
***/ ***/
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue()); this.loanTransactionRepository.save(mapEntry.getValue());
@@ -1155,7 +1173,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Map<String, Object> changes = loan.updateLoanCharge(loanCharge, command); final Map<String, Object> changes = loan.updateLoanCharge(loanCharge, command);
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -1220,7 +1238,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber); existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber);
this.loanTransactionRepository.save(waiveTransaction); this.loanTransactionRepository.save(waiveTransaction);
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); 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_CHARGE_CANNOT_BE_DELETED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); }
loan.removeLoanCharge(loanCharge); loan.removeLoanCharge(loanCharge);
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -1448,7 +1466,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.setLoanStatus(LoanStatus.TRANSFER_IN_PROGRESS.getValue()); loan.setLoanStatus(LoanStatus.TRANSFER_IN_PROGRESS.getValue());
this.loanTransactionRepository.save(newTransferTransaction); this.loanTransactionRepository.save(newTransferTransaction);
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
@@ -1477,7 +1495,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
this.loanTransactionRepository.save(newTransferAcceptanceTransaction); this.loanTransactionRepository.save(newTransferAcceptanceTransaction);
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
@@ -1497,7 +1515,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.setLoanStatus(LoanStatus.ACTIVE.getValue()); loan.setLoanStatus(LoanStatus.ACTIVE.getValue());
this.loanTransactionRepository.save(newTransferAcceptanceTransaction); this.loanTransactionRepository.save(newTransferAcceptanceTransaction);
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
@@ -1509,7 +1527,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public void rejectLoanTransfer(final Long accountId) { public void rejectLoanTransfer(final Long accountId) {
final Loan loan = this.loanAssembler.assembleFrom(accountId); final Loan loan = this.loanAssembler.assembleFrom(accountId);
loan.setLoanStatus(LoanStatus.TRANSFER_ON_HOLD.getValue()); loan.setLoanStatus(LoanStatus.TRANSFER_ON_HOLD.getValue());
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
} }
@Transactional @Transactional
@@ -1534,7 +1552,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment); loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment);
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -1570,7 +1588,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); } if (!loan.hasLoanOfficer(fromLoanOfficer)) { throw new LoanOfficerAssignmentException(loanId, fromLoanOfficerId); }
loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment); loan.reassignLoanOfficer(toLoanOfficer, dateOfLoanOfficerAssignment);
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
} }
this.loanRepository.flush(); this.loanRepository.flush();
@@ -1598,7 +1616,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.removeLoanOfficer(dateOfLoanOfficerunAssigned); loan.removeLoanOfficer(dateOfLoanOfficerunAssigned);
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -1644,7 +1662,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled, loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
holidays, workingDays); 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.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
} }
} }
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
@@ -2064,7 +2082,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
existingTransactionIds, existingReversedTransactionIds, changes, this.loanScheduleFactory, applicationCurrency, existingTransactionIds, existingReversedTransactionIds, changes, this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance); calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance);
this.loanRepository.saveAndFlush(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -2191,7 +2209,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance,
existingTransactionIds, existingReversedTransactionIds); existingTransactionIds, existingReversedTransactionIds);
this.loanRepository.save(loan); saveLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {