MIFOSX-90: support modification of loan charge, fix issue with repayments paying off loan charges in wrong order.
This commit is contained in:
@@ -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 })
|
||||
|
||||
+2
@@ -19,5 +19,7 @@ public interface ChargeReadPlatformService {
|
||||
|
||||
Collection<LoanChargeData> retrieveLoanCharges(Long loanId);
|
||||
|
||||
LoanChargeData retrieveLoanChargeDetails(Long loanChargeId, Long loanId);
|
||||
|
||||
ChargeData retrieveLoanChargeTemplate();
|
||||
}
|
||||
|
||||
+12
@@ -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) {
|
||||
|
||||
+18
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user