diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java index 8016ba11f..e67a6c5b2 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java @@ -493,7 +493,6 @@ public class LoansApiResource { public String retrieveNewLoanChargeDetails(@Context final UriInfo uriInfo) { final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); final boolean feeChargesOnly = false; @@ -503,6 +502,23 @@ public class LoansApiResource { return this.apiJsonSerializerService.serializeLoanChargeDataToJson(prettyPrint, responseParameters, loanChargeTemplate); } + @GET + @Path("{loanId}/charges/{chargeId}") + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + public String retrieveLoanCharge( + @PathParam("loanId") final Long loanId, + @PathParam("chargeId") final Long loanChargeId, + @Context final UriInfo uriInfo){ + + final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); + final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); + + final LoanChargeData loanCharge = this.chargeReadPlatformService.retrieveLoanChargeDetails(loanChargeId, loanId); + + return this.apiJsonSerializerService.serializeLoanChargeDataToJson(prettyPrint, responseParameters, loanCharge); + } + @PUT @Path("{loanId}/charges/{chargeId}") @Consumes({ MediaType.APPLICATION_JSON }) diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/charge/service/ChargeReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosng/platform/charge/service/ChargeReadPlatformService.java index bf1cb22aa..72e6d588d 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/charge/service/ChargeReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/charge/service/ChargeReadPlatformService.java @@ -19,5 +19,7 @@ public interface ChargeReadPlatformService { Collection retrieveLoanCharges(Long loanId); + LoanChargeData retrieveLoanChargeDetails(Long loanChargeId, Long loanId); + ChargeData retrieveLoanChargeTemplate(); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/charge/service/ChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/charge/service/ChargeReadPlatformServiceImpl.java index 65a5d6fcf..84e5cb3df 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/charge/service/ChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/charge/service/ChargeReadPlatformServiceImpl.java @@ -101,11 +101,23 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService final LoanChargeMapper rm = new LoanChargeMapper(); + // TODO - KW - check to see if its important that 'c.is_deleted=0' is required here? final String sql = "select " + rm.loanChargeSchema() + " where c.is_deleted=0 and lc.loan_id=? " + " order by lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC"; return this.jdbcTemplate.query(sql, rm, new Object[] {loanId}); } + + @Override + public LoanChargeData retrieveLoanChargeDetails(final Long id, final Long loanId) { + this.context.authenticatedUser(); + + final LoanChargeMapper rm = new LoanChargeMapper(); + + final String sql = "select " + rm.loanChargeSchema() + " where lc.id=? and lc.loan_id=?"; + + return this.jdbcTemplate.queryForObject(sql, rm, new Object[] {id, loanId}); + } @Override public Collection retrieveLoanApplicableCharges(final boolean feeChargesOnly) { diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/AbstractLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/AbstractLoanRepaymentScheduleTransactionProcessor.java index 010071788..b7e22d763 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -125,13 +125,29 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen Money amountRemaining = feeCharges; for (LoanCharge loanCharge : charges) { if (!loanCharge.isDueAtDisbursement()) { - if (loanCharge.isPenaltyCharge() && loanCharge.isNotFullyPaid() && amountRemaining.isGreaterThanZero()) { - amountRemaining = loanCharge.updatePaidAmountBy(amountRemaining); + + if (loanCharge.isPenaltyCharge() && amountRemaining.isGreaterThanZero()) { + final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges); + amountRemaining = unpaidCharge.updatePaidAmountBy(amountRemaining); } } } } + private LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(Set charges) { + LoanCharge earliestUnpaidCharge = null; + + for (LoanCharge loanCharge : charges) { + if (loanCharge.isNotFullyPaid() && !loanCharge.isDueAtDisbursement()) { + if (earliestUnpaidCharge == null || loanCharge.getDueForCollectionAsOfLocalDate().isBefore(earliestUnpaidCharge.getDueForCollectionAsOfLocalDate())) { + earliestUnpaidCharge = loanCharge; + } + } + } + + return earliestUnpaidCharge; + } + @Override public void handleWriteOff( final LoanTransaction loanTransaction, diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java index dcddf13f0..135b88f26 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java @@ -370,10 +370,22 @@ public class Loan extends AbstractAuditableCustom { public void updateLoanCharge(final LoanCharge loanCharge, final LoanChargeCommand command) { + validateLoanIsNotClosed(loanCharge); + if (this.charges.contains(loanCharge)) { loanCharge.update(command, getPrincpal().getAmount()); updateTotalChargesDueAtDisbursement(); } + + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy); + if (!loanCharge.isDueAtDisbursement()) { + final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, this.charges); + } else { + // reprocess loan schedule based on charge been waived. + LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges); + } } public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine) { @@ -398,15 +410,15 @@ public class Loan extends AbstractAuditableCustom { final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, amountWaived, transactionDate,feeChargesWaived, penaltyChargesWaived); - // reprocess loan schedule based on charge been added. - LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges); - // Waive of charges whose due date falls after latest 'repayment' transaction dont require entire loan schedule to be reprocessed. final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy); if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, this.charges); + } else { + // reprocess loan schedule based on charge been waived. + LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges); } doPostLoanTransactionChecks(waiveLoanChargeTransaction.getTransactionDate(), loanLifecycleStateMachine); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanCharge.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanCharge.java index 65d5acee4..9ec9d1b48 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanCharge.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanCharge.java @@ -256,6 +256,7 @@ public class LoanCharge extends AbstractPersistable { public Money waive(final MonetaryCurrency currency) { this.amountWaived = this.amount; this.amountOutstanding = calculateAmountOutstanding(currency); + this.paid = determineIfFullyPaid(); return getAmountWaived(currency); } @@ -349,7 +350,19 @@ public class LoanCharge extends AbstractPersistable { amountPaidLocal = this.amountPaid; } - return this.amount.subtract(amountPaidLocal); + BigDecimal amountWaivedLocal = BigDecimal.ZERO; + if (this.amountWaived != null) { + amountWaivedLocal = this.amountWaived; + } + + BigDecimal amountWrittenOffLocal = BigDecimal.ZERO; + if (this.amountWrittenOff != null) { + amountWrittenOffLocal = this.amountWrittenOff; + } + + final BigDecimal totalAccountedFor = amountPaidLocal.add(amountWaivedLocal).add(amountWrittenOffLocal); + + return this.amount.subtract(totalAccountedFor); } private BigDecimal percentageOf(final BigDecimal value, final BigDecimal percentage) { @@ -452,6 +465,8 @@ public class LoanCharge extends AbstractPersistable { this.amountOutstanding = amountExpected.minus(amountPaidToDate).getAmount(); } + this.paid = determineIfFullyPaid(); + return incrementBy.minus(amountPaidOnThisCharge); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java index a775616fe..8fe77e4ca 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -662,7 +662,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); loan.updateLoanCharge(loanCharge, command); - this.loanRepository.saveAndFlush(loan); + + this.loanRepository.save(loan); return new EntityIdentifier(loanCharge.getId()); }