MIFOSX-32: support ability to modify loan application (or loan contract).

This commit is contained in:
Keith Woodlock
2012-09-06 16:57:05 +01:00
parent 756729ae8e
commit a3eed0fdc2
13 changed files with 325 additions and 106 deletions
@@ -4,13 +4,13 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE; import javax.ws.rs.DELETE;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
@@ -25,10 +25,9 @@ import org.mifosng.platform.api.commands.AdjustLoanTransactionCommand;
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand; import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
import org.mifosng.platform.api.commands.LoanStateTransitionCommand; import org.mifosng.platform.api.commands.LoanStateTransitionCommand;
import org.mifosng.platform.api.commands.LoanTransactionCommand; import org.mifosng.platform.api.commands.LoanTransactionCommand;
import org.mifosng.platform.api.commands.SubmitLoanApplicationCommand; import org.mifosng.platform.api.commands.LoanApplicationCommand;
import org.mifosng.platform.api.commands.UndoStateTransitionCommand; import org.mifosng.platform.api.commands.UndoStateTransitionCommand;
import org.mifosng.platform.api.data.ChargeData; import org.mifosng.platform.api.data.ChargeData;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.EntityIdentifier; import org.mifosng.platform.api.data.EntityIdentifier;
import org.mifosng.platform.api.data.EnumOptionData; import org.mifosng.platform.api.data.EnumOptionData;
import org.mifosng.platform.api.data.FundData; import org.mifosng.platform.api.data.FundData;
@@ -47,7 +46,6 @@ import org.mifosng.platform.api.infrastructure.ApiDataConversionService;
import org.mifosng.platform.api.infrastructure.ApiJsonSerializerService; import org.mifosng.platform.api.infrastructure.ApiJsonSerializerService;
import org.mifosng.platform.api.infrastructure.ApiParameterHelper; import org.mifosng.platform.api.infrastructure.ApiParameterHelper;
import org.mifosng.platform.charge.service.ChargeReadPlatformService; import org.mifosng.platform.charge.service.ChargeReadPlatformService;
import org.mifosng.platform.currency.service.CurrencyReadPlatformService;
import org.mifosng.platform.exceptions.UnrecognizedQueryParamException; import org.mifosng.platform.exceptions.UnrecognizedQueryParamException;
import org.mifosng.platform.fund.service.FundReadPlatformService; import org.mifosng.platform.fund.service.FundReadPlatformService;
import org.mifosng.platform.loan.service.CalculationPlatformService; import org.mifosng.platform.loan.service.CalculationPlatformService;
@@ -167,14 +165,14 @@ public class LoansApiResource {
} }
Collection<LoanProductLookup> productOptions = new ArrayList<LoanProductLookup>(); Collection<LoanProductLookup> productOptions = new ArrayList<LoanProductLookup>();
List<EnumOptionData> loanTermFrequencyTypeOptions = new ArrayList<EnumOptionData>(); Collection<EnumOptionData> loanTermFrequencyTypeOptions = new ArrayList<EnumOptionData>();
List<EnumOptionData> repaymentFrequencyTypeOptions = new ArrayList<EnumOptionData>(); Collection<EnumOptionData> repaymentFrequencyTypeOptions = new ArrayList<EnumOptionData>();
Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions = new ArrayList<TransactionProcessingStrategyData>(); Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions = new ArrayList<TransactionProcessingStrategyData>();
List<EnumOptionData> interestRateFrequencyTypeOptions = new ArrayList<EnumOptionData>(); Collection<EnumOptionData> interestRateFrequencyTypeOptions = new ArrayList<EnumOptionData>();
List<EnumOptionData> amortizationTypeOptions = new ArrayList<EnumOptionData>(); Collection<EnumOptionData> amortizationTypeOptions = new ArrayList<EnumOptionData>();
List<EnumOptionData> interestTypeOptions = new ArrayList<EnumOptionData>(); Collection<EnumOptionData> interestTypeOptions = new ArrayList<EnumOptionData>();
List<EnumOptionData> interestCalculationPeriodTypeOptions = new ArrayList<EnumOptionData>(); Collection<EnumOptionData> interestCalculationPeriodTypeOptions = new ArrayList<EnumOptionData>();
Collection<FundData> fundOptions = new ArrayList<FundData>(); Collection<FundData> fundOptions = new ArrayList<FundData>();
final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters()); final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters());
@@ -195,10 +193,10 @@ public class LoansApiResource {
transactionProcessingStrategyOptions = this.dropdownReadPlatformService.retreiveTransactionProcessingStrategies(); transactionProcessingStrategyOptions = this.dropdownReadPlatformService.retreiveTransactionProcessingStrategies();
} }
LoanAccountData loanAccount = new LoanAccountData(loanBasicDetails, summary, repaymentSchedule, loanRepayments, permissions, charges, final LoanAccountData loanAccount = new LoanAccountData(loanBasicDetails, summary, repaymentSchedule, loanRepayments, permissions, charges,
productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions, productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions, transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions,
fundOptions); amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions);
return this.apiJsonSerializerService.serialzieLoanAccountDataToJson(prettyPrint, responseParameters, loanAccount); return this.apiJsonSerializerService.serialzieLoanAccountDataToJson(prettyPrint, responseParameters, loanAccount);
} }
@@ -210,8 +208,7 @@ public class LoansApiResource {
@QueryParam("command") final String commandParam, @QueryParam("command") final String commandParam,
final String jsonRequestBody) { final String jsonRequestBody) {
SubmitLoanApplicationCommand command = this.apiDataConversionService LoanApplicationCommand command = this.apiDataConversionService.convertJsonToLoanApplicationCommand(null, jsonRequestBody);
.convertJsonToSubmitLoanApplicationCommand(jsonRequestBody);
CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand(); CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateLoanScheduleCommand); LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateLoanScheduleCommand);
@@ -228,6 +225,33 @@ public class LoansApiResource {
return Response.ok().entity(identifier).build(); return Response.ok().entity(identifier).build();
} }
@PUT
@Path("{loanId}")
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public Response modifyLoanApplication(
@PathParam("loanId") final Long loanId,
@QueryParam("command") final String commandParam,
final String jsonRequestBody) {
final LoanApplicationCommand command = this.apiDataConversionService.convertJsonToLoanApplicationCommand(loanId, jsonRequestBody);
final CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
final LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateLoanScheduleCommand);
// for now just auto generating the loan schedule and setting support
// for 'manual' loan schedule creation later.
command.setLoanSchedule(loanSchedule);
if (is(commandParam, "calculateLoanSchedule")) {
return Response.ok().entity(loanSchedule).build();
}
EntityIdentifier identifier = this.loanWritePlatformService.modifyLoanApplication(command);
return Response.ok().entity(identifier).build();
}
@DELETE @DELETE
@Path("{loanId}") @Path("{loanId}")
@@ -1,6 +1,7 @@
package org.mifosng.platform.api.commands; package org.mifosng.platform.api.commands;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Set;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosng.platform.api.data.LoanSchedule; import org.mifosng.platform.api.data.LoanSchedule;
@@ -8,8 +9,9 @@ import org.mifosng.platform.api.data.LoanSchedule;
/** /**
* Immutable command for submitting new loan application. * Immutable command for submitting new loan application.
*/ */
public class SubmitLoanApplicationCommand { public class LoanApplicationCommand {
private final Long loanId;
private final Long clientId; private final Long clientId;
private final Long productId; private final Long productId;
private final String externalId; private final String externalId;
@@ -42,8 +44,12 @@ public class SubmitLoanApplicationCommand {
private final LoanChargeCommand[] charges; private final LoanChargeCommand[] charges;
private LoanSchedule loanSchedule; private LoanSchedule loanSchedule;
private final Set<String> modifiedParameters;
public SubmitLoanApplicationCommand( public LoanApplicationCommand(
final Set<String> modifiedParameters,
final Long loanId,
final Long clientId, final Long productId, final String externalId, final Long clientId, final Long productId, final String externalId,
final Long fundId, final Long transactionProcessingStrategyId, final Long fundId, final Long transactionProcessingStrategyId,
final LocalDate submittedOnDate, final String submittedOnNote, final LocalDate submittedOnDate, final String submittedOnNote,
@@ -58,6 +64,8 @@ public class SubmitLoanApplicationCommand {
final Integer repaymentEvery, final Integer repaymentFrequency, final Integer numberOfRepayments, Integer amortizationMethod, final Integer repaymentEvery, final Integer repaymentFrequency, final Integer numberOfRepayments, Integer amortizationMethod,
final Integer loanTermFrequency, final Integer loanTermFrequencyType, final Integer loanTermFrequency, final Integer loanTermFrequencyType,
final BigDecimal toleranceAmount, final LoanChargeCommand[] charges) { final BigDecimal toleranceAmount, final LoanChargeCommand[] charges) {
this.modifiedParameters = modifiedParameters;
this.loanId = loanId;
this.clientId = clientId; this.clientId = clientId;
this.productId = productId; this.productId = productId;
this.externalId = externalId; this.externalId = externalId;
@@ -101,6 +109,10 @@ public class SubmitLoanApplicationCommand {
public void setLoanSchedule(LoanSchedule loanSchedule) { public void setLoanSchedule(LoanSchedule loanSchedule) {
this.loanSchedule = loanSchedule; this.loanSchedule = loanSchedule;
} }
public Long getLoanId() {
return loanId;
}
public Long getClientId() { public Long getClientId() {
return clientId; return clientId;
@@ -194,4 +206,45 @@ public class SubmitLoanApplicationCommand {
return charges; return charges;
} }
public LoanProductCommand toLoanProductCommand() {
// TODO - FIX UP FOR CHARGES.
return new LoanProductCommand(modifiedParameters, this.productId, null, null,
this.fundId, this.transactionProcessingStrategyId,
null, null,
this.principal, this.inArrearsTolerance,
this.numberOfRepayments, this.repaymentEvery, this.interestRatePerPeriod,
this.repaymentFrequencyType, this.interestRateFrequencyType,
this.amortizationType, this.interestType,
this.interestCalculationPeriodType,
null);
}
public boolean isClientChanged() {
return this.modifiedParameters.contains("clientId");
}
public boolean isProductChanged() {
return this.modifiedParameters.contains("productId");
}
public boolean isFundChanged() {
return this.modifiedParameters.contains("fundId");
}
public boolean isTransactionStrategyChanged() {
return this.modifiedParameters.contains("transactionProcessingStrategyId");
}
public boolean isTermFrequencyChanged() {
return this.modifiedParameters.contains("loanTermFrequency");
}
public boolean isTermFrequencyTypeChanged() {
return this.modifiedParameters.contains("loanTermFrequencyType");
}
public boolean isSubmittedOnDateChanged() {
return this.modifiedParameters.contains("submittedOnDate");
}
} }
@@ -2,7 +2,6 @@ package org.mifosng.platform.api.data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
@@ -52,9 +51,13 @@ public class LoanAccountData {
// template // template
private final Collection<LoanProductLookup> productOptions; private final Collection<LoanProductLookup> productOptions;
private final List<EnumOptionData> termFrequencyTypeOptions; private final Collection<EnumOptionData> termFrequencyTypeOptions;
private final List<EnumOptionData> repaymentFrequencyTypeOptions; private final Collection<EnumOptionData> repaymentFrequencyTypeOptions;
private final Collection<TransactionProcessingStrategyData> repaymentStrategyOptions; private final Collection<TransactionProcessingStrategyData> repaymentStrategyOptions;
private final Collection<EnumOptionData> interestRateFrequencyTypeOptions;
private final Collection<EnumOptionData> amortizationTypeOptions;
private final Collection<EnumOptionData> interestTypeOptions;
private final Collection<EnumOptionData> interestCalculationPeriodTypeOptions;
private final Collection<FundData> fundOptions; private final Collection<FundData> fundOptions;
// associations // associations
@@ -64,8 +67,6 @@ public class LoanAccountData {
private final LoanPermissionData permissions; private final LoanPermissionData permissions;
private final LoanConvenienceData convenienceData; private final LoanConvenienceData convenienceData;
private final List<EnumOptionData> interestRateFrequencyTypeOptions;
public LoanAccountData( public LoanAccountData(
final LoanBasicDetailsData basicDetails, final LoanBasicDetailsData basicDetails,
@@ -75,10 +76,13 @@ public class LoanAccountData {
final LoanPermissionData permissions, final LoanPermissionData permissions,
final Collection<ChargeData> charges, final Collection<ChargeData> charges,
final Collection<LoanProductLookup> productOptions, final Collection<LoanProductLookup> productOptions,
final List<EnumOptionData> termFrequencyTypeOptions, final Collection<EnumOptionData> termFrequencyTypeOptions,
final List<EnumOptionData> repaymentFrequencyTypeOptions, final Collection<EnumOptionData> repaymentFrequencyTypeOptions,
final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions, final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions,
final List<EnumOptionData> interestRateFrequencyTypeOptions, final Collection<EnumOptionData> interestRateFrequencyTypeOptions,
final Collection<EnumOptionData> amortizationTypeOptions,
final Collection<EnumOptionData> interestTypeOptions,
final Collection<EnumOptionData> interestCalculationPeriodTypeOptions,
final Collection<FundData> fundOptions) { final Collection<FundData> fundOptions) {
this.summary = summary; this.summary = summary;
this.repaymentSchedule = repaymentSchedule; this.repaymentSchedule = repaymentSchedule;
@@ -90,6 +94,9 @@ public class LoanAccountData {
this.repaymentFrequencyTypeOptions = repaymentFrequencyTypeOptions; this.repaymentFrequencyTypeOptions = repaymentFrequencyTypeOptions;
this.repaymentStrategyOptions = transactionProcessingStrategyOptions; this.repaymentStrategyOptions = transactionProcessingStrategyOptions;
this.interestRateFrequencyTypeOptions = interestRateFrequencyTypeOptions; this.interestRateFrequencyTypeOptions = interestRateFrequencyTypeOptions;
this.amortizationTypeOptions = amortizationTypeOptions;
this.interestTypeOptions = interestTypeOptions;
this.interestCalculationPeriodTypeOptions = interestCalculationPeriodTypeOptions;
this.fundOptions = fundOptions; this.fundOptions = fundOptions;
int maxSubmittedOnOffsetFromToday = basicDetails int maxSubmittedOnOffsetFromToday = basicDetails
@@ -315,27 +322,39 @@ public class LoanAccountData {
public void setCharges(Collection<ChargeData> charges) { public void setCharges(Collection<ChargeData> charges) {
this.charges = charges; this.charges = charges;
} }
public Collection<LoanProductLookup> getProductOptions() { public Collection<LoanProductLookup> getProductOptions() {
return productOptions; return productOptions;
} }
public List<EnumOptionData> getTermFrequencyTypeOptions() { public Collection<EnumOptionData> getTermFrequencyTypeOptions() {
return termFrequencyTypeOptions; return termFrequencyTypeOptions;
} }
public List<EnumOptionData> getRepaymentFrequencyTypeOptions() { public Collection<EnumOptionData> getRepaymentFrequencyTypeOptions() {
return repaymentFrequencyTypeOptions; return repaymentFrequencyTypeOptions;
} }
public Collection<TransactionProcessingStrategyData> getRepaymentStrategyOptions() { public Collection<TransactionProcessingStrategyData> getRepaymentStrategyOptions() {
return repaymentStrategyOptions; return repaymentStrategyOptions;
} }
public List<EnumOptionData> getInterestRateFrequencyTypeOptions() { public Collection<EnumOptionData> getInterestRateFrequencyTypeOptions() {
return interestRateFrequencyTypeOptions; return interestRateFrequencyTypeOptions;
} }
public Collection<EnumOptionData> getAmortizationTypeOptions() {
return amortizationTypeOptions;
}
public Collection<EnumOptionData> getInterestTypeOptions() {
return interestTypeOptions;
}
public Collection<EnumOptionData> getInterestCalculationPeriodTypeOptions() {
return interestCalculationPeriodTypeOptions;
}
public Collection<FundData> getFundOptions() { public Collection<FundData> getFundOptions() {
return fundOptions; return fundOptions;
} }
@@ -19,7 +19,7 @@ import org.mifosng.platform.api.commands.OrganisationCurrencyCommand;
import org.mifosng.platform.api.commands.RoleCommand; import org.mifosng.platform.api.commands.RoleCommand;
import org.mifosng.platform.api.commands.SavingProductCommand; import org.mifosng.platform.api.commands.SavingProductCommand;
import org.mifosng.platform.api.commands.StaffCommand; import org.mifosng.platform.api.commands.StaffCommand;
import org.mifosng.platform.api.commands.SubmitLoanApplicationCommand; import org.mifosng.platform.api.commands.LoanApplicationCommand;
import org.mifosng.platform.api.commands.UserCommand; import org.mifosng.platform.api.commands.UserCommand;
public interface ApiDataConversionService { public interface ApiDataConversionService {
@@ -46,7 +46,7 @@ public interface ApiDataConversionService {
GroupCommand convertJsonToGroupCommand(Long resourceIdentifier, String jsonRequestBody); GroupCommand convertJsonToGroupCommand(Long resourceIdentifier, String jsonRequestBody);
SubmitLoanApplicationCommand convertJsonToSubmitLoanApplicationCommand(String jsonRequestBody); LoanApplicationCommand convertJsonToLoanApplicationCommand(Long resourceIdentifier, String jsonRequestBody);
LoanStateTransitionCommand convertJsonToLoanStateTransitionCommand(Long resourceIdentifier, String jsonRequestBody); LoanStateTransitionCommand convertJsonToLoanStateTransitionCommand(Long resourceIdentifier, String jsonRequestBody);
@@ -37,7 +37,7 @@ import org.mifosng.platform.api.commands.OrganisationCurrencyCommand;
import org.mifosng.platform.api.commands.RoleCommand; import org.mifosng.platform.api.commands.RoleCommand;
import org.mifosng.platform.api.commands.SavingProductCommand; import org.mifosng.platform.api.commands.SavingProductCommand;
import org.mifosng.platform.api.commands.StaffCommand; import org.mifosng.platform.api.commands.StaffCommand;
import org.mifosng.platform.api.commands.SubmitLoanApplicationCommand; import org.mifosng.platform.api.commands.LoanApplicationCommand;
import org.mifosng.platform.api.commands.UserCommand; import org.mifosng.platform.api.commands.UserCommand;
import org.mifosng.platform.api.data.ApiParameterError; import org.mifosng.platform.api.data.ApiParameterError;
import org.mifosng.platform.api.errorhandling.InvalidJsonException; import org.mifosng.platform.api.errorhandling.InvalidJsonException;
@@ -430,7 +430,7 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
} }
@Override @Override
public SubmitLoanApplicationCommand convertJsonToSubmitLoanApplicationCommand(final String json) { public LoanApplicationCommand convertJsonToLoanApplicationCommand(final Long resourceIdentifier, final String json) {
if (StringUtils.isBlank(json)) { if (StringUtils.isBlank(json)) {
throw new InvalidJsonException(); throw new InvalidJsonException();
@@ -516,7 +516,8 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
} }
} }
return new SubmitLoanApplicationCommand(clientId, productId, externalId, fundId, transactionProcessingStrategyId, return new LoanApplicationCommand(modifiedParameters,
resourceIdentifier, clientId, productId, externalId, fundId, transactionProcessingStrategyId,
submittedOnDate, submittedOnNote, submittedOnDate, submittedOnNote,
expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate, expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate,
principal, interestRatePerPeriod, interestRateFrequencyTypeValue, interestTypeValue, interestCalculationPeriodTypeValue, principal, interestRatePerPeriod, interestRateFrequencyTypeValue, interestTypeValue, interestCalculationPeriodTypeValue,
@@ -83,7 +83,7 @@ public class Note extends AbstractAuditableCustom<AppUser, Long> {
private Note(Loan loan, String note) { private Note(Loan loan, String note) {
this.loan = loan; this.loan = loan;
this.client = loan.getClient(); this.client = loan.client();
this.note = note; this.note = note;
this.noteTypeId = NoteType.LOAN.getValue(); this.noteTypeId = NoteType.LOAN.getValue();
} }
@@ -91,7 +91,7 @@ public class Note extends AbstractAuditableCustom<AppUser, Long> {
private Note(Loan loan, LoanTransaction loanTransaction, String note) { private Note(Loan loan, LoanTransaction loanTransaction, String note) {
this.loan = loan; this.loan = loan;
this.loanTransaction = loanTransaction; this.loanTransaction = loanTransaction;
this.client = loan.getClient(); this.client = loan.client();
this.note = note; this.note = note;
this.noteTypeId = NoteType.LOAN_TRANSACTION.getValue(); this.noteTypeId = NoteType.LOAN_TRANSACTION.getValue();
} }
@@ -11,8 +11,6 @@ import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Embedded; import javax.persistence.Embedded;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
@@ -29,6 +27,10 @@ import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption; import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosng.platform.api.commands.LoanApplicationCommand;
import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.client.domain.Client; import org.mifosng.platform.client.domain.Client;
import org.mifosng.platform.currency.domain.MonetaryCurrency; import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
@@ -44,12 +46,13 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@ManyToOne @ManyToOne
@JoinColumn(name = "client_id", nullable = false) @JoinColumn(name = "client_id", nullable = false)
private final Client client; private Client client;
@ManyToOne @ManyToOne
@JoinColumn(name = "product_id") @JoinColumn(name = "product_id")
private final LoanProduct loanProduct; private LoanProduct loanProduct;
@SuppressWarnings("unused")
@ManyToOne @ManyToOne
@JoinColumn(name = "fund_id", nullable = true) @JoinColumn(name = "fund_id", nullable = true)
private Fund fund; private Fund fund;
@@ -62,16 +65,15 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
private String externalId; private String externalId;
@Embedded @Embedded
private final LoanProductRelatedDetail loanRepaymentScheduleDetail; private LoanProductRelatedDetail loanRepaymentScheduleDetail;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Column(name = "term_frequency", nullable = false) @Column(name = "term_frequency", nullable = false)
private Integer termFrequency; private Integer termFrequency;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Enumerated(EnumType.ORDINAL)
@Column(name = "term_period_frequency_enum", nullable = false) @Column(name = "term_period_frequency_enum", nullable = false)
private PeriodFrequencyType termPeriodFrequencyType; private Integer termPeriodFrequencyType;
@Column(name = "loan_status_id", nullable = false) @Column(name = "loan_status_id", nullable = false)
private Integer loanStatus; private Integer loanStatus;
@@ -184,21 +186,76 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.interestRebateOwed = BigDecimal.ZERO; this.interestRebateOwed = BigDecimal.ZERO;
} }
public Client getClient() { public Client client() {
return this.client; return this.client;
} }
public Fund getFund() { public LoanProduct loanProduct() {
return this.fund;
}
public LoanProduct getLoanProduct() {
return this.loanProduct; return this.loanProduct;
} }
public LoanProductRelatedDetail getLoanRepaymentScheduleDetail() { public LoanProductRelatedDetail repaymentScheduleDetail() {
return this.loanRepaymentScheduleDetail; return this.loanRepaymentScheduleDetail;
} }
public void modifyLoanApplication(final LoanApplicationCommand command, final Client client, final LoanProduct loanProduct,
final Fund fund, final LoanTransactionProcessingStrategy strategy, final LoanSchedule modifiedLoanSchedule) {
if (command.isClientChanged()) {
this.client = client;
}
if (command.isProductChanged()) {
this.loanProduct = loanProduct;
}
if (command.isFundChanged()) {
this.fund = fund;
}
if (command.isTransactionStrategyChanged()) {
this.transactionProcessingStrategy = strategy;
}
if (command.isTermFrequencyChanged()) {
this.termFrequency = command.getLoanTermFrequency();
}
if (command.isTermFrequencyTypeChanged()) {
this.termPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType()).getValue();
}
if (command.isSubmittedOnDateChanged()) {
this.submittedOnDate = command.getSubmittedOnDate().toDate();
}
this.loanRepaymentScheduleDetail.update(command.toLoanProductCommand());
// FIXME - rewrite over loan schedule by default for now but worth putting in check to see if required
// i.e. only a client change wouldn't require it, only if one of parameters related to loan schedule calculation is changed.
this.repaymentScheduleInstallments.clear();
for (ScheduledLoanInstallment scheduledLoanInstallment : modifiedLoanSchedule.getScheduledLoanInstallments()) {
MoneyData readPrincipalDue = scheduledLoanInstallment.getPrincipalDue();
MoneyData readInterestDue = scheduledLoanInstallment.getInterestDue();
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
this, scheduledLoanInstallment.getInstallmentNumber(),
scheduledLoanInstallment.getPeriodEnd(), readPrincipalDue.getAmount(),
readInterestDue.getAmount());
this.addRepaymentScheduleInstallment(installment);
}
// if the loan application/contract is modified when repayments are already against it - then need to re-process it
List<LoanTransaction> repaymentsOrWaivers = new ArrayList<LoanTransaction>();
for (LoanTransaction transaction : this.loanTransactions) {
if (!transaction.isDisbursement() && transaction.isNotContra()) {
repaymentsOrWaivers.add(transaction);
}
}
LoanTransactionComparator transactionComparator = new LoanTransactionComparator();
Collections.sort(repaymentsOrWaivers, transactionComparator);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor.determineProcessor(this.transactionProcessingStrategy);
loanRepaymentScheduleTransactionProcessor.handleTransaction(repaymentsOrWaivers, getCurrency(), this.repaymentScheduleInstallments);
}
public void submitApplication( public void submitApplication(
final Integer loanTermFrequency, final Integer loanTermFrequency,
@@ -218,7 +275,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
this.termFrequency = loanTermFrequency; this.termFrequency = loanTermFrequency;
this.termPeriodFrequencyType = loanTermFrequencyType; this.termPeriodFrequencyType = loanTermFrequencyType.getValue();
this.submittedOnDate = submittedOn.toDateTimeAtCurrentTime().toDate(); this.submittedOnDate = submittedOn.toDateTimeAtCurrentTime().toDate();
@@ -257,8 +314,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
} }
public void reject(final LocalDate rejectedOn, public void reject(final LocalDate rejectedOn, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
LoanLifecycleStateMachine loanLifecycleStateMachine) {
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED, LoanStatus.fromInt(this.loanStatus)); LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED, LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
@@ -1118,5 +1174,4 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return this.loanRepaymentScheduleDetail.getPrincipal() return this.loanRepaymentScheduleDetail.getPrincipal()
.getCurrencyCode(); .getCurrencyCode();
} }
} }
@@ -17,8 +17,7 @@ import org.mifosng.platform.currency.domain.Money;
* {@link LoanProduct} that are also used and persisted by a {@link Loan}. * {@link LoanProduct} that are also used and persisted by a {@link Loan}.
*/ */
@Embeddable @Embeddable
public class LoanProductRelatedDetail implements public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentScheduleRelatedDetail {
LoanProductMinimumRepaymentScheduleRelatedDetail {
@Embedded @Embedded
private MonetaryCurrency currency; private MonetaryCurrency currency;
@@ -5,15 +5,15 @@ import java.util.List;
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand; import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
import org.mifosng.platform.api.commands.LoanChargeCommand; import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.api.commands.SubmitLoanApplicationCommand; import org.mifosng.platform.api.commands.LoanApplicationCommand;
import org.mifosng.platform.api.data.ApiParameterError; import org.mifosng.platform.api.data.ApiParameterError;
import org.mifosng.platform.exceptions.PlatformApiDataValidationException; import org.mifosng.platform.exceptions.PlatformApiDataValidationException;
public class SubmitLoanApplicationCommandValidator { public class LoanApplicationCommandValidator {
private final SubmitLoanApplicationCommand command; private final LoanApplicationCommand command;
public SubmitLoanApplicationCommandValidator(SubmitLoanApplicationCommand command) { public LoanApplicationCommandValidator(LoanApplicationCommand command) {
this.command = command; this.command = command;
} }
@@ -7,7 +7,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import org.mifosng.platform.api.commands.LoanChargeCommand; import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.api.commands.SubmitLoanApplicationCommand; import org.mifosng.platform.api.commands.LoanApplicationCommand;
import org.mifosng.platform.api.data.LoanSchedule; import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.MoneyData; import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment; import org.mifosng.platform.api.data.ScheduledLoanInstallment;
@@ -69,7 +69,7 @@ public class LoanAssembler {
this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository; this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository;
} }
public Loan assembleFrom(final SubmitLoanApplicationCommand command) { public Loan assembleFrom(final LoanApplicationCommand command) {
LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId()); LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId());
if (loanProduct == null) { if (loanProduct == null) {
@@ -82,26 +82,10 @@ public class LoanAssembler {
} }
MonetaryCurrency currency = loanProduct.getCurrency(); MonetaryCurrency currency = loanProduct.getCurrency();
final BigDecimal defaultNominalInterestRatePerPeriod = command.getInterestRatePerPeriod();
final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType());
BigDecimal defaultAnnualNominalInterestRate = aprCalculator.calculateFrom(interestPeriodFrequencyType, command.getInterestRatePerPeriod());
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType());
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodType());
final Integer loanTermFrequency = command.getLoanTermFrequency(); final Integer loanTermFrequency = command.getLoanTermFrequency();
final PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType()); final PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType());
final Integer defaultNumberOfInstallments = command.getNumberOfRepayments();
final Integer repayEvery = command.getRepaymentEvery();
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType());
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType()); LoanProductRelatedDetail loanRepaymentScheduleDetail = assembleLoanProductRelatedDetailFrom(command, currency);
LoanProductRelatedDetail loanRepaymentScheduleDetail = new LoanProductRelatedDetail(currency,
command.getPrincipal(), defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
interestMethod, interestCalculationPeriodMethod,
repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, command.getInArrearsTolerance());
LoanSchedule loanSchedule = command.getLoanSchedule(); LoanSchedule loanSchedule = command.getLoanSchedule();
List<ScheduledLoanInstallment> loanRepaymentSchedule = loanSchedule.getScheduledLoanInstallments(); List<ScheduledLoanInstallment> loanRepaymentSchedule = loanSchedule.getScheduledLoanInstallments();
@@ -136,12 +120,35 @@ public class LoanAssembler {
return loan; return loan;
} }
public LoanProductRelatedDetail assembleLoanProductRelatedDetailFrom(final LoanApplicationCommand command, MonetaryCurrency currency) {
final BigDecimal defaultNominalInterestRatePerPeriod = command.getInterestRatePerPeriod();
final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType());
BigDecimal defaultAnnualNominalInterestRate = aprCalculator.calculateFrom(interestPeriodFrequencyType, command.getInterestRatePerPeriod());
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType());
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodType());
final Integer defaultNumberOfInstallments = command.getNumberOfRepayments();
final Integer repayEvery = command.getRepaymentEvery();
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType());
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType());
LoanProductRelatedDetail loanRepaymentScheduleDetail = new LoanProductRelatedDetail(currency,
command.getPrincipal(), defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
interestMethod, interestCalculationPeriodMethod,
repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, command.getInArrearsTolerance());
return loanRepaymentScheduleDetail;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values()); List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
} }
private Fund findFundByIdIfProvided(final Long fundId) { public Fund findFundByIdIfProvided(final Long fundId) {
Fund fund = null; Fund fund = null;
if (fundId != null) { if (fundId != null) {
fund = this.fundRepository.findOne(fundId); fund = this.fundRepository.findOne(fundId);
@@ -152,7 +159,7 @@ public class LoanAssembler {
return fund; return fund;
} }
private LoanTransactionProcessingStrategy findStrategyByIdIfProvided(final Long transactionProcessingStrategyId) { public LoanTransactionProcessingStrategy findStrategyByIdIfProvided(final Long transactionProcessingStrategyId) {
LoanTransactionProcessingStrategy strategy = null; LoanTransactionProcessingStrategy strategy = null;
if (transactionProcessingStrategyId != null) { if (transactionProcessingStrategyId != null) {
strategy = this.loanTransactionProcessingStrategyRepository.findOne(transactionProcessingStrategyId); strategy = this.loanTransactionProcessingStrategyRepository.findOne(transactionProcessingStrategyId);
@@ -163,7 +170,7 @@ public class LoanAssembler {
return strategy; return strategy;
} }
private Set<LoanCharge> assembleSetOfCharges(final SubmitLoanApplicationCommand command, final Loan loan, private Set<LoanCharge> assembleSetOfCharges(final LoanApplicationCommand command, final Loan loan,
final LoanProduct product, final String currencyCode) { final LoanProduct product, final String currencyCode) {
Set<LoanCharge> charges = new HashSet<LoanCharge>(); Set<LoanCharge> charges = new HashSet<LoanCharge>();
@@ -293,7 +293,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
throw new LoanNotFoundException(loanId); throw new LoanNotFoundException(loanId);
} }
final String currencyCode = loan.getLoanRepaymentScheduleDetail() final String currencyCode = loan.repaymentScheduleDetail()
.getPrincipal().getCurrencyCode(); .getPrincipal().getCurrencyCode();
ApplicationCurrency currency = this.applicationCurrencyRepository ApplicationCurrency currency = this.applicationCurrencyRepository
.findOneByCode(currencyCode); .findOneByCode(currencyCode);
@@ -332,7 +332,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
throw new LoanNotFoundException(loanId); throw new LoanNotFoundException(loanId);
} }
final String currencyCode = loan.getLoanRepaymentScheduleDetail() final String currencyCode = loan.repaymentScheduleDetail()
.getPrincipal().getCurrencyCode(); .getPrincipal().getCurrencyCode();
ApplicationCurrency currency = this.applicationCurrencyRepository ApplicationCurrency currency = this.applicationCurrencyRepository
.findOneByCode(currencyCode); .findOneByCode(currencyCode);
@@ -368,7 +368,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
throw new LoanNotFoundException(loanId); throw new LoanNotFoundException(loanId);
} }
final String currencyCode = loan.getLoanRepaymentScheduleDetail() final String currencyCode = loan.repaymentScheduleDetail()
.getPrincipal().getCurrencyCode(); .getPrincipal().getCurrencyCode();
ApplicationCurrency currency = this.applicationCurrencyRepository ApplicationCurrency currency = this.applicationCurrencyRepository
.findOneByCode(currencyCode); .findOneByCode(currencyCode);
@@ -3,7 +3,7 @@ package org.mifosng.platform.loan.service;
import org.mifosng.platform.api.commands.AdjustLoanTransactionCommand; import org.mifosng.platform.api.commands.AdjustLoanTransactionCommand;
import org.mifosng.platform.api.commands.LoanStateTransitionCommand; import org.mifosng.platform.api.commands.LoanStateTransitionCommand;
import org.mifosng.platform.api.commands.LoanTransactionCommand; import org.mifosng.platform.api.commands.LoanTransactionCommand;
import org.mifosng.platform.api.commands.SubmitLoanApplicationCommand; import org.mifosng.platform.api.commands.LoanApplicationCommand;
import org.mifosng.platform.api.commands.UndoStateTransitionCommand; import org.mifosng.platform.api.commands.UndoStateTransitionCommand;
import org.mifosng.platform.api.data.EntityIdentifier; import org.mifosng.platform.api.data.EntityIdentifier;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@@ -11,7 +11,10 @@ import org.springframework.security.access.prepost.PreAuthorize;
public interface LoanWritePlatformService { public interface LoanWritePlatformService {
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_SUBMIT_NEW_LOAN_APPLICATION_ROLE', 'CAN_SUBMIT_HISTORIC_LOAN_APPLICATION_ROLE')") @PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_SUBMIT_NEW_LOAN_APPLICATION_ROLE', 'CAN_SUBMIT_HISTORIC_LOAN_APPLICATION_ROLE')")
EntityIdentifier submitLoanApplication(SubmitLoanApplicationCommand command); EntityIdentifier submitLoanApplication(LoanApplicationCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_SUBMIT_NEW_LOAN_APPLICATION_ROLE', 'CAN_SUBMIT_HISTORIC_LOAN_APPLICATION_ROLE')")
EntityIdentifier modifyLoanApplication(LoanApplicationCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_DELETE_LOAN_THAT_IS_SUBMITTED_AND_NOT_APPROVED')") @PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_DELETE_LOAN_THAT_IS_SUBMITTED_AND_NOT_APPROVED')")
EntityIdentifier deleteLoan(Long loanId); EntityIdentifier deleteLoan(Long loanId);
@@ -9,28 +9,36 @@ import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosng.platform.api.commands.AdjustLoanTransactionCommand; import org.mifosng.platform.api.commands.AdjustLoanTransactionCommand;
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand; import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
import org.mifosng.platform.api.commands.LoanApplicationCommand;
import org.mifosng.platform.api.commands.LoanStateTransitionCommand; import org.mifosng.platform.api.commands.LoanStateTransitionCommand;
import org.mifosng.platform.api.commands.LoanTransactionCommand; import org.mifosng.platform.api.commands.LoanTransactionCommand;
import org.mifosng.platform.api.commands.SubmitLoanApplicationCommand;
import org.mifosng.platform.api.commands.UndoStateTransitionCommand; import org.mifosng.platform.api.commands.UndoStateTransitionCommand;
import org.mifosng.platform.api.data.EntityIdentifier; import org.mifosng.platform.api.data.EntityIdentifier;
import org.mifosng.platform.api.data.LoanSchedule; import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.ScheduledLoanInstallment; import org.mifosng.platform.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.client.domain.Client;
import org.mifosng.platform.client.domain.ClientRepository;
import org.mifosng.platform.client.domain.Note; import org.mifosng.platform.client.domain.Note;
import org.mifosng.platform.client.domain.NoteRepository; import org.mifosng.platform.client.domain.NoteRepository;
import org.mifosng.platform.currency.domain.MonetaryCurrency; import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.exceptions.ClientNotFoundException;
import org.mifosng.platform.exceptions.LoanNotFoundException; import org.mifosng.platform.exceptions.LoanNotFoundException;
import org.mifosng.platform.exceptions.LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted; import org.mifosng.platform.exceptions.LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted;
import org.mifosng.platform.exceptions.LoanProductNotFoundException;
import org.mifosng.platform.exceptions.LoanTransactionNotFoundException; import org.mifosng.platform.exceptions.LoanTransactionNotFoundException;
import org.mifosng.platform.exceptions.NoAuthorizationException; import org.mifosng.platform.exceptions.NoAuthorizationException;
import org.mifosng.platform.fund.domain.Fund;
import org.mifosng.platform.loan.domain.DefaultLoanLifecycleStateMachine; import org.mifosng.platform.loan.domain.DefaultLoanLifecycleStateMachine;
import org.mifosng.platform.loan.domain.Loan; import org.mifosng.platform.loan.domain.Loan;
import org.mifosng.platform.loan.domain.LoanLifecycleStateMachine; import org.mifosng.platform.loan.domain.LoanLifecycleStateMachine;
import org.mifosng.platform.loan.domain.LoanProduct;
import org.mifosng.platform.loan.domain.LoanProductRepository;
import org.mifosng.platform.loan.domain.LoanRepaymentScheduleInstallment; import org.mifosng.platform.loan.domain.LoanRepaymentScheduleInstallment;
import org.mifosng.platform.loan.domain.LoanRepository; import org.mifosng.platform.loan.domain.LoanRepository;
import org.mifosng.platform.loan.domain.LoanStatus; import org.mifosng.platform.loan.domain.LoanStatus;
import org.mifosng.platform.loan.domain.LoanTransaction; import org.mifosng.platform.loan.domain.LoanTransaction;
import org.mifosng.platform.loan.domain.LoanTransactionProcessingStrategy;
import org.mifosng.platform.loan.domain.LoanTransactionRepository; import org.mifosng.platform.loan.domain.LoanTransactionRepository;
import org.mifosng.platform.loan.domain.PeriodFrequencyType; import org.mifosng.platform.loan.domain.PeriodFrequencyType;
import org.mifosng.platform.security.PlatformSecurityContext; import org.mifosng.platform.security.PlatformSecurityContext;
@@ -49,17 +57,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final CalculationPlatformService calculationPlatformService; private final CalculationPlatformService calculationPlatformService;
private final LoanTransactionRepository loanTransactionRepository; private final LoanTransactionRepository loanTransactionRepository;
private final LoanAssembler loanAssembler; private final LoanAssembler loanAssembler;
private final ClientRepository clientRepository;
private final LoanProductRepository loanProductRepository;
@Autowired @Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final LoanAssembler loanAssembler, public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final LoanAssembler loanAssembler,
final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository, final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository,
final NoteRepository noteRepository, final CalculationPlatformService calculationPlatformService) { final NoteRepository noteRepository, final CalculationPlatformService calculationPlatformService,
final ClientRepository clientRepository, final LoanProductRepository loanProductRepository) {
this.context = context; this.context = context;
this.loanAssembler = loanAssembler; this.loanAssembler = loanAssembler;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository; this.loanTransactionRepository = loanTransactionRepository;
this.noteRepository = noteRepository; this.noteRepository = noteRepository;
this.calculationPlatformService = calculationPlatformService; this.calculationPlatformService = calculationPlatformService;
this.clientRepository = clientRepository;
this.loanProductRepository = loanProductRepository;
} }
private boolean isBeforeToday(final LocalDate date) { private boolean isBeforeToday(final LocalDate date) {
@@ -73,11 +86,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Transactional @Transactional
@Override @Override
public EntityIdentifier submitLoanApplication(final SubmitLoanApplicationCommand command) { public EntityIdentifier submitLoanApplication(final LoanApplicationCommand command) {
AppUser currentUser = context.authenticatedUser(); AppUser currentUser = context.authenticatedUser();
SubmitLoanApplicationCommandValidator validator = new SubmitLoanApplicationCommandValidator(command); LoanApplicationCommandValidator validator = new LoanApplicationCommandValidator(command);
validator.validate(); validator.validate();
LocalDate submittedOn = command.getSubmittedOnDate(); LocalDate submittedOn = command.getSubmittedOnDate();
@@ -97,6 +110,51 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
return new EntityIdentifier(loan.getId()); return new EntityIdentifier(loan.getId());
} }
@Transactional
@Override
public EntityIdentifier modifyLoanApplication(final LoanApplicationCommand command) {
AppUser currentUser = context.authenticatedUser();
LoanApplicationCommandValidator validator = new LoanApplicationCommandValidator(command);
validator.validate();
// TODO - fix up permissions for loan modification
LocalDate submittedOn = command.getSubmittedOnDate();
if (this.isBeforeToday(submittedOn) && currentUser.hasNotPermissionForAnyOf("CAN_SUBMIT_HISTORIC_LOAN_APPLICATION_ROLE", "PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE")) {
throw new NoAuthorizationException("Cannot modify backdated loan.");
}
Loan loan = this.loanRepository.findOne(command.getLoanId());
if (loan == null) {
throw new LoanNotFoundException(command.getLoanId());
}
LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId());
if (loanProduct == null) {
throw new LoanProductNotFoundException(command.getProductId());
}
Client client = this.clientRepository.findOne(command.getClientId());
if (client == null || client.isDeleted()) {
throw new ClientNotFoundException(command.getClientId());
}
Fund fund = this.loanAssembler.findFundByIdIfProvided(command.getFundId());
LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId());
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command.toCalculateLoanScheduleCommand());
loan.modifyLoanApplication(command, client, loanProduct, fund, strategy, loanSchedule);
this.loanRepository.save(loan);
if (StringUtils.isNotBlank(command.getSubmittedOnNote())) {
Note note = Note.loanNote(loan, command.getSubmittedOnNote());
this.noteRepository.save(note);
}
return new EntityIdentifier(loan.getId());
}
@Transactional @Transactional
@Override @Override
public EntityIdentifier deleteLoan(final Long loanId) { public EntityIdentifier deleteLoan(final Long loanId) {
@@ -261,13 +319,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
LocalDate repaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate(); LocalDate repaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate();
LocalDate interestCalculatedFromDate = loan.getInterestChargedFromDate(); LocalDate interestCalculatedFromDate = loan.getInterestChargedFromDate();
BigDecimal principalAsDecimal = loan.getLoanRepaymentScheduleDetail().getPrincipal().getAmount(); BigDecimal principalAsDecimal = loan.repaymentScheduleDetail().getPrincipal().getAmount();
BigDecimal interestRatePerYear = loan.getLoanRepaymentScheduleDetail().getAnnualNominalInterestRate(); BigDecimal interestRatePerYear = loan.repaymentScheduleDetail().getAnnualNominalInterestRate();
Integer numberOfInstallments = loan.getLoanRepaymentScheduleDetail().getNumberOfRepayments(); Integer numberOfInstallments = loan.repaymentScheduleDetail().getNumberOfRepayments();
Integer repaidEvery = loan.getLoanRepaymentScheduleDetail().getRepayEvery(); Integer repaidEvery = loan.repaymentScheduleDetail().getRepayEvery();
Integer selectedRepaymentFrequency = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType().getValue(); Integer selectedRepaymentFrequency = loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType().getValue();
Integer selectedAmortizationMethod = loan.getLoanRepaymentScheduleDetail().getAmortizationMethod().getValue(); Integer selectedAmortizationMethod = loan.repaymentScheduleDetail().getAmortizationMethod().getValue();
// FIXME - use values from loan table here instead of inferring. // FIXME - use values from loan table here instead of inferring.
Integer loanTermFrequency = repaidEvery * numberOfInstallments; Integer loanTermFrequency = repaidEvery * numberOfInstallments;
@@ -277,11 +335,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
BigDecimal interestRatePerPeriod = interestRatePerYear; BigDecimal interestRatePerPeriod = interestRatePerYear;
Integer interestRateFrequencyMethod = PeriodFrequencyType.YEARS.getValue(); Integer interestRateFrequencyMethod = PeriodFrequencyType.YEARS.getValue();
Integer interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod().getValue(); Integer interestMethod = loan.repaymentScheduleDetail().getInterestMethod().getValue();
Integer interestCalculationInPeriod = loan.getLoanRepaymentScheduleDetail().getInterestCalculationPeriodMethod().getValue(); Integer interestCalculationInPeriod = loan.repaymentScheduleDetail().getInterestCalculationPeriodMethod().getValue();
CalculateLoanScheduleCommand calculateCommand = new CalculateLoanScheduleCommand( CalculateLoanScheduleCommand calculateCommand = new CalculateLoanScheduleCommand(
loan.getLoanProduct().getId(), loan.loanProduct().getId(),
principalAsDecimal, principalAsDecimal,
interestRatePerPeriod, interestRateFrequencyMethod, interestMethod, interestCalculationInPeriod, interestRatePerPeriod, interestRateFrequencyMethod, interestMethod, interestCalculationInPeriod,
repaidEvery, selectedRepaymentFrequency, numberOfInstallments, selectedAmortizationMethod, repaidEvery, selectedRepaymentFrequency, numberOfInstallments, selectedAmortizationMethod,
@@ -374,7 +432,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new NoAuthorizationException("error.msg.no.permission.to.make.repayment.on.loan.in.past"); throw new NoAuthorizationException("error.msg.no.permission.to.make.repayment.on.loan.in.past");
} }
Money repayment = Money.of(loan.getLoanRepaymentScheduleDetail() Money repayment = Money.of(loan.repaymentScheduleDetail()
.getPrincipal().getCurrency(), .getPrincipal().getCurrency(),
command.getTransactionAmount()); command.getTransactionAmount());
@@ -412,7 +470,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
Money transactionAmount = Money.of(loan Money transactionAmount = Money.of(loan
.getLoanRepaymentScheduleDetail().getPrincipal() .repaymentScheduleDetail().getPrincipal()
.getCurrency(), command.getTransactionAmount()); .getCurrency(), command.getTransactionAmount());
// adjustment is only supported for repayments and waivers at present // adjustment is only supported for repayments and waivers at present
@@ -451,7 +509,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new LoanNotFoundException(command.getLoanId()); throw new LoanNotFoundException(command.getLoanId());
} }
Money waived = Money.of(loan.getLoanRepaymentScheduleDetail() Money waived = Money.of(loan.repaymentScheduleDetail()
.getPrincipal().getCurrency(), .getPrincipal().getCurrency(),
command.getTransactionAmount()); command.getTransactionAmount());