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) {
|
public String retrieveNewLoanChargeDetails(@Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
|
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||||
|
|
||||||
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
|
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
|
||||||
|
|
||||||
final boolean feeChargesOnly = false;
|
final boolean feeChargesOnly = false;
|
||||||
@@ -503,6 +502,23 @@ public class LoansApiResource {
|
|||||||
return this.apiJsonSerializerService.serializeLoanChargeDataToJson(prettyPrint, responseParameters, loanChargeTemplate);
|
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
|
@PUT
|
||||||
@Path("{loanId}/charges/{chargeId}")
|
@Path("{loanId}/charges/{chargeId}")
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
|||||||
+2
@@ -19,5 +19,7 @@ public interface ChargeReadPlatformService {
|
|||||||
|
|
||||||
Collection<LoanChargeData> retrieveLoanCharges(Long loanId);
|
Collection<LoanChargeData> retrieveLoanCharges(Long loanId);
|
||||||
|
|
||||||
|
LoanChargeData retrieveLoanChargeDetails(Long loanChargeId, Long loanId);
|
||||||
|
|
||||||
ChargeData retrieveLoanChargeTemplate();
|
ChargeData retrieveLoanChargeTemplate();
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -101,11 +101,23 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
|
|||||||
|
|
||||||
final LoanChargeMapper rm = new LoanChargeMapper();
|
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=? " +
|
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";
|
" 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});
|
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
|
@Override
|
||||||
public Collection<ChargeData> retrieveLoanApplicableCharges(final boolean feeChargesOnly) {
|
public Collection<ChargeData> retrieveLoanApplicableCharges(final boolean feeChargesOnly) {
|
||||||
|
|||||||
+18
-2
@@ -125,13 +125,29 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
Money amountRemaining = feeCharges;
|
Money amountRemaining = feeCharges;
|
||||||
for (LoanCharge loanCharge : charges) {
|
for (LoanCharge loanCharge : charges) {
|
||||||
if (!loanCharge.isDueAtDisbursement()) {
|
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
|
@Override
|
||||||
public void handleWriteOff(
|
public void handleWriteOff(
|
||||||
final LoanTransaction loanTransaction,
|
final LoanTransaction loanTransaction,
|
||||||
|
|||||||
@@ -370,10 +370,22 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
|
|
||||||
public void updateLoanCharge(final LoanCharge loanCharge, final LoanChargeCommand command) {
|
public void updateLoanCharge(final LoanCharge loanCharge, final LoanChargeCommand command) {
|
||||||
|
|
||||||
|
validateLoanIsNotClosed(loanCharge);
|
||||||
|
|
||||||
if (this.charges.contains(loanCharge)) {
|
if (this.charges.contains(loanCharge)) {
|
||||||
loanCharge.update(command, getPrincpal().getAmount());
|
loanCharge.update(command, getPrincpal().getAmount());
|
||||||
updateTotalChargesDueAtDisbursement();
|
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) {
|
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);
|
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.
|
// 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);
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy);
|
||||||
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
|
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
|
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
|
||||||
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, this.charges);
|
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);
|
doPostLoanTransactionChecks(waiveLoanChargeTransaction.getTransactionDate(), loanLifecycleStateMachine);
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
|||||||
public Money waive(final MonetaryCurrency currency) {
|
public Money waive(final MonetaryCurrency currency) {
|
||||||
this.amountWaived = this.amount;
|
this.amountWaived = this.amount;
|
||||||
this.amountOutstanding = calculateAmountOutstanding(currency);
|
this.amountOutstanding = calculateAmountOutstanding(currency);
|
||||||
|
this.paid = determineIfFullyPaid();
|
||||||
return getAmountWaived(currency);
|
return getAmountWaived(currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,7 +350,19 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
|||||||
amountPaidLocal = this.amountPaid;
|
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) {
|
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.amountOutstanding = amountExpected.minus(amountPaidToDate).getAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.paid = determineIfFullyPaid();
|
||||||
|
|
||||||
return incrementBy.minus(amountPaidOnThisCharge);
|
return incrementBy.minus(amountPaidOnThisCharge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -662,7 +662,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
|
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
|
||||||
|
|
||||||
loan.updateLoanCharge(loanCharge, command);
|
loan.updateLoanCharge(loanCharge, command);
|
||||||
this.loanRepository.saveAndFlush(loan);
|
|
||||||
|
this.loanRepository.save(loan);
|
||||||
|
|
||||||
return new EntityIdentifier(loanCharge.getId());
|
return new EntityIdentifier(loanCharge.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user