MIFOSX-90: support modification of loan charge, fix issue with repayments paying off loan charges in wrong order.

This commit is contained in:
Keith Woodlock
2012-11-08 14:35:41 +00:00
parent f90802a292
commit 152a70477d
7 changed files with 83 additions and 9 deletions
@@ -493,7 +493,6 @@ public class LoansApiResource {
public String retrieveNewLoanChargeDetails(@Context final UriInfo uriInfo) {
final Set<String> 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<String> 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 })
@@ -19,5 +19,7 @@ public interface ChargeReadPlatformService {
Collection<LoanChargeData> retrieveLoanCharges(Long loanId);
LoanChargeData retrieveLoanChargeDetails(Long loanChargeId, Long loanId);
ChargeData retrieveLoanChargeTemplate();
}
@@ -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<ChargeData> retrieveLoanApplicableCharges(final boolean feeChargesOnly) {
@@ -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<LoanCharge> 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,
@@ -370,10 +370,22 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
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<LoanTransaction> 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<AppUser, Long> {
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<LoanTransaction> 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);
@@ -256,6 +256,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
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<Long> {
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<Long> {
this.amountOutstanding = amountExpected.minus(amountPaidToDate).getAmount();
}
this.paid = determineIfFullyPaid();
return incrementBy.minus(amountPaidOnThisCharge);
}
@@ -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());
}