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.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
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.LoanStateTransitionCommand;
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.data.ChargeData;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.EntityIdentifier;
import org.mifosng.platform.api.data.EnumOptionData;
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.ApiParameterHelper;
import org.mifosng.platform.charge.service.ChargeReadPlatformService;
import org.mifosng.platform.currency.service.CurrencyReadPlatformService;
import org.mifosng.platform.exceptions.UnrecognizedQueryParamException;
import org.mifosng.platform.fund.service.FundReadPlatformService;
import org.mifosng.platform.loan.service.CalculationPlatformService;
@@ -167,14 +165,14 @@ public class LoansApiResource {
}
Collection<LoanProductLookup> productOptions = new ArrayList<LoanProductLookup>();
List<EnumOptionData> loanTermFrequencyTypeOptions = new ArrayList<EnumOptionData>();
List<EnumOptionData> repaymentFrequencyTypeOptions = new ArrayList<EnumOptionData>();
Collection<EnumOptionData> loanTermFrequencyTypeOptions = new ArrayList<EnumOptionData>();
Collection<EnumOptionData> repaymentFrequencyTypeOptions = new ArrayList<EnumOptionData>();
Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions = new ArrayList<TransactionProcessingStrategyData>();
List<EnumOptionData> interestRateFrequencyTypeOptions = new ArrayList<EnumOptionData>();
List<EnumOptionData> amortizationTypeOptions = new ArrayList<EnumOptionData>();
List<EnumOptionData> interestTypeOptions = new ArrayList<EnumOptionData>();
List<EnumOptionData> interestCalculationPeriodTypeOptions = new ArrayList<EnumOptionData>();
Collection<EnumOptionData> interestRateFrequencyTypeOptions = new ArrayList<EnumOptionData>();
Collection<EnumOptionData> amortizationTypeOptions = new ArrayList<EnumOptionData>();
Collection<EnumOptionData> interestTypeOptions = new ArrayList<EnumOptionData>();
Collection<EnumOptionData> interestCalculationPeriodTypeOptions = new ArrayList<EnumOptionData>();
Collection<FundData> fundOptions = new ArrayList<FundData>();
final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters());
@@ -195,10 +193,10 @@ public class LoansApiResource {
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,
transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions,
fundOptions);
transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions,
amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions);
return this.apiJsonSerializerService.serialzieLoanAccountDataToJson(prettyPrint, responseParameters, loanAccount);
}
@@ -210,8 +208,7 @@ public class LoansApiResource {
@QueryParam("command") final String commandParam,
final String jsonRequestBody) {
SubmitLoanApplicationCommand command = this.apiDataConversionService
.convertJsonToSubmitLoanApplicationCommand(jsonRequestBody);
LoanApplicationCommand command = this.apiDataConversionService.convertJsonToLoanApplicationCommand(null, jsonRequestBody);
CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateLoanScheduleCommand);
@@ -228,6 +225,33 @@ public class LoansApiResource {
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
@Path("{loanId}")
@@ -1,6 +1,7 @@
package org.mifosng.platform.api.commands;
import java.math.BigDecimal;
import java.util.Set;
import org.joda.time.LocalDate;
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.
*/
public class SubmitLoanApplicationCommand {
public class LoanApplicationCommand {
private final Long loanId;
private final Long clientId;
private final Long productId;
private final String externalId;
@@ -42,8 +44,12 @@ public class SubmitLoanApplicationCommand {
private final LoanChargeCommand[] charges;
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 fundId, final Long transactionProcessingStrategyId,
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 loanTermFrequency, final Integer loanTermFrequencyType,
final BigDecimal toleranceAmount, final LoanChargeCommand[] charges) {
this.modifiedParameters = modifiedParameters;
this.loanId = loanId;
this.clientId = clientId;
this.productId = productId;
this.externalId = externalId;
@@ -101,6 +109,10 @@ public class SubmitLoanApplicationCommand {
public void setLoanSchedule(LoanSchedule loanSchedule) {
this.loanSchedule = loanSchedule;
}
public Long getLoanId() {
return loanId;
}
public Long getClientId() {
return clientId;
@@ -194,4 +206,45 @@ public class SubmitLoanApplicationCommand {
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.util.Collection;
import java.util.List;
import org.joda.time.LocalDate;
@@ -52,9 +51,13 @@ public class LoanAccountData {
// template
private final Collection<LoanProductLookup> productOptions;
private final List<EnumOptionData> termFrequencyTypeOptions;
private final List<EnumOptionData> repaymentFrequencyTypeOptions;
private final Collection<EnumOptionData> termFrequencyTypeOptions;
private final Collection<EnumOptionData> repaymentFrequencyTypeOptions;
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;
// associations
@@ -64,8 +67,6 @@ public class LoanAccountData {
private final LoanPermissionData permissions;
private final LoanConvenienceData convenienceData;
private final List<EnumOptionData> interestRateFrequencyTypeOptions;
public LoanAccountData(
final LoanBasicDetailsData basicDetails,
@@ -75,10 +76,13 @@ public class LoanAccountData {
final LoanPermissionData permissions,
final Collection<ChargeData> charges,
final Collection<LoanProductLookup> productOptions,
final List<EnumOptionData> termFrequencyTypeOptions,
final List<EnumOptionData> repaymentFrequencyTypeOptions,
final Collection<EnumOptionData> termFrequencyTypeOptions,
final Collection<EnumOptionData> repaymentFrequencyTypeOptions,
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) {
this.summary = summary;
this.repaymentSchedule = repaymentSchedule;
@@ -90,6 +94,9 @@ public class LoanAccountData {
this.repaymentFrequencyTypeOptions = repaymentFrequencyTypeOptions;
this.repaymentStrategyOptions = transactionProcessingStrategyOptions;
this.interestRateFrequencyTypeOptions = interestRateFrequencyTypeOptions;
this.amortizationTypeOptions = amortizationTypeOptions;
this.interestTypeOptions = interestTypeOptions;
this.interestCalculationPeriodTypeOptions = interestCalculationPeriodTypeOptions;
this.fundOptions = fundOptions;
int maxSubmittedOnOffsetFromToday = basicDetails
@@ -315,27 +322,39 @@ public class LoanAccountData {
public void setCharges(Collection<ChargeData> charges) {
this.charges = charges;
}
public Collection<LoanProductLookup> getProductOptions() {
return productOptions;
}
public List<EnumOptionData> getTermFrequencyTypeOptions() {
public Collection<EnumOptionData> getTermFrequencyTypeOptions() {
return termFrequencyTypeOptions;
}
public List<EnumOptionData> getRepaymentFrequencyTypeOptions() {
public Collection<EnumOptionData> getRepaymentFrequencyTypeOptions() {
return repaymentFrequencyTypeOptions;
}
public Collection<TransactionProcessingStrategyData> getRepaymentStrategyOptions() {
return repaymentStrategyOptions;
}
public List<EnumOptionData> getInterestRateFrequencyTypeOptions() {
public Collection<EnumOptionData> getInterestRateFrequencyTypeOptions() {
return interestRateFrequencyTypeOptions;
}
public Collection<EnumOptionData> getAmortizationTypeOptions() {
return amortizationTypeOptions;
}
public Collection<EnumOptionData> getInterestTypeOptions() {
return interestTypeOptions;
}
public Collection<EnumOptionData> getInterestCalculationPeriodTypeOptions() {
return interestCalculationPeriodTypeOptions;
}
public Collection<FundData> getFundOptions() {
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.SavingProductCommand;
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;
public interface ApiDataConversionService {
@@ -46,7 +46,7 @@ public interface ApiDataConversionService {
GroupCommand convertJsonToGroupCommand(Long resourceIdentifier, String jsonRequestBody);
SubmitLoanApplicationCommand convertJsonToSubmitLoanApplicationCommand(String jsonRequestBody);
LoanApplicationCommand convertJsonToLoanApplicationCommand(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.SavingProductCommand;
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.data.ApiParameterError;
import org.mifosng.platform.api.errorhandling.InvalidJsonException;
@@ -430,7 +430,7 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
}
@Override
public SubmitLoanApplicationCommand convertJsonToSubmitLoanApplicationCommand(final String json) {
public LoanApplicationCommand convertJsonToLoanApplicationCommand(final Long resourceIdentifier, final String json) {
if (StringUtils.isBlank(json)) {
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,
expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate,
principal, interestRatePerPeriod, interestRateFrequencyTypeValue, interestTypeValue, interestCalculationPeriodTypeValue,
@@ -83,7 +83,7 @@ public class Note extends AbstractAuditableCustom<AppUser, Long> {
private Note(Loan loan, String note) {
this.loan = loan;
this.client = loan.getClient();
this.client = loan.client();
this.note = note;
this.noteTypeId = NoteType.LOAN.getValue();
}
@@ -91,7 +91,7 @@ public class Note extends AbstractAuditableCustom<AppUser, Long> {
private Note(Loan loan, LoanTransaction loanTransaction, String note) {
this.loan = loan;
this.loanTransaction = loanTransaction;
this.client = loan.getClient();
this.client = loan.client();
this.note = note;
this.noteTypeId = NoteType.LOAN_TRANSACTION.getValue();
}
@@ -11,8 +11,6 @@ import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
@@ -29,6 +27,10 @@ import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.DateTime;
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.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
@@ -44,12 +46,13 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@ManyToOne
@JoinColumn(name = "client_id", nullable = false)
private final Client client;
private Client client;
@ManyToOne
@JoinColumn(name = "product_id")
private final LoanProduct loanProduct;
private LoanProduct loanProduct;
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "fund_id", nullable = true)
private Fund fund;
@@ -62,16 +65,15 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
private String externalId;
@Embedded
private final LoanProductRelatedDetail loanRepaymentScheduleDetail;
private LoanProductRelatedDetail loanRepaymentScheduleDetail;
@SuppressWarnings("unused")
@Column(name = "term_frequency", nullable = false)
private Integer termFrequency;
@SuppressWarnings("unused")
@Enumerated(EnumType.ORDINAL)
@Column(name = "term_period_frequency_enum", nullable = false)
private PeriodFrequencyType termPeriodFrequencyType;
private Integer termPeriodFrequencyType;
@Column(name = "loan_status_id", nullable = false)
private Integer loanStatus;
@@ -184,21 +186,76 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.interestRebateOwed = BigDecimal.ZERO;
}
public Client getClient() {
public Client client() {
return this.client;
}
public Fund getFund() {
return this.fund;
}
public LoanProduct getLoanProduct() {
public LoanProduct loanProduct() {
return this.loanProduct;
}
public LoanProductRelatedDetail getLoanRepaymentScheduleDetail() {
public LoanProductRelatedDetail repaymentScheduleDetail() {
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(
final Integer loanTermFrequency,
@@ -218,7 +275,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.loanStatus = statusEnum.getValue();
this.termFrequency = loanTermFrequency;
this.termPeriodFrequencyType = loanTermFrequencyType;
this.termPeriodFrequencyType = loanTermFrequencyType.getValue();
this.submittedOnDate = submittedOn.toDateTimeAtCurrentTime().toDate();
@@ -257,8 +314,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
}
}
public void reject(final LocalDate rejectedOn,
LoanLifecycleStateMachine loanLifecycleStateMachine) {
public void reject(final LocalDate rejectedOn, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED, LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue();
@@ -1118,5 +1174,4 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return this.loanRepaymentScheduleDetail.getPrincipal()
.getCurrencyCode();
}
}
@@ -17,8 +17,7 @@ import org.mifosng.platform.currency.domain.Money;
* {@link LoanProduct} that are also used and persisted by a {@link Loan}.
*/
@Embeddable
public class LoanProductRelatedDetail implements
LoanProductMinimumRepaymentScheduleRelatedDetail {
public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentScheduleRelatedDetail {
@Embedded
private MonetaryCurrency currency;
@@ -5,15 +5,15 @@ import java.util.List;
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
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.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;
}
@@ -7,7 +7,7 @@ import java.util.List;
import java.util.Set;
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.MoneyData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
@@ -69,7 +69,7 @@ public class LoanAssembler {
this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository;
}
public Loan assembleFrom(final SubmitLoanApplicationCommand command) {
public Loan assembleFrom(final LoanApplicationCommand command) {
LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId());
if (loanProduct == null) {
@@ -82,26 +82,10 @@ public class LoanAssembler {
}
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 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 = new LoanProductRelatedDetail(currency,
command.getPrincipal(), defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
interestMethod, interestCalculationPeriodMethod,
repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, command.getInArrearsTolerance());
LoanProductRelatedDetail loanRepaymentScheduleDetail = assembleLoanProductRelatedDetailFrom(command, currency);
LoanSchedule loanSchedule = command.getLoanSchedule();
List<ScheduledLoanInstallment> loanRepaymentSchedule = loanSchedule.getScheduledLoanInstallments();
@@ -136,12 +120,35 @@ public class LoanAssembler {
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() {
List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
}
private Fund findFundByIdIfProvided(final Long fundId) {
public Fund findFundByIdIfProvided(final Long fundId) {
Fund fund = null;
if (fundId != null) {
fund = this.fundRepository.findOne(fundId);
@@ -152,7 +159,7 @@ public class LoanAssembler {
return fund;
}
private LoanTransactionProcessingStrategy findStrategyByIdIfProvided(final Long transactionProcessingStrategyId) {
public LoanTransactionProcessingStrategy findStrategyByIdIfProvided(final Long transactionProcessingStrategyId) {
LoanTransactionProcessingStrategy strategy = null;
if (transactionProcessingStrategyId != null) {
strategy = this.loanTransactionProcessingStrategyRepository.findOne(transactionProcessingStrategyId);
@@ -163,7 +170,7 @@ public class LoanAssembler {
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) {
Set<LoanCharge> charges = new HashSet<LoanCharge>();
@@ -293,7 +293,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
throw new LoanNotFoundException(loanId);
}
final String currencyCode = loan.getLoanRepaymentScheduleDetail()
final String currencyCode = loan.repaymentScheduleDetail()
.getPrincipal().getCurrencyCode();
ApplicationCurrency currency = this.applicationCurrencyRepository
.findOneByCode(currencyCode);
@@ -332,7 +332,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
throw new LoanNotFoundException(loanId);
}
final String currencyCode = loan.getLoanRepaymentScheduleDetail()
final String currencyCode = loan.repaymentScheduleDetail()
.getPrincipal().getCurrencyCode();
ApplicationCurrency currency = this.applicationCurrencyRepository
.findOneByCode(currencyCode);
@@ -368,7 +368,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
throw new LoanNotFoundException(loanId);
}
final String currencyCode = loan.getLoanRepaymentScheduleDetail()
final String currencyCode = loan.repaymentScheduleDetail()
.getPrincipal().getCurrencyCode();
ApplicationCurrency currency = this.applicationCurrencyRepository
.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.LoanStateTransitionCommand;
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.data.EntityIdentifier;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -11,7 +11,10 @@ import org.springframework.security.access.prepost.PreAuthorize;
public interface LoanWritePlatformService {
@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')")
EntityIdentifier deleteLoan(Long loanId);
@@ -9,28 +9,36 @@ import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.commands.AdjustLoanTransactionCommand;
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.LoanTransactionCommand;
import org.mifosng.platform.api.commands.SubmitLoanApplicationCommand;
import org.mifosng.platform.api.commands.UndoStateTransitionCommand;
import org.mifosng.platform.api.data.EntityIdentifier;
import org.mifosng.platform.api.data.LoanSchedule;
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.NoteRepository;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.exceptions.ClientNotFoundException;
import org.mifosng.platform.exceptions.LoanNotFoundException;
import org.mifosng.platform.exceptions.LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted;
import org.mifosng.platform.exceptions.LoanProductNotFoundException;
import org.mifosng.platform.exceptions.LoanTransactionNotFoundException;
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.Loan;
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.LoanRepository;
import org.mifosng.platform.loan.domain.LoanStatus;
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.PeriodFrequencyType;
import org.mifosng.platform.security.PlatformSecurityContext;
@@ -49,17 +57,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final CalculationPlatformService calculationPlatformService;
private final LoanTransactionRepository loanTransactionRepository;
private final LoanAssembler loanAssembler;
private final ClientRepository clientRepository;
private final LoanProductRepository loanProductRepository;
@Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final LoanAssembler loanAssembler,
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.loanAssembler = loanAssembler;
this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository;
this.noteRepository = noteRepository;
this.calculationPlatformService = calculationPlatformService;
this.clientRepository = clientRepository;
this.loanProductRepository = loanProductRepository;
}
private boolean isBeforeToday(final LocalDate date) {
@@ -73,11 +86,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Transactional
@Override
public EntityIdentifier submitLoanApplication(final SubmitLoanApplicationCommand command) {
public EntityIdentifier submitLoanApplication(final LoanApplicationCommand command) {
AppUser currentUser = context.authenticatedUser();
SubmitLoanApplicationCommandValidator validator = new SubmitLoanApplicationCommandValidator(command);
LoanApplicationCommandValidator validator = new LoanApplicationCommandValidator(command);
validator.validate();
LocalDate submittedOn = command.getSubmittedOnDate();
@@ -97,6 +110,51 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
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
@Override
public EntityIdentifier deleteLoan(final Long loanId) {
@@ -261,13 +319,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
LocalDate repaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate();
LocalDate interestCalculatedFromDate = loan.getInterestChargedFromDate();
BigDecimal principalAsDecimal = loan.getLoanRepaymentScheduleDetail().getPrincipal().getAmount();
BigDecimal interestRatePerYear = loan.getLoanRepaymentScheduleDetail().getAnnualNominalInterestRate();
Integer numberOfInstallments = loan.getLoanRepaymentScheduleDetail().getNumberOfRepayments();
BigDecimal principalAsDecimal = loan.repaymentScheduleDetail().getPrincipal().getAmount();
BigDecimal interestRatePerYear = loan.repaymentScheduleDetail().getAnnualNominalInterestRate();
Integer numberOfInstallments = loan.repaymentScheduleDetail().getNumberOfRepayments();
Integer repaidEvery = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
Integer selectedRepaymentFrequency = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType().getValue();
Integer selectedAmortizationMethod = loan.getLoanRepaymentScheduleDetail().getAmortizationMethod().getValue();
Integer repaidEvery = loan.repaymentScheduleDetail().getRepayEvery();
Integer selectedRepaymentFrequency = loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType().getValue();
Integer selectedAmortizationMethod = loan.repaymentScheduleDetail().getAmortizationMethod().getValue();
// FIXME - use values from loan table here instead of inferring.
Integer loanTermFrequency = repaidEvery * numberOfInstallments;
@@ -277,11 +335,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
BigDecimal interestRatePerPeriod = interestRatePerYear;
Integer interestRateFrequencyMethod = PeriodFrequencyType.YEARS.getValue();
Integer interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod().getValue();
Integer interestCalculationInPeriod = loan.getLoanRepaymentScheduleDetail().getInterestCalculationPeriodMethod().getValue();
Integer interestMethod = loan.repaymentScheduleDetail().getInterestMethod().getValue();
Integer interestCalculationInPeriod = loan.repaymentScheduleDetail().getInterestCalculationPeriodMethod().getValue();
CalculateLoanScheduleCommand calculateCommand = new CalculateLoanScheduleCommand(
loan.getLoanProduct().getId(),
loan.loanProduct().getId(),
principalAsDecimal,
interestRatePerPeriod, interestRateFrequencyMethod, interestMethod, interestCalculationInPeriod,
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");
}
Money repayment = Money.of(loan.getLoanRepaymentScheduleDetail()
Money repayment = Money.of(loan.repaymentScheduleDetail()
.getPrincipal().getCurrency(),
command.getTransactionAmount());
@@ -412,7 +470,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
Money transactionAmount = Money.of(loan
.getLoanRepaymentScheduleDetail().getPrincipal()
.repaymentScheduleDetail().getPrincipal()
.getCurrency(), command.getTransactionAmount());
// adjustment is only supported for repayments and waivers at present
@@ -451,7 +509,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new LoanNotFoundException(command.getLoanId());
}
Money waived = Money.of(loan.getLoanRepaymentScheduleDetail()
Money waived = Money.of(loan.repaymentScheduleDetail()
.getPrincipal().getCurrency(),
command.getTransactionAmount());