diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java index 12d311544..be4102913 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java @@ -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 productOptions = new ArrayList(); - List loanTermFrequencyTypeOptions = new ArrayList(); - List repaymentFrequencyTypeOptions = new ArrayList(); + Collection loanTermFrequencyTypeOptions = new ArrayList(); + Collection repaymentFrequencyTypeOptions = new ArrayList(); Collection transactionProcessingStrategyOptions = new ArrayList(); - List interestRateFrequencyTypeOptions = new ArrayList(); - List amortizationTypeOptions = new ArrayList(); - List interestTypeOptions = new ArrayList(); - List interestCalculationPeriodTypeOptions = new ArrayList(); + Collection interestRateFrequencyTypeOptions = new ArrayList(); + Collection amortizationTypeOptions = new ArrayList(); + Collection interestTypeOptions = new ArrayList(); + Collection interestCalculationPeriodTypeOptions = new ArrayList(); Collection fundOptions = new ArrayList(); 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}") diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/SubmitLoanApplicationCommand.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/LoanApplicationCommand.java similarity index 77% rename from mifosng-provider/src/main/java/org/mifosng/platform/api/commands/SubmitLoanApplicationCommand.java rename to mifosng-provider/src/main/java/org/mifosng/platform/api/commands/LoanApplicationCommand.java index 511103bb9..d44dbe4fe 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/SubmitLoanApplicationCommand.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/LoanApplicationCommand.java @@ -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 modifiedParameters; - public SubmitLoanApplicationCommand( + public LoanApplicationCommand( + final Set 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"); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanAccountData.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanAccountData.java index 95821554b..eb702a9a2 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanAccountData.java @@ -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 productOptions; - private final List termFrequencyTypeOptions; - private final List repaymentFrequencyTypeOptions; + private final Collection termFrequencyTypeOptions; + private final Collection repaymentFrequencyTypeOptions; private final Collection repaymentStrategyOptions; + private final Collection interestRateFrequencyTypeOptions; + private final Collection amortizationTypeOptions; + private final Collection interestTypeOptions; + private final Collection interestCalculationPeriodTypeOptions; private final Collection fundOptions; // associations @@ -64,8 +67,6 @@ public class LoanAccountData { private final LoanPermissionData permissions; private final LoanConvenienceData convenienceData; - private final List interestRateFrequencyTypeOptions; - public LoanAccountData( final LoanBasicDetailsData basicDetails, @@ -75,10 +76,13 @@ public class LoanAccountData { final LoanPermissionData permissions, final Collection charges, final Collection productOptions, - final List termFrequencyTypeOptions, - final List repaymentFrequencyTypeOptions, + final Collection termFrequencyTypeOptions, + final Collection repaymentFrequencyTypeOptions, final Collection transactionProcessingStrategyOptions, - final List interestRateFrequencyTypeOptions, + final Collection interestRateFrequencyTypeOptions, + final Collection amortizationTypeOptions, + final Collection interestTypeOptions, + final Collection interestCalculationPeriodTypeOptions, final Collection 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 charges) { this.charges = charges; } - + public Collection getProductOptions() { return productOptions; } - - public List getTermFrequencyTypeOptions() { + + public Collection getTermFrequencyTypeOptions() { return termFrequencyTypeOptions; } - public List getRepaymentFrequencyTypeOptions() { + public Collection getRepaymentFrequencyTypeOptions() { return repaymentFrequencyTypeOptions; } - + public Collection getRepaymentStrategyOptions() { return repaymentStrategyOptions; } - - public List getInterestRateFrequencyTypeOptions() { + + public Collection getInterestRateFrequencyTypeOptions() { return interestRateFrequencyTypeOptions; } + public Collection getAmortizationTypeOptions() { + return amortizationTypeOptions; + } + + public Collection getInterestTypeOptions() { + return interestTypeOptions; + } + + public Collection getInterestCalculationPeriodTypeOptions() { + return interestCalculationPeriodTypeOptions; + } + public Collection getFundOptions() { return fundOptions; } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionService.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionService.java index f17123173..3d18f3ab5 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionService.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionService.java @@ -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); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java index 8342959fd..75b2e6c8a 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java @@ -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, diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/client/domain/Note.java b/mifosng-provider/src/main/java/org/mifosng/platform/client/domain/Note.java index 8e8b41315..7be4b0f04 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/client/domain/Note.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/client/domain/Note.java @@ -83,7 +83,7 @@ public class Note extends AbstractAuditableCustom { 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 { 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(); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java index d795014e1..5eaef00b4 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java @@ -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 { @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 { 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 { 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 repaymentsOrWaivers = new ArrayList(); + 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 { 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 { } } - 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 { return this.loanRepaymentScheduleDetail.getPrincipal() .getCurrencyCode(); } - } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanProductRelatedDetail.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanProductRelatedDetail.java index 6837713c8..1ac0cd6f0 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanProductRelatedDetail.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanProductRelatedDetail.java @@ -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; diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/SubmitLoanApplicationCommandValidator.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanApplicationCommandValidator.java similarity index 90% rename from mifosng-provider/src/main/java/org/mifosng/platform/loan/service/SubmitLoanApplicationCommandValidator.java rename to mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanApplicationCommandValidator.java index 6cb6f22ef..1b0b3e61e 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/SubmitLoanApplicationCommandValidator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanApplicationCommandValidator.java @@ -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; } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanAssembler.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanAssembler.java index 9e7f6c2f0..1e3909476 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanAssembler.java @@ -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 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 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 assembleSetOfCharges(final SubmitLoanApplicationCommand command, final Loan loan, + private Set assembleSetOfCharges(final LoanApplicationCommand command, final Loan loan, final LoanProduct product, final String currencyCode) { Set charges = new HashSet(); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanReadPlatformServiceImpl.java index 0c190e600..0bff24f5d 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanReadPlatformServiceImpl.java @@ -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); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformService.java index 3fa7a0a70..6ca0889aa 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformService.java @@ -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); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 5f46f4e95..43c102a0f 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -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());