support submit new loan application and delete loan capabilities through api.
This commit is contained in:
-209
@@ -1,209 +0,0 @@
|
||||
package org.mifosng.platform;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.mifosng.data.ErrorResponse;
|
||||
import org.mifosng.data.command.CalculateLoanScheduleCommand;
|
||||
import org.mifosng.platform.exceptions.NewDataValidationException;
|
||||
import org.mifosng.platform.loan.domain.AmortizationMethod;
|
||||
import org.mifosng.platform.loan.domain.InterestMethod;
|
||||
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
|
||||
|
||||
public class CalculateLoanScheduleCommandValidator {
|
||||
|
||||
private final CalculateLoanScheduleCommand command;
|
||||
|
||||
public CalculateLoanScheduleCommandValidator(CalculateLoanScheduleCommand command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
List<ErrorResponse> dataValidationErrors = new ArrayList<ErrorResponse>();
|
||||
|
||||
if (StringUtils.isBlank(command.getCurrencyCode())) {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.currency.cannot.be.empty", "currencyCode");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getDigitsAfterDecimal() != null) {
|
||||
|
||||
if (command.getDigitsAfterDecimal() < 0 || command.getDigitsAfterDecimal() > 6) {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.currency.digitsAfterDecimal.must.be.between.zero.and.six.inclusive", "selectedDigitsAfterDecimal", command.getDigitsAfterDecimal());
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.currency.digitsAfterDecimal.cannot.be.empty", "selectedDigitsAfterDecimal");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getPrincipal() != null) {
|
||||
if (command.getPrincipal().doubleValue() <= Double.valueOf("0.0")) {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.principal.amount.must.be.greater.than.zero", "principalMoney");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
// TODO - check number of digits before decimal and after decimal
|
||||
} else {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.principal.amount.cannot.be.empty", "principalMoney");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getRepaymentFrequency() != null) {
|
||||
PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequency());
|
||||
ErrorResponse error = null;
|
||||
switch (frequencyType) {
|
||||
case DAYS:
|
||||
error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 365);
|
||||
break;
|
||||
case WEEKS:
|
||||
error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 52);
|
||||
break;
|
||||
case MONTHS:
|
||||
error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 12);
|
||||
break;
|
||||
case YEARS:
|
||||
error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 2);
|
||||
break;
|
||||
default:
|
||||
error = new ErrorResponse("validation.msg.product.repayment.frequency.invalid", "selectedRepaymentFrequencyOption");
|
||||
break;
|
||||
}
|
||||
if (error != null) {
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.repayment.frequency.cannot.be.empty", "selectedRepaymentFrequencyOption");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getNumberOfRepayments() != null) {
|
||||
if (command.getNumberOfRepayments() < 1 || command.getNumberOfRepayments() > 100) {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.number.of.repayments.is.out.of.range", "numberOfRepayments");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.number.of.repayments.cannot.be.empty", "numberOfRepayments");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getAmortizationMethod() != null) {
|
||||
AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationMethod());
|
||||
ErrorResponse error = null;
|
||||
switch (amortizationMethod) {
|
||||
case EQUAL_PRINCIPAL:
|
||||
break;
|
||||
case EQUAL_INSTALLMENTS:
|
||||
break;
|
||||
default:
|
||||
error = new ErrorResponse("validation.msg.product.amortization.method.invalid", "selectedAmortizationMethodOption");
|
||||
break;
|
||||
}
|
||||
if (error != null) {
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.amortization.method.cannot.be.empty", "selectedAmortizationMethodOption");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
|
||||
if (command.getExpectedDisbursementDate() == null) {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.loan.expectedDisbursementDate.method.cannot.be.blank", "expectedDisbursementDate");
|
||||
dataValidationErrors.add(error);
|
||||
} else {
|
||||
|
||||
if (command.getRepaymentsStartingFromDate() != null
|
||||
&& command.getExpectedDisbursementDate().isAfter(command.getRepaymentsStartingFromDate())) {
|
||||
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.loan.expectedDisbursementDate.cannot.be.after.first.repayment.date", "expectedDisbursementDate");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (command.getRepaymentsStartingFromDate() != null && command.getInterestCalculatedFromDate() == null) {
|
||||
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.loan.interestCalculatedFromDate.must.be.entered.when.using.repayments.startfrom.field", "interestCalculatedFromDate");
|
||||
dataValidationErrors.add(error);
|
||||
} else if (command.getRepaymentsStartingFromDate() == null && command.getInterestCalculatedFromDate() != null) {
|
||||
|
||||
// validate interestCalculatedFromDate is after or on repaymentsStartingFromDate
|
||||
|
||||
if (command.getExpectedDisbursementDate().isAfter(command.getInterestCalculatedFromDate())) {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.loan.interestCalculatedFromDate.cannot.be.before.disbursement.date", "interestCalculatedFromDate");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (command.getInterestRatePerPeriod()!= null) {
|
||||
if (command.getInterestRatePerPeriod().doubleValue() < Double.valueOf("0.0")) {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.interest.rate.per.period.amount.cannot.be.negative", "nominalInterestRate");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.interest.rate.per.period.amount.cannot.be.empty", "nominalInterestRate");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
// interest rate period type
|
||||
if (command.getInterestRateFrequencyMethod() != null) {
|
||||
PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyMethod());
|
||||
ErrorResponse error = null;
|
||||
switch (frequencyType) {
|
||||
case DAYS:
|
||||
break;
|
||||
case WEEKS:
|
||||
break;
|
||||
case MONTHS:
|
||||
break;
|
||||
case YEARS:
|
||||
break;
|
||||
default:
|
||||
error = new ErrorResponse("validation.msg.product.interest.frequency.invalid", "selectedInterestFrequencyOption");
|
||||
break;
|
||||
}
|
||||
if (error != null) {
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.interest.frequency.cannot.be.empty", "selectedInterestFrequencyOption");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getInterestMethod() != null) {
|
||||
InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestMethod());
|
||||
ErrorResponse error = null;
|
||||
switch (interestMethod) {
|
||||
case DECLINING_BALANCE:
|
||||
break;
|
||||
case FLAT:
|
||||
break;
|
||||
default:
|
||||
error = new ErrorResponse("validation.msg.product.interest.method.invalid", "selectedInterestMethodOption");
|
||||
break;
|
||||
}
|
||||
if (error != null) {
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.product.interest.method.cannot.be.empty", "selectedInterestMethodOption");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new NewDataValidationException(dataValidationErrors, "Validation errors exist.");
|
||||
}
|
||||
}
|
||||
|
||||
private ErrorResponse validateNumberExistsAndInRange(String errorCodePreFix, String fieldName, Integer value, int min, int max) {
|
||||
ErrorResponse error = null;
|
||||
if (value == null) {
|
||||
error = new ErrorResponse(errorCodePreFix + ".cannot.be.empty", fieldName);
|
||||
} else {
|
||||
if (value < min || value > max) {
|
||||
error = new ErrorResponse(errorCodePreFix + ".outside.allowed.range", fieldName);
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ import org.mifosng.platform.loan.domain.LoanStatus;
|
||||
import org.mifosng.platform.loan.domain.LoanStatusRepository;
|
||||
import org.mifosng.platform.loan.domain.LoanTransaction;
|
||||
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
|
||||
import org.mifosng.platform.loan.service.CalculationPlatformService;
|
||||
import org.mifosng.platform.organisation.domain.Office;
|
||||
import org.mifosng.platform.organisation.domain.OfficeRepository;
|
||||
import org.mifosng.platform.organisation.domain.Organisation;
|
||||
@@ -452,7 +453,6 @@ public class ImportPlatformServiceImpl implements ImportPlatformService {
|
||||
|
||||
String currencyCode = matchingProduct.getCurrency().getCode();
|
||||
int digitsAfterDecimal = matchingProduct.getCurrency().getDigitsAfterDecimal();
|
||||
boolean flexibleRepaymentSchedule = matchingProduct.isFlexibleRepaymentSchedule();
|
||||
Integer repaymentEvery = matchingProduct.getRepayEvery();
|
||||
Integer repaymentFrequency = matchingProduct.getRepaymentPeriodFrequencyType().getValue();
|
||||
Integer amortizationMethod = matchingProduct.getAmortizationMethod().getValue();
|
||||
@@ -469,8 +469,7 @@ public class ImportPlatformServiceImpl implements ImportPlatformService {
|
||||
interestRatePeriodFrequency,
|
||||
interestMethod, interestCalculationPeriodMethod,
|
||||
repaymentEvery,
|
||||
repaymentFrequency, numberOfRepayments, amortizationMethod, flexibleRepaymentSchedule,
|
||||
matchingProduct.isInterestRebateAllowed(), expectedDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate);
|
||||
repaymentFrequency, numberOfRepayments, amortizationMethod, expectedDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate);
|
||||
|
||||
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateLoanScheduleCommand);
|
||||
|
||||
@@ -478,8 +477,7 @@ public class ImportPlatformServiceImpl implements ImportPlatformService {
|
||||
expectedDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate, loanSchedule,
|
||||
currencyCode, digitsAfterDecimal, principal,
|
||||
interestRatePerPeriod, interestRatePeriodFrequency, interestMethod, interestCalculationPeriodMethod,
|
||||
repaymentEvery, repaymentFrequency, numberOfRepayments, amortizationMethod, toleranceAmount.getAmount(),
|
||||
flexibleRepaymentSchedule, matchingProduct.isInterestRebateAllowed());
|
||||
repaymentEvery, repaymentFrequency, numberOfRepayments, amortizationMethod, toleranceAmount.getAmount());
|
||||
|
||||
parsedNewLoanDetails.setExternalId(loanExternalId);
|
||||
|
||||
|
||||
@@ -5,18 +5,14 @@ import static org.mifosng.platform.Specifications.loansThatMatch;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.data.ClientData;
|
||||
import org.mifosng.data.CurrencyData;
|
||||
import org.mifosng.data.LoanProductData;
|
||||
import org.mifosng.data.LoanRepaymentData;
|
||||
import org.mifosng.data.MoneyData;
|
||||
import org.mifosng.data.NewLoanWorkflowStepOneData;
|
||||
import org.mifosng.data.OrganisationReadModel;
|
||||
import org.mifosng.platform.client.service.ClientReadPlatformService;
|
||||
import org.mifosng.platform.currency.domain.ApplicationCurrency;
|
||||
|
||||
@@ -4,7 +4,6 @@ import org.mifosng.data.EntityIdentifier;
|
||||
import org.mifosng.data.command.AdjustLoanTransactionCommand;
|
||||
import org.mifosng.data.command.LoanStateTransitionCommand;
|
||||
import org.mifosng.data.command.LoanTransactionCommand;
|
||||
import org.mifosng.data.command.SubmitLoanApplicationCommand;
|
||||
import org.mifosng.data.command.UndoLoanApprovalCommand;
|
||||
import org.mifosng.data.command.UndoLoanDisbursalCommand;
|
||||
import org.mifosng.data.command.UpdateUsernamePasswordCommand;
|
||||
@@ -14,12 +13,6 @@ public interface WritePlatformService {
|
||||
|
||||
void updateUsernamePasswordOnFirstTimeLogin(UpdateUsernamePasswordCommand command);
|
||||
|
||||
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_DELETE_LOAN_THAT_IS_SUBMITTED_AND_NOT_APPROVED')")
|
||||
EntityIdentifier deleteLoan(Long loanId);
|
||||
|
||||
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_SUBMIT_NEW_LOAN_APPLICATION_ROLE', 'CAN_SUBMIT_HISTORIC_LOAN_APPLICATION_ROLE')")
|
||||
EntityIdentifier submitLoanApplication(SubmitLoanApplicationCommand command);
|
||||
|
||||
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_APPROVE_LOAN_ROLE', 'CAN_APPROVE_LOAN_IN_THE_PAST_ROLE')")
|
||||
EntityIdentifier approveLoanApplication(LoanStateTransitionCommand command);
|
||||
|
||||
|
||||
+2
-149
@@ -3,7 +3,6 @@ package org.mifosng.platform;
|
||||
import static org.mifosng.platform.Specifications.loansThatMatch;
|
||||
import static org.mifosng.platform.Specifications.usersThatMatch;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -13,39 +12,27 @@ import org.joda.time.LocalDate;
|
||||
import org.mifosng.data.EntityIdentifier;
|
||||
import org.mifosng.data.ErrorResponse;
|
||||
import org.mifosng.data.LoanSchedule;
|
||||
import org.mifosng.data.MoneyData;
|
||||
import org.mifosng.data.ScheduledLoanInstallment;
|
||||
import org.mifosng.data.command.AdjustLoanTransactionCommand;
|
||||
import org.mifosng.data.command.CalculateLoanScheduleCommand;
|
||||
import org.mifosng.data.command.LoanStateTransitionCommand;
|
||||
import org.mifosng.data.command.LoanTransactionCommand;
|
||||
import org.mifosng.data.command.SubmitLoanApplicationCommand;
|
||||
import org.mifosng.data.command.UndoLoanApprovalCommand;
|
||||
import org.mifosng.data.command.UndoLoanDisbursalCommand;
|
||||
import org.mifosng.data.command.UpdateUsernamePasswordCommand;
|
||||
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.ApplicationDomainRuleException;
|
||||
import org.mifosng.platform.exceptions.NoAuthorizationException;
|
||||
import org.mifosng.platform.exceptions.UnAuthenticatedUserException;
|
||||
import org.mifosng.platform.infrastructure.BasicPasswordEncodablePlatformUser;
|
||||
import org.mifosng.platform.infrastructure.PlatformPasswordEncoder;
|
||||
import org.mifosng.platform.infrastructure.PlatformUser;
|
||||
import org.mifosng.platform.infrastructure.UsernameAlreadyExistsException;
|
||||
import org.mifosng.platform.loan.domain.AmortizationMethod;
|
||||
import org.mifosng.platform.loan.domain.DefaultLoanLifecycleStateMachine;
|
||||
import org.mifosng.platform.loan.domain.InterestCalculationPeriodMethod;
|
||||
import org.mifosng.platform.loan.domain.InterestMethod;
|
||||
import org.mifosng.platform.loan.domain.Loan;
|
||||
import org.mifosng.platform.loan.domain.LoanBuilder;
|
||||
import org.mifosng.platform.loan.domain.LoanLifecycleStateMachine;
|
||||
import org.mifosng.platform.loan.domain.LoanProduct;
|
||||
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
|
||||
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;
|
||||
@@ -53,6 +40,7 @@ import org.mifosng.platform.loan.domain.LoanStatusRepository;
|
||||
import org.mifosng.platform.loan.domain.LoanTransaction;
|
||||
import org.mifosng.platform.loan.domain.LoanTransactionRepository;
|
||||
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
|
||||
import org.mifosng.platform.loan.service.CalculationPlatformService;
|
||||
import org.mifosng.platform.user.domain.AppUser;
|
||||
import org.mifosng.platform.user.domain.AppUserRepository;
|
||||
import org.mifosng.platform.user.domain.PlatformUserRepository;
|
||||
@@ -69,8 +57,6 @@ public class WritePlatformServiceJpaRepositoryImpl implements WritePlatformServi
|
||||
|
||||
private final PlatformUserRepository platformUserRepository;
|
||||
private final PlatformPasswordEncoder platformPasswordEncoder;
|
||||
private final ClientRepository clientRepository;
|
||||
private final LoanProductRepository loanProductRepository;
|
||||
private final LoanRepository loanRepository;
|
||||
private final LoanStatusRepository loanStatusRepository;
|
||||
private final CalculationPlatformService calculationPlatformService;
|
||||
@@ -81,9 +67,7 @@ public class WritePlatformServiceJpaRepositoryImpl implements WritePlatformServi
|
||||
public WritePlatformServiceJpaRepositoryImpl(
|
||||
final PlatformUserRepository platformUserRepository,
|
||||
final PlatformPasswordEncoder platformPasswordEncoder,
|
||||
final ClientRepository clientRepository,
|
||||
final NoteRepository noteRepository,
|
||||
final LoanProductRepository loanProductRepository,
|
||||
final LoanRepository loanRepository,
|
||||
final LoanTransactionRepository loanTransactionRepository,
|
||||
final LoanStatusRepository loanStatusRepository,
|
||||
@@ -91,9 +75,7 @@ public class WritePlatformServiceJpaRepositoryImpl implements WritePlatformServi
|
||||
) {
|
||||
this.platformUserRepository = platformUserRepository;
|
||||
this.platformPasswordEncoder = platformPasswordEncoder;
|
||||
this.clientRepository = clientRepository;
|
||||
this.noteRepository = noteRepository;
|
||||
this.loanProductRepository = loanProductRepository;
|
||||
this.loanRepository = loanRepository;
|
||||
this.loanTransactionRepository = loanTransactionRepository;
|
||||
this.loanStatusRepository = loanStatusRepository;
|
||||
@@ -145,105 +127,6 @@ public class WritePlatformServiceJpaRepositoryImpl implements WritePlatformServi
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public EntityIdentifier submitLoanApplication(final SubmitLoanApplicationCommand command) {
|
||||
|
||||
AppUser currentUser = extractAuthenticatedUser();
|
||||
|
||||
SubmitLoanApplicationCommandValidator validator = new SubmitLoanApplicationCommandValidator(command);
|
||||
validator.validate();
|
||||
|
||||
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 add backdated loan.");
|
||||
}
|
||||
|
||||
LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId());
|
||||
Client client = this.clientRepository.findOne(command.getApplicantId());
|
||||
|
||||
MonetaryCurrency currency = new MonetaryCurrency(command.getCurrencyCode(), command.getDigitsAfterDecimal());
|
||||
|
||||
final BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(command
|
||||
.getInterestRatePerPeriod().doubleValue());
|
||||
final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyMethod());
|
||||
|
||||
// apr calculator
|
||||
BigDecimal defaultAnnualNominalInterestRate = BigDecimal.ZERO;
|
||||
switch (interestPeriodFrequencyType) {
|
||||
case DAYS:
|
||||
break;
|
||||
case WEEKS:
|
||||
defaultAnnualNominalInterestRate = command
|
||||
.getInterestRatePerPeriod()
|
||||
.multiply(BigDecimal.valueOf(52));
|
||||
break;
|
||||
case MONTHS:
|
||||
defaultAnnualNominalInterestRate = command
|
||||
.getInterestRatePerPeriod()
|
||||
.multiply(BigDecimal.valueOf(12));
|
||||
break;
|
||||
case YEARS:
|
||||
defaultAnnualNominalInterestRate = command
|
||||
.getInterestRatePerPeriod().multiply(BigDecimal.valueOf(1));
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestMethod());
|
||||
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodMethod());
|
||||
|
||||
final Integer repayEvery = command.getRepaymentEvery();
|
||||
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType
|
||||
.fromInt(command.getRepaymentFrequency());
|
||||
final Integer defaultNumberOfInstallments = command.getNumberOfRepayments();
|
||||
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationMethod());
|
||||
final boolean flexibleRepaymentSchedule = command.isFlexibleRepaymentSchedule();
|
||||
final boolean interestRebateAllowed = command.isInterestRebateAllowed();
|
||||
|
||||
LoanProductRelatedDetail loanRepaymentScheduleDetail = new LoanProductRelatedDetail(currency,
|
||||
command.getPrincipal(), defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
|
||||
interestMethod, interestCalculationPeriodMethod,
|
||||
repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, command.getInArrearsToleranceAmount(),
|
||||
flexibleRepaymentSchedule, interestRebateAllowed);
|
||||
|
||||
LoanSchedule loanSchedule = command.getLoanSchedule();
|
||||
List<ScheduledLoanInstallment> loanRepaymentSchedule = loanSchedule.getScheduledLoanInstallments();
|
||||
|
||||
Loan loan = new LoanBuilder()
|
||||
.with(currentUser.getOrganisation())
|
||||
.with(loanProduct).with(client)
|
||||
.with(loanRepaymentScheduleDetail)
|
||||
.build();
|
||||
|
||||
for (ScheduledLoanInstallment scheduledLoanInstallment : loanRepaymentSchedule) {
|
||||
|
||||
MoneyData readPrincipalDue = scheduledLoanInstallment
|
||||
.getPrincipalDue();
|
||||
|
||||
MoneyData readInterestDue = scheduledLoanInstallment
|
||||
.getInterestDue();
|
||||
|
||||
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
|
||||
loan, scheduledLoanInstallment.getInstallmentNumber(),
|
||||
scheduledLoanInstallment.getPeriodStart(),
|
||||
scheduledLoanInstallment.getPeriodEnd(), readPrincipalDue.getAmount(),
|
||||
readInterestDue.getAmount());
|
||||
loan.addRepaymentScheduleInstallment(installment);
|
||||
}
|
||||
|
||||
loan.submitApplication(submittedOn, command.getExpectedDisbursementDate(), command.getRepaymentsStartingFromDate(), command.getInterestCalculatedFromDate(), defaultLoanLifecycleStateMachine());
|
||||
this.loanRepository.save(loan);
|
||||
|
||||
if (StringUtils.isNotBlank(command.getSubmittedOnNote())) {
|
||||
Note note = Note.loanNote(currentUser.getOrganisation(), loan, command.getSubmittedOnNote());
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
return new EntityIdentifier(loan.getId());
|
||||
}
|
||||
|
||||
private boolean isBeforeToday(final LocalDate date) {
|
||||
return date.isBefore(new LocalDate());
|
||||
}
|
||||
@@ -253,34 +136,6 @@ public class WritePlatformServiceJpaRepositoryImpl implements WritePlatformServi
|
||||
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public EntityIdentifier deleteLoan(Long loanId) {
|
||||
|
||||
AppUser currentUser = extractAuthenticatedUser();
|
||||
|
||||
Loan loan = this.loanRepository.findOne(loansThatMatch(
|
||||
currentUser.getOrganisation(), loanId));
|
||||
|
||||
if (loan == null) {
|
||||
throw new ApplicationDomainRuleException(loanIdentifierDoesNotExistError(loanId), "No loan exists with id: " + loanId);
|
||||
}
|
||||
|
||||
if (loan.isNotSubmittedAndPendingApproval()) {
|
||||
ErrorResponse errorResponse = new ErrorResponse("error.msg.cannot.delete.loan.in.its.present.state", "eventDate");
|
||||
throw new ApplicationDomainRuleException(Arrays.asList(errorResponse), "Loan must be in pending approval state.");
|
||||
}
|
||||
|
||||
Long clientId = loan.getClient().getId();
|
||||
|
||||
List<Note> relatedNotes = this.noteRepository.findByLoanId(loan.getId());
|
||||
this.noteRepository.deleteInBatch(relatedNotes);
|
||||
|
||||
this.loanRepository.delete(loanId);
|
||||
|
||||
return new EntityIdentifier(clientId);
|
||||
}
|
||||
|
||||
private List<ErrorResponse> loanIdentifierDoesNotExistError(Long loanId) {
|
||||
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.loan.with.identifier.exists", "id", loanId);
|
||||
return Arrays.asList(errorResponse);
|
||||
@@ -444,7 +299,6 @@ public class WritePlatformServiceJpaRepositoryImpl implements WritePlatformServi
|
||||
Integer repaidEvery = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
|
||||
Integer selectedRepaymentFrequency = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType().getValue();
|
||||
Integer selectedRepaymentSchedule = loan.getLoanRepaymentScheduleDetail().getAmortizationMethod().getValue();
|
||||
boolean flexibleRepaymentSchedule = loan.isFlexibleRepaymentSchedule();
|
||||
|
||||
// use annual percentage rate to re-calculate loan schedule for late disbursement
|
||||
Number interestRatePerPeriod = interestRatePerYear;
|
||||
@@ -452,12 +306,11 @@ public class WritePlatformServiceJpaRepositoryImpl implements WritePlatformServi
|
||||
|
||||
Integer interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod().getValue();
|
||||
Integer interestCalculationInPeriod = loan.getLoanRepaymentScheduleDetail().getInterestCalculationPeriodMethod().getValue();
|
||||
boolean interestRebateAllowed = loan.getLoanRepaymentScheduleDetail().isInterestRebateAllowed();
|
||||
|
||||
CalculateLoanScheduleCommand calculateCommand = new CalculateLoanScheduleCommand(currencyCode, currencyDigits, principalAsDecimal,
|
||||
interestRatePerPeriod, interestRateFrequencyMethod, interestMethod, interestCalculationInPeriod,
|
||||
repaidEvery, selectedRepaymentFrequency, numberOfInstallments,
|
||||
selectedRepaymentSchedule, flexibleRepaymentSchedule, interestRebateAllowed, actualDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate);
|
||||
selectedRepaymentSchedule, actualDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate);
|
||||
|
||||
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateCommand);
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package org.mifosng.platform.api;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
@@ -9,10 +14,20 @@ import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.data.EntityIdentifier;
|
||||
import org.mifosng.data.LoanAccountData;
|
||||
import org.mifosng.data.LoanSchedule;
|
||||
import org.mifosng.data.NewLoanWorkflowStepOneData;
|
||||
import org.mifosng.data.command.CalculateLoanScheduleCommand;
|
||||
import org.mifosng.data.command.SubmitLoanApplicationCommand;
|
||||
import org.mifosng.platform.ReadPlatformService;
|
||||
import org.mifosng.platform.WritePlatformService;
|
||||
import org.mifosng.platform.api.infrastructure.ApiDataConversionService;
|
||||
import org.mifosng.platform.loan.service.CalculationPlatformService;
|
||||
import org.mifosng.platform.loan.service.LoanReadPlatformService;
|
||||
import org.mifosng.platform.loan.service.LoanWritePlatformService;
|
||||
import org.mifosng.platform.user.domain.AppUser;
|
||||
import org.mifosng.platform.user.domain.AppUserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -31,9 +46,21 @@ public class LoansApiResource {
|
||||
@Autowired
|
||||
private LoanReadPlatformService loanReadPlatformService;
|
||||
|
||||
@Autowired
|
||||
private LoanWritePlatformService loanWritePlatformService;
|
||||
|
||||
@Autowired
|
||||
private ReadPlatformService readPlatformService;
|
||||
|
||||
@Autowired
|
||||
private WritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
private CalculationPlatformService calculationPlatformService;
|
||||
|
||||
@Autowired
|
||||
private ApiDataConversionService apiDataConversionService;
|
||||
|
||||
@Autowired
|
||||
private AppUserRepository appUserRepository;
|
||||
|
||||
@@ -51,6 +78,8 @@ public class LoansApiResource {
|
||||
@Produces({ MediaType.APPLICATION_JSON})
|
||||
public Response retrieveDetailsForNewLoanApplicationStepOne(@QueryParam("clientId") final Long clientId, @QueryParam("productId") final Long productId) {
|
||||
|
||||
hardcodeUserIntoSecurityContext();
|
||||
|
||||
NewLoanWorkflowStepOneData workflowData = this.loanReadPlatformService.retrieveClientAndProductDetails(clientId, productId);
|
||||
|
||||
return Response.ok().entity(workflowData).build();
|
||||
@@ -68,4 +97,58 @@ public class LoansApiResource {
|
||||
|
||||
return Response.ok().entity(loanAccount).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
|
||||
@Produces({MediaType.APPLICATION_JSON})
|
||||
public Response calculateLoanSchedule(@QueryParam("command") final String commandParam, final SubmitLoanApplicationCommand command) {
|
||||
|
||||
hardcodeUserIntoSecurityContext();
|
||||
|
||||
LocalDate expectedDisbursementDate = apiDataConversionService.convertFrom(command.getExpectedDisbursementDateFormatted(), "expectedDisbursementDateFormatted", command.getDateFormat());
|
||||
LocalDate repaymentsStartingFromDate = apiDataConversionService.convertFrom(command.getRepaymentsStartingFromDateFormatted(), "repaymentsStartingFromDateFormatted", command.getDateFormat());
|
||||
LocalDate interestCalculatedFromDate = apiDataConversionService.convertFrom(command.getInterestCalculatedFromDateFormatted(), "interestCalculatedFromDateFormatted", command.getDateFormat());
|
||||
LocalDate submittedOnDate = apiDataConversionService.convertFrom(command.getSubmittedOnDateFormatted(), "submittedOnDateFormatted", command.getDateFormat());
|
||||
command.setExpectedDisbursementDate(expectedDisbursementDate);
|
||||
command.setRepaymentsStartingFromDate(repaymentsStartingFromDate);
|
||||
command.setInterestCalculatedFromDate(interestCalculatedFromDate);
|
||||
command.setSubmittedOnDate(submittedOnDate);
|
||||
|
||||
// FIXME - pass in locale through query string or in 'request body'
|
||||
Locale clientApplicationLocale = Locale.UK;
|
||||
BigDecimal principal = this.apiDataConversionService.convertFrom(command.getPrincipalFormatted(), "principalFormatted", clientApplicationLocale);
|
||||
BigDecimal interestRatePerPeriod = this.apiDataConversionService.convertFrom(command.getInterestRatePerPeriodFormatted(), "interestRatePerPeriodFormatted", clientApplicationLocale);
|
||||
BigDecimal inArrearsToleranceAmount = this.apiDataConversionService.convertFrom(command.getInArrearsToleranceAmountFormatted(), "inArrearsToleranceAmountFormatted", clientApplicationLocale);
|
||||
command.setPrincipal(principal);
|
||||
command.setInterestRatePerPeriod(interestRatePerPeriod);
|
||||
command.setInArrearsToleranceAmount(inArrearsToleranceAmount);
|
||||
|
||||
CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
|
||||
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 (StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase("calculateLoanSchedule")) {
|
||||
|
||||
return Response.ok().entity(loanSchedule).build();
|
||||
}
|
||||
|
||||
EntityIdentifier identifier = this.loanWritePlatformService.submitLoanApplication(command);
|
||||
|
||||
return Response.ok().entity(identifier).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{loanId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
|
||||
@Produces({ MediaType.APPLICATION_JSON})
|
||||
public Response deleteLoanApplication(@PathParam("loanId") final Long loanId) {
|
||||
|
||||
hardcodeUserIntoSecurityContext();
|
||||
|
||||
EntityIdentifier identifier = this.loanWritePlatformService.deleteLoan(loanId);
|
||||
|
||||
return Response.ok().entity(identifier).build();
|
||||
}
|
||||
}
|
||||
+5
@@ -1,9 +1,14 @@
|
||||
package org.mifosng.platform.api.infrastructure;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
public interface ApiDataConversionService {
|
||||
|
||||
LocalDate convertFrom(String dateAsString, String parameterName, String dateFormat);
|
||||
|
||||
BigDecimal convertFrom(String principalFormatted, String parameterName, Locale clientApplicationLocale);
|
||||
|
||||
}
|
||||
|
||||
+27
@@ -1,5 +1,8 @@
|
||||
package org.mifosng.platform.api.infrastructure;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -33,4 +36,28 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
|
||||
|
||||
return eventLocalDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal convertFrom(String numericalValueFormatted, String parameterName, Locale clientApplicationLocale) {
|
||||
|
||||
try {
|
||||
BigDecimal number = null;
|
||||
|
||||
if (StringUtils.isNotBlank(numericalValueFormatted)) {
|
||||
String sourceWithoutSpaces = numericalValueFormatted.replaceAll(" ", "");
|
||||
NumberFormat format = NumberFormat.getNumberInstance(clientApplicationLocale);
|
||||
Number parsedNumber = format.parse(sourceWithoutSpaces);
|
||||
number = BigDecimal.valueOf(Double.valueOf(parsedNumber.doubleValue()));
|
||||
}
|
||||
|
||||
return number;
|
||||
} catch (ParseException e) {
|
||||
|
||||
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.number.format", "The parameter " + parameterName + " is invalid.", parameterName, numericalValueFormatted);
|
||||
dataValidationErrors.add(error);
|
||||
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package org.mifosng.platform.exceptions;
|
||||
|
||||
/**
|
||||
* A {@link RuntimeException} thrown when valid api request end up violating some domain rule.
|
||||
*/
|
||||
public class PlatformDomainRuleException extends RuntimeException {
|
||||
|
||||
private final String globalisationMessageCode;
|
||||
private final String defaultUserMessage;
|
||||
private final Object[] defaultUserMessageArgs;
|
||||
|
||||
public PlatformDomainRuleException(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) {
|
||||
this.globalisationMessageCode = globalisationMessageCode;
|
||||
this.defaultUserMessage = defaultUserMessage;
|
||||
this.defaultUserMessageArgs = defaultUserMessageArgs;
|
||||
}
|
||||
|
||||
public String getGlobalisationMessageCode() {
|
||||
return globalisationMessageCode;
|
||||
}
|
||||
|
||||
public String getDefaultUserMessage() {
|
||||
return defaultUserMessage;
|
||||
}
|
||||
|
||||
public Object[] getDefaultUserMessageArgs() {
|
||||
return defaultUserMessageArgs;
|
||||
}
|
||||
}
|
||||
+4
@@ -70,6 +70,10 @@ public class DerivedLoanDataProcessor {
|
||||
|
||||
Collections.sort(loanRepayments, new LoanRepaymentDataComparator());
|
||||
|
||||
if (loanRepayments.isEmpty()) {
|
||||
loanRepayments = null;
|
||||
}
|
||||
|
||||
return new DerivedLoanData(processRepaymentScheduleData, summaryData, loanRepayments);
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,12 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
||||
@Transient
|
||||
private final InterestRebateCalculatorFactory interestRebateCalculatorFactory = new DailyEquivalentInterestRebateCalculatorFactory();
|
||||
|
||||
public static Loan createNew(Organisation organisation,
|
||||
LoanProduct loanProduct, Client client,
|
||||
LoanProductRelatedDetail loanRepaymentScheduleDetail) {
|
||||
return new Loan(organisation, client, loanProduct, loanRepaymentScheduleDetail, null);
|
||||
}
|
||||
|
||||
public Loan() {
|
||||
this.organisation = null;
|
||||
this.client = null;
|
||||
|
||||
@@ -3,6 +3,10 @@ package org.mifosng.platform.loan.domain;
|
||||
import org.mifosng.platform.client.domain.Client;
|
||||
import org.mifosng.platform.organisation.domain.Organisation;
|
||||
|
||||
/**
|
||||
* remove in favour of static factory method on {@link Loan}. No advantage to builder patter in this case?
|
||||
*/
|
||||
@Deprecated
|
||||
public class LoanBuilder {
|
||||
|
||||
private Organisation organisation;
|
||||
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
package org.mifosng.platform.loan.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.mifosng.data.ApiParameterError;
|
||||
import org.mifosng.data.command.CalculateLoanScheduleCommand;
|
||||
import org.mifosng.platform.exceptions.PlatformApiDataValidationException;
|
||||
import org.mifosng.platform.loan.domain.AmortizationMethod;
|
||||
import org.mifosng.platform.loan.domain.InterestMethod;
|
||||
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
|
||||
|
||||
public class CalculateLoanScheduleCommandValidator {
|
||||
|
||||
private final CalculateLoanScheduleCommand command;
|
||||
|
||||
public CalculateLoanScheduleCommandValidator(CalculateLoanScheduleCommand command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
|
||||
if (StringUtils.isBlank(command.getCurrencyCode())) {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.currency.cannot.be.empty", "The parameter currencyCode cannot be blank.", "currencyCode");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getDigitsAfterDecimal() != null) {
|
||||
|
||||
if (command.getDigitsAfterDecimal() < 0 || command.getDigitsAfterDecimal() > 6) {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.currency.digitsAfterDecimal.must.be.between.zero.and.six.inclusive", "The parameter digitsAfterDecimal must be a value between 1 and 6.", "digitsAfterDecimal", command.getDigitsAfterDecimal(), 1, 6);
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.currency.digitsAfterDecimal.cannot.be.empty", "The parameter digitsAfterDecimal cannot be blank.", "digitsAfterDecimal");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
// FIXME - validate the number of digits before and after decimal provided (before is limited by database field, after is limited by number selected for product (and ultimately by the database field))
|
||||
if (command.getPrincipal() != null) {
|
||||
if (command.getPrincipal().doubleValue() <= Double.valueOf("0.0")) {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.principal.amount.must.be.greater.than.zero", "The parameter principalMoney must be greater than zero.", "principalMoney");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.principal.amount.cannot.be.empty", "The parameter principalMoney cannot be blank.", "principalMoney");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getRepaymentFrequency() != null) {
|
||||
PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequency());
|
||||
ApiParameterError error = null;
|
||||
switch (frequencyType) {
|
||||
case DAYS:
|
||||
error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 365);
|
||||
break;
|
||||
case WEEKS:
|
||||
error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 52);
|
||||
break;
|
||||
case MONTHS:
|
||||
error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 12);
|
||||
break;
|
||||
case YEARS:
|
||||
error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 2);
|
||||
break;
|
||||
default:
|
||||
error = ApiParameterError.parameterError("validation.msg.product.repayment.frequency.invalid", "The parameter repaymentFrequency is invalid.", "repaymentFrequency", command.getRepaymentFrequency());
|
||||
break;
|
||||
}
|
||||
if (error != null) {
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.repayment.frequency.cannot.be.empty", "The parameter repaymentFrequency cannot be empty.", "repaymentFrequency");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getNumberOfRepayments() != null) {
|
||||
if (command.getNumberOfRepayments() < 1 || command.getNumberOfRepayments() > 100) {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.number.of.repayments.is.out.of.range", "The parameter numberOfRepayments must between {1} and {2}.", "numberOfRepayments", command.getNumberOfRepayments(), 1, 100);
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.number.of.repayments.cannot.be.empty", "The parameter numberOfRepayments cannot be empty.", "numberOfRepayments");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getAmortizationMethod() != null) {
|
||||
AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationMethod());
|
||||
ApiParameterError error = null;
|
||||
switch (amortizationMethod) {
|
||||
case EQUAL_PRINCIPAL:
|
||||
break;
|
||||
case EQUAL_INSTALLMENTS:
|
||||
break;
|
||||
default:
|
||||
error = ApiParameterError.parameterError("validation.msg.product.amortization.method.invalid", "The parameter amortizationMethod is invalid.", "amortizationMethod", command.getAmortizationMethod());
|
||||
dataValidationErrors.add(error);
|
||||
break;
|
||||
}
|
||||
if (error != null) {
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.amortization.method.cannot.be.empty", "The parameter amortizationMethod cannot be empty.", "amortizationMethod");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getExpectedDisbursementDate() == null) {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.expectedDisbursementDate.method.cannot.be.blank", "The parameter expectedDisbursementDate cannot be empty.", "expectedDisbursementDate");
|
||||
dataValidationErrors.add(error);
|
||||
} else {
|
||||
if (command.getRepaymentsStartingFromDate() != null
|
||||
&& command.getExpectedDisbursementDate().isAfter(command.getRepaymentsStartingFromDate())) {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.expectedDisbursementDate.cannot.be.after.first.repayment.date",
|
||||
"The parameter expectedDisbursementDate has a date which falls after the given first repayment date.", "expectedDisbursementDate",
|
||||
command.getExpectedDisbursementDate(), command.getRepaymentsStartingFromDate());
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (command.getRepaymentsStartingFromDate() != null && command.getInterestCalculatedFromDate() == null) {
|
||||
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.interestCalculatedFromDate.must.be.entered.when.using.repayments.startfrom.field",
|
||||
"The parameter interestCalculatedFromDate cannot be empty when first repayment date is provided.", "interestCalculatedFromDate", command.getRepaymentsStartingFromDate());
|
||||
dataValidationErrors.add(error);
|
||||
} else if (command.getRepaymentsStartingFromDate() == null && command.getInterestCalculatedFromDate() != null) {
|
||||
|
||||
// validate interestCalculatedFromDate is after or on repaymentsStartingFromDate
|
||||
if (command.getExpectedDisbursementDate().isAfter(command.getInterestCalculatedFromDate())) {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.interestCalculatedFromDate.cannot.be.before.disbursement.date",
|
||||
"The parameter interestCalculatedFromDate cannot be before the date given for expected disbursement.", "interestCalculatedFromDate", command.getInterestCalculatedFromDate(), command.getExpectedDisbursementDate());
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (command.getInterestRatePerPeriod()!= null) {
|
||||
if (command.getInterestRatePerPeriod().doubleValue() < Double.valueOf("0.0")) {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.interest.rate.per.period.amount.cannot.be.negative",
|
||||
"The parameter interestRatePerPeriodFormatted cannot be less than zero.", "interestRatePerPeriodFormatted",
|
||||
command.getInterestRatePerPeriod());
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.interest.rate.per.period.amount.cannot.be.empty",
|
||||
"The parameter interestRatePerPeriodFormatted cannot be empty.", "interestRatePerPeriodFormatted",
|
||||
command.getInterestRatePerPeriod());
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
// interest rate period type
|
||||
if (command.getInterestRateFrequencyMethod() != null) {
|
||||
PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyMethod());
|
||||
ApiParameterError error = null;
|
||||
switch (frequencyType) {
|
||||
case DAYS:
|
||||
break;
|
||||
case WEEKS:
|
||||
break;
|
||||
case MONTHS:
|
||||
break;
|
||||
case YEARS:
|
||||
break;
|
||||
default:
|
||||
error = ApiParameterError.parameterError("validation.msg.product.interest.frequency.invalid",
|
||||
"The parameter interestRateFrequencyMethod cannot be empty.", "interestRateFrequencyMethod", command.getInterestRateFrequencyMethod());
|
||||
break;
|
||||
}
|
||||
if (error != null) {
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.interest.frequency.cannot.be.empty",
|
||||
"The parameter interestRateFrequencyMethod cannot be empty.", "interestRateFrequencyMethod");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getInterestMethod() != null) {
|
||||
InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestMethod());
|
||||
ApiParameterError error = null;
|
||||
switch (interestMethod) {
|
||||
case DECLINING_BALANCE:
|
||||
break;
|
||||
case FLAT:
|
||||
break;
|
||||
default:
|
||||
error = ApiParameterError.parameterError("validation.msg.product.interest.method.invalid",
|
||||
"The parameter interestMethod is invalid.", "interestMethod", command.getInterestMethod());
|
||||
break;
|
||||
}
|
||||
if (error != null) {
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
} else {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.interest.method.cannot.be.empty",
|
||||
"The parameter interestMethod cannot be empty.", "interestMethod");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
private ApiParameterError validateNumberExistsAndInRange(String errorCodePreFix, String fieldName, Integer value, int min, int max) {
|
||||
ApiParameterError error = null;
|
||||
if (value == null) {
|
||||
error = ApiParameterError.parameterError(errorCodePreFix + ".cannot.be.empty", "The parameter " + fieldName + " cannot be blank.", fieldName);
|
||||
} else {
|
||||
if (value < min || value > max) {
|
||||
error = ApiParameterError.parameterError(errorCodePreFix + ".outside.allowed.range", "The parameter " + fieldName + " must be between {0} and {1} inclusive.", fieldName, min, max);
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package org.mifosng.platform;
|
||||
package org.mifosng.platform.loan.service;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.data.LoanPayoffReadModel;
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
package org.mifosng.platform;
|
||||
package org.mifosng.platform.loan.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@@ -88,7 +88,6 @@ public class CalculationPlatformServiceImpl implements
|
||||
@Override
|
||||
public LoanPayoffReadModel calculatePayoffOn(final Long loanId, final LocalDate payoffDate) {
|
||||
|
||||
// TODO - use jdbc approach to calculating payoff if possible.
|
||||
final Loan loan = this.loanRepository.findOne(loanId);
|
||||
|
||||
final LoanPayoffSummary payoffSummary = loan.getPayoffSummaryOn(payoffDate);
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package org.mifosng.platform.loan.service;
|
||||
|
||||
import org.mifosng.data.EntityIdentifier;
|
||||
import org.mifosng.data.command.SubmitLoanApplicationCommand;
|
||||
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);
|
||||
|
||||
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_DELETE_LOAN_THAT_IS_SUBMITTED_AND_NOT_APPROVED')")
|
||||
EntityIdentifier deleteLoan(Long loanId);
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
package org.mifosng.platform.loan.service;
|
||||
|
||||
import static org.mifosng.platform.Specifications.loansThatMatch;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.data.EntityIdentifier;
|
||||
import org.mifosng.data.LoanSchedule;
|
||||
import org.mifosng.data.MoneyData;
|
||||
import org.mifosng.data.ScheduledLoanInstallment;
|
||||
import org.mifosng.data.command.SubmitLoanApplicationCommand;
|
||||
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.exceptions.NoAuthorizationException;
|
||||
import org.mifosng.platform.exceptions.PlatformDomainRuleException;
|
||||
import org.mifosng.platform.exceptions.PlatformResourceNotFoundException;
|
||||
import org.mifosng.platform.loan.domain.AmortizationMethod;
|
||||
import org.mifosng.platform.loan.domain.DefaultLoanLifecycleStateMachine;
|
||||
import org.mifosng.platform.loan.domain.InterestCalculationPeriodMethod;
|
||||
import org.mifosng.platform.loan.domain.InterestMethod;
|
||||
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.LoanProductRelatedDetail;
|
||||
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.LoanStatusRepository;
|
||||
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
|
||||
import org.mifosng.platform.loanschedule.domain.AprCalculator;
|
||||
import org.mifosng.platform.security.PlatformSecurityContext;
|
||||
import org.mifosng.platform.user.domain.AppUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final LoanRepository loanRepository;
|
||||
private final LoanProductRepository loanProductRepository;
|
||||
private final LoanStatusRepository loanStatusRepository;
|
||||
private final ClientRepository clientRepository;
|
||||
private final NoteRepository noteRepository;
|
||||
|
||||
private final AprCalculator aprCalculator = new AprCalculator();
|
||||
|
||||
@Autowired
|
||||
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
|
||||
final LoanRepository loanRepository, final LoanStatusRepository loanStatusRepository, final LoanProductRepository loanProductRepository,
|
||||
final ClientRepository clientRepository, final NoteRepository noteRepository) {
|
||||
this.context = context;
|
||||
this.loanRepository = loanRepository;
|
||||
this.loanProductRepository = loanProductRepository;
|
||||
this.clientRepository = clientRepository;
|
||||
this.loanStatusRepository = loanStatusRepository;
|
||||
this.noteRepository = noteRepository;
|
||||
}
|
||||
|
||||
private boolean isBeforeToday(final LocalDate date) {
|
||||
return date.isBefore(new LocalDate());
|
||||
}
|
||||
|
||||
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
|
||||
List<LoanStatus> allowedLoanStatuses = this.loanStatusRepository.findAll();
|
||||
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public EntityIdentifier submitLoanApplication(final SubmitLoanApplicationCommand command) {
|
||||
|
||||
AppUser currentUser = context.authenticatedUser();
|
||||
|
||||
SubmitLoanApplicationCommandValidator validator = new SubmitLoanApplicationCommandValidator(command);
|
||||
validator.validate();
|
||||
|
||||
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 add backdated loan.");
|
||||
}
|
||||
|
||||
// FIXME - looks like assembly of LoanProductRelatedDetail is common with loan product and calculate loan schedule areas. remove duplication into an assembler.
|
||||
LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId());
|
||||
Client client = this.clientRepository.findOne(command.getApplicantId());
|
||||
|
||||
MonetaryCurrency currency = new MonetaryCurrency(command.getCurrencyCode(), command.getDigitsAfterDecimal());
|
||||
|
||||
final BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(command.getInterestRatePerPeriod().doubleValue());
|
||||
final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyMethod());
|
||||
|
||||
BigDecimal defaultAnnualNominalInterestRate = aprCalculator.calculateFrom(interestPeriodFrequencyType, command.getInterestRatePerPeriod());
|
||||
|
||||
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestMethod());
|
||||
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodMethod());
|
||||
|
||||
final Integer repayEvery = command.getRepaymentEvery();
|
||||
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequency());
|
||||
final Integer defaultNumberOfInstallments = command.getNumberOfRepayments();
|
||||
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationMethod());
|
||||
final boolean flexibleRepaymentSchedule = command.isFlexibleRepaymentSchedule();
|
||||
final boolean interestRebateAllowed = command.isInterestRebateAllowed();
|
||||
|
||||
LoanProductRelatedDetail loanRepaymentScheduleDetail = new LoanProductRelatedDetail(currency,
|
||||
command.getPrincipal(), defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
|
||||
interestMethod, interestCalculationPeriodMethod,
|
||||
repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, command.getInArrearsToleranceAmount(),
|
||||
flexibleRepaymentSchedule, interestRebateAllowed);
|
||||
|
||||
LoanSchedule loanSchedule = command.getLoanSchedule();
|
||||
List<ScheduledLoanInstallment> loanRepaymentSchedule = loanSchedule.getScheduledLoanInstallments();
|
||||
|
||||
Loan loan = Loan.createNew(currentUser.getOrganisation(), loanProduct, client, loanRepaymentScheduleDetail);
|
||||
|
||||
for (ScheduledLoanInstallment scheduledLoanInstallment : loanRepaymentSchedule) {
|
||||
|
||||
MoneyData readPrincipalDue = scheduledLoanInstallment.getPrincipalDue();
|
||||
MoneyData readInterestDue = scheduledLoanInstallment.getInterestDue();
|
||||
|
||||
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
|
||||
loan, scheduledLoanInstallment.getInstallmentNumber(),
|
||||
scheduledLoanInstallment.getPeriodStart(),
|
||||
scheduledLoanInstallment.getPeriodEnd(), readPrincipalDue.getAmount(),
|
||||
readInterestDue.getAmount());
|
||||
loan.addRepaymentScheduleInstallment(installment);
|
||||
}
|
||||
|
||||
loan.submitApplication(submittedOn, command.getExpectedDisbursementDate(), command.getRepaymentsStartingFromDate(), command.getInterestCalculatedFromDate(), defaultLoanLifecycleStateMachine());
|
||||
this.loanRepository.save(loan);
|
||||
|
||||
if (StringUtils.isNotBlank(command.getSubmittedOnNote())) {
|
||||
Note note = Note.loanNote(currentUser.getOrganisation(), loan, command.getSubmittedOnNote());
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
return new EntityIdentifier(loan.getId());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public EntityIdentifier deleteLoan(Long loanId) {
|
||||
|
||||
AppUser currentUser = context.authenticatedUser();
|
||||
|
||||
Loan loan = this.loanRepository.findOne(loansThatMatch(currentUser.getOrganisation(), loanId));
|
||||
if (loan == null) {
|
||||
throw new PlatformResourceNotFoundException("error.msg.loan.id.invalid", "Loan with identifier {0} does not exist", loanId);
|
||||
}
|
||||
|
||||
if (loan.isNotSubmittedAndPendingApproval()) {
|
||||
throw new PlatformDomainRuleException("error.msg.cannot.delete.loan.in.its.present.state", "Loan with identifier {0} cannot be deleted in its current state.", loanId);
|
||||
}
|
||||
|
||||
Long clientId = loan.getClient().getId();
|
||||
|
||||
List<Note> relatedNotes = this.noteRepository.findByLoanId(loan.getId());
|
||||
this.noteRepository.deleteInBatch(relatedNotes);
|
||||
|
||||
this.loanRepository.delete(loanId);
|
||||
|
||||
return new EntityIdentifier(clientId);
|
||||
}
|
||||
}
|
||||
+14
-12
@@ -1,12 +1,12 @@
|
||||
package org.mifosng.platform;
|
||||
package org.mifosng.platform.loan.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.mifosng.data.ErrorResponse;
|
||||
import org.mifosng.data.ApiParameterError;
|
||||
import org.mifosng.data.command.CalculateLoanScheduleCommand;
|
||||
import org.mifosng.data.command.SubmitLoanApplicationCommand;
|
||||
import org.mifosng.platform.exceptions.NewDataValidationException;
|
||||
import org.mifosng.platform.exceptions.PlatformApiDataValidationException;
|
||||
|
||||
public class SubmitLoanApplicationCommandValidator {
|
||||
|
||||
@@ -17,19 +17,23 @@ public class SubmitLoanApplicationCommandValidator {
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
List<ErrorResponse> dataValidationErrors = new ArrayList<ErrorResponse>();
|
||||
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
|
||||
if (command.getLoanSchedule() == null) {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.submit.loan.loan.schedule.cannot.be.blank", "loanSchedule");
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.submit.loan.loan.schedule.cannot.be.blank",
|
||||
"The parameter loanSchedule cannot be empty.", "loanSchedule");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
|
||||
if (command.getSubmittedOnDate() == null) {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.loan.submitted.on.date.cannot.be.blank", "submittedOnDate");
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.submitted.on.date.cannot.be.blank",
|
||||
"The parameter submittedOnDateFormatted cannot be empty.", "submittedOnDateFormatted");
|
||||
dataValidationErrors.add(error);
|
||||
} else {
|
||||
if (command.getSubmittedOnDate().isAfter(command.getExpectedDisbursementDate())) {
|
||||
ErrorResponse error = new ErrorResponse("validation.msg.loan.submitted.on.date.cannot.be.after.expectedDisbursementDate", "submittedOnDate");
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.submitted.on.date.cannot.be.after.expectedDisbursementDate",
|
||||
"The date of parameter submittedOnDateFormatted cannot fall after the date given for expectedDisbursementDateFormatted.", "submittedOnDateFormatted",
|
||||
command.getSubmittedOnDateFormatted(), command.getExpectedDisbursementDateFormatted());
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
}
|
||||
@@ -45,8 +49,6 @@ public class SubmitLoanApplicationCommandValidator {
|
||||
command.getRepaymentFrequency(),
|
||||
command.getNumberOfRepayments(),
|
||||
command.getAmortizationMethod(),
|
||||
command.isFlexibleRepaymentSchedule(),
|
||||
command.isInterestRebateAllowed(),
|
||||
command.getExpectedDisbursementDate(),
|
||||
command.getRepaymentsStartingFromDate(),
|
||||
command.getInterestCalculatedFromDate());
|
||||
@@ -54,12 +56,12 @@ public class SubmitLoanApplicationCommandValidator {
|
||||
CalculateLoanScheduleCommandValidator validator = new CalculateLoanScheduleCommandValidator(
|
||||
calculateLoanScheduleCommand);
|
||||
validator.validate();
|
||||
} catch (NewDataValidationException e) {
|
||||
dataValidationErrors.addAll(e.getValidationErrors());
|
||||
} catch (PlatformApiDataValidationException e) {
|
||||
dataValidationErrors.addAll(e.getErrors());
|
||||
}
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new NewDataValidationException(dataValidationErrors, "Data validation errors exist.");
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -11,6 +11,8 @@ import org.mifosng.platform.loan.domain.AmortizationMethod;
|
||||
import org.mifosng.platform.loan.domain.InterestMethod;
|
||||
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
|
||||
|
||||
// FIXME - There is a lot of common validation across parameters for LoanProduct create/update, loan schedule calculation and submit new loan application flows.
|
||||
// - reduce duplicator across this validators.
|
||||
public class LoanProductCommandValidator {
|
||||
|
||||
public void validateForUpdate(LoanProductCommand command) {
|
||||
@@ -67,7 +69,6 @@ public class LoanProductCommandValidator {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.principal.amount.must.be.greater.than.zero", "The parameter amount must be greater than zero.", "amount");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
// TODO - check number of digits before decimal and after decimal
|
||||
} else {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.principal.amount.cannot.be.empty", "The parameter amount cannot be blank.", "amount");
|
||||
dataValidationErrors.add(error);
|
||||
|
||||
@@ -17,13 +17,19 @@ import org.joda.time.format.DateTimeFormatter;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
import org.mifosng.data.LoanPayoffReadModel;
|
||||
import org.mifosng.data.MoneyData;
|
||||
import org.mifosng.platform.CalculationPlatformService;
|
||||
import org.mifosng.platform.loan.service.CalculationPlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.sun.jersey.api.json.JSONWithPadding;
|
||||
|
||||
/**
|
||||
* Payoff functionality was something CreoCore was looking for e.g. if paid in full on date x what should be paid? full minus any rebate or incentive provided.
|
||||
*
|
||||
* NOTE: JSONP functionality not needed now due to use of CORS
|
||||
*/
|
||||
@Deprecated
|
||||
@Path("/open/loan/{loanId}")
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
@@ -20,15 +19,11 @@ import org.mifosng.data.EntityIdentifier;
|
||||
import org.mifosng.data.ErrorResponse;
|
||||
import org.mifosng.data.ErrorResponseList;
|
||||
import org.mifosng.data.LoanRepaymentData;
|
||||
import org.mifosng.data.LoanSchedule;
|
||||
import org.mifosng.data.command.AdjustLoanTransactionCommand;
|
||||
import org.mifosng.data.command.CalculateLoanScheduleCommand;
|
||||
import org.mifosng.data.command.LoanStateTransitionCommand;
|
||||
import org.mifosng.data.command.LoanTransactionCommand;
|
||||
import org.mifosng.data.command.SubmitLoanApplicationCommand;
|
||||
import org.mifosng.data.command.UndoLoanApprovalCommand;
|
||||
import org.mifosng.data.command.UndoLoanDisbursalCommand;
|
||||
import org.mifosng.platform.CalculationPlatformService;
|
||||
import org.mifosng.platform.ReadPlatformService;
|
||||
import org.mifosng.platform.ReadPlatformServiceImpl;
|
||||
import org.mifosng.platform.WritePlatformService;
|
||||
@@ -49,92 +44,12 @@ public class LoanResource {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ReadPlatformServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private CalculationPlatformService calculationPlatformService;
|
||||
|
||||
@Autowired
|
||||
private ReadPlatformService readPlatformService;
|
||||
|
||||
@Autowired
|
||||
private WritePlatformService writePlatformService;
|
||||
|
||||
@POST
|
||||
@Path("calculate")
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
|
||||
public Response calculateLoanSchedule(final CalculateLoanScheduleCommand command) {
|
||||
|
||||
try {
|
||||
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command);
|
||||
|
||||
return Response.ok().entity(loanSchedule).build();
|
||||
} catch (UnAuthenticatedUserException e) {
|
||||
throw new WebApplicationException(Response.status(Status.UNAUTHORIZED).build());
|
||||
} catch (AccessDeniedException e) {
|
||||
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission", "id");
|
||||
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
|
||||
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(list).build());
|
||||
} catch (ApplicationDomainRuleException e) {
|
||||
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getErrors())).build());
|
||||
} catch (NewDataValidationException e) {
|
||||
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getValidationErrors())).build());
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("new")
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
public Response submitLoanApplication(final SubmitLoanApplicationCommand command) {
|
||||
|
||||
try {
|
||||
EntityIdentifier identifier = this.writePlatformService.submitLoanApplication(command);
|
||||
|
||||
return Response.ok().entity(identifier).build();
|
||||
} catch (UnAuthenticatedUserException e) {
|
||||
throw new WebApplicationException(Response.status(Status.UNAUTHORIZED).build());
|
||||
} catch (AccessDeniedException e) {
|
||||
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission", "id");
|
||||
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
|
||||
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(list).build());
|
||||
} catch (ApplicationDomainRuleException e) {
|
||||
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getErrors())).build());
|
||||
} catch (NewDataValidationException e) {
|
||||
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getValidationErrors())).build());
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{loanId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
public Response deleteLoanApplication(@PathParam("loanId") final Long loanId) {
|
||||
|
||||
try {
|
||||
EntityIdentifier identifier = this.writePlatformService.deleteLoan(loanId);
|
||||
|
||||
return Response.ok().entity(identifier).build();
|
||||
} catch (UnAuthenticatedUserException e) {
|
||||
throw new WebApplicationException(Response.status(Status.UNAUTHORIZED).build());
|
||||
} catch (AccessDeniedException e) {
|
||||
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission", "id");
|
||||
ErrorResponseList list = new ErrorResponseList(Arrays.asList(errorResponse));
|
||||
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(list).build());
|
||||
} catch (ApplicationDomainRuleException e) {
|
||||
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getErrors())).build());
|
||||
} catch (NewDataValidationException e) {
|
||||
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(e.getValidationErrors())).build());
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
|
||||
List<ErrorResponse> allErrors = new ArrayList<ErrorResponse>();
|
||||
ErrorResponse err = new ErrorResponse("unknown.error", "error", e.getMessage());
|
||||
allErrors.add(err);
|
||||
|
||||
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new ErrorResponseList(allErrors)).build());
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("approve")
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||
|
||||
Reference in New Issue
Block a user