add remaining state transitions and loan transactions to api format.

This commit is contained in:
Keith Woodlock
2012-05-17 02:07:41 +01:00
parent 8bc0b7461f
commit 7bb8b9359e
28 changed files with 397 additions and 1462 deletions
@@ -29,12 +29,12 @@ public class AdjustLoanTransactionCommandValidator {
dataValidationErrors.add(error);
}
if (command.getPaymentDate() == null) {
if (command.getTransactionDate() == null) {
ErrorResponse error = new ErrorResponse("validation.msg.loan.repayment.date.cannot.be.blank", "paymentDate");
dataValidationErrors.add(error);
}
if (command.getPaymentAmount() == null || command.getPaymentAmount().doubleValue() < 0) {
if (command.getTransactionAmount() == null || command.getTransactionAmount().doubleValue() < 0) {
ErrorResponse error = new ErrorResponse("validation.msg.loan.adjustment.must.be.zero.or.greater", "paymentAmount");
dataValidationErrors.add(error);
}
@@ -247,9 +247,9 @@ public class ImportPlatformServiceImpl implements ImportPlatformService {
for (LoanTransactionCommand repaymentDetail : command.getRepayments()) {
Loan loan = findLoanByIdentifier(allLoans, repaymentDetail.getLoanId().toString());
Money repaymentAmount = Money.of(loan.getCurrency(), repaymentDetail.getPaymentAmount());
Money repaymentAmount = Money.of(loan.getCurrency(), repaymentDetail.getTransactionAmount());
LoanTransaction loanRepayment = LoanTransaction.repayment(repaymentAmount, repaymentDetail.getPaymentDate());
LoanTransaction loanRepayment = LoanTransaction.repayment(repaymentAmount, repaymentDetail.getTransactionDate());
loan.makeRepayment(loanRepayment, loanLifecycleStateMachine);
}
@@ -24,12 +24,12 @@ public class LoanTransactionValidator {
dataValidationErrors.add(error);
}
if (command.getPaymentDate() == null) {
if (command.getTransactionDate() == null) {
ErrorResponse error = new ErrorResponse("validation.msg.loan.repayment.date.cannot.be.blank", "transactionDate");
dataValidationErrors.add(error);
}
if (command.getPaymentAmount() == null || command.getPaymentAmount().doubleValue() <= 0) {
if (command.getTransactionAmount() == null || command.getTransactionAmount().doubleValue() <= 0) {
ErrorResponse error = new ErrorResponse("validation.msg.loan.repayment.must.be.greater.than.zero", "transactionAmount");
dataValidationErrors.add(error);
}
@@ -2,9 +2,7 @@ package org.mifosng.platform;
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.UndoLoanDisbursalCommand;
import org.mifosng.data.command.UpdateUsernamePasswordCommand;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -12,12 +10,6 @@ public interface WritePlatformService {
void updateUsernamePasswordOnFirstTimeLogin(UpdateUsernamePasswordCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_DISBURSE_LOAN_ROLE', 'CAN_DISBURSE_LOAN_IN_THE_PAST_ROLE')")
EntityIdentifier disburseLoan(LoanStateTransitionCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_UNDO_LOAN_DISBURSAL_ROLE')")
public EntityIdentifier undloLoanDisbursal(UndoLoanDisbursalCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_MAKE_LOAN_REPAYMENT_ROLE', 'CAN_MAKE_LOAN_REPAYMENT_IN_THE_PAST_ROLE')")
public EntityIdentifier makeLoanRepayment(LoanTransactionCommand command);
@@ -3,7 +3,6 @@ package org.mifosng.platform;
import static org.mifosng.platform.Specifications.loansThatMatch;
import static org.mifosng.platform.Specifications.usersThatMatch;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -11,18 +10,11 @@ import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosng.data.EntityIdentifier;
import org.mifosng.data.ErrorResponse;
import org.mifosng.data.LoanSchedule;
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.UndoLoanApprovalCommand;
import org.mifosng.data.command.UndoLoanDisbursalCommand;
import org.mifosng.data.command.UpdateUsernamePasswordCommand;
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.UnAuthenticatedUserException;
@@ -33,13 +25,11 @@ import org.mifosng.platform.infrastructure.UsernameAlreadyExistsException;
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.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.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;
@@ -141,128 +131,6 @@ public class WritePlatformServiceJpaRepositoryImpl implements WritePlatformServi
return Arrays.asList(errorResponse);
}
@Transactional
@Override
public EntityIdentifier disburseLoan(final LoanStateTransitionCommand command) {
AppUser currentUser = extractAuthenticatedUser();
LoanStateTransitionCommandValidator validator = new LoanStateTransitionCommandValidator(command);
validator.validate();
Loan loan = this.loanRepository.findOne(loansThatMatch(
currentUser.getOrganisation(), command.getLoanId()));
if (loan == null) {
throw new ApplicationDomainRuleException(loanIdentifierDoesNotExistError(command.getLoanId()), "No loan exists with id: " + command.getLoanId());
}
if (this.isBeforeToday(command.getEventDate()) && currentUser.canNotDisburseLoanInPast()) {
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission.to.disburse.loan.in.past", "eventDate");
throw new ApplicationDomainRuleException(Arrays.asList(errorResponse));
}
LocalDate disbursedOn = command.getEventDate();
String comment = command.getComment();
LocalDate actualDisbursementDate = new LocalDate(disbursedOn);
if (loan.isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
LocalDate repaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate();
LocalDate interestCalculatedFromDate = loan.getInterestCalculatedFromDate();
Number principalAsDecimal = loan.getLoanRepaymentScheduleDetail().getPrincipal().getAmount();
String currencyCode = loan.getLoanRepaymentScheduleDetail().getPrincipal().getCurrencyCode();
int currencyDigits = loan.getLoanRepaymentScheduleDetail().getPrincipal().getCurrencyDigitsAfterDecimal();
Number interestRatePerYear = loan.getLoanRepaymentScheduleDetail().getAnnualNominalInterestRate();
Integer numberOfInstallments = loan.getLoanRepaymentScheduleDetail().getNumberOfRepayments();
Integer repaidEvery = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
Integer selectedRepaymentFrequency = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType().getValue();
Integer selectedRepaymentSchedule = loan.getLoanRepaymentScheduleDetail().getAmortizationMethod().getValue();
// use annual percentage rate to re-calculate loan schedule for late disbursement
Number interestRatePerPeriod = interestRatePerYear;
Integer interestRateFrequencyMethod = PeriodFrequencyType.YEARS.getValue();
Integer interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod().getValue();
Integer interestCalculationInPeriod = loan.getLoanRepaymentScheduleDetail().getInterestCalculationPeriodMethod().getValue();
CalculateLoanScheduleCommand calculateCommand = new CalculateLoanScheduleCommand(currencyCode, currencyDigits, principalAsDecimal,
interestRatePerPeriod, interestRateFrequencyMethod, interestMethod, interestCalculationInPeriod,
repaidEvery, selectedRepaymentFrequency, numberOfInstallments,
selectedRepaymentSchedule, actualDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate);
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateCommand);
List<LoanRepaymentScheduleInstallment> modifiedLoanRepaymentSchedule = new ArrayList<LoanRepaymentScheduleInstallment>();
for (ScheduledLoanInstallment scheduledLoanInstallment : loanSchedule
.getScheduledLoanInstallments()) {
final MonetaryCurrency monetaryCurrency = new MonetaryCurrency(
scheduledLoanInstallment.getPrincipalDue().getCurrencyCode(),
scheduledLoanInstallment.getPrincipalDue().getCurrencyDigitsAfterDecimal());
Money principal = Money.of(monetaryCurrency,
scheduledLoanInstallment.getPrincipalDue().getAmount());
Money interest = Money.of(monetaryCurrency,
scheduledLoanInstallment.getInterestDue().getAmount());
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
loan, scheduledLoanInstallment.getInstallmentNumber(),
scheduledLoanInstallment.getPeriodStart(),
scheduledLoanInstallment.getPeriodEnd(), principal.getAmount(),
interest.getAmount());
modifiedLoanRepaymentSchedule.add(installment);
}
loan.disburseWithModifiedRepaymentSchedule(disbursedOn, comment, modifiedLoanRepaymentSchedule, defaultLoanLifecycleStateMachine());
} else {
loan.disburse(disbursedOn, defaultLoanLifecycleStateMachine());
}
this.loanRepository.save(loan);
if (StringUtils.isNotBlank(command.getComment())) {
Note note = Note.loanNote(currentUser.getOrganisation(), loan, command.getComment());
this.noteRepository.save(note);
}
return new EntityIdentifier(loan.getClient().getId());
}
@Transactional
@Override
public EntityIdentifier undloLoanDisbursal(
final UndoLoanDisbursalCommand command) {
AppUser currentUser = extractAuthenticatedUser();
Loan loan = this.loanRepository.findOne(loansThatMatch(
currentUser.getOrganisation(), command.getLoanId()));
if (loan == null) {
throw new ApplicationDomainRuleException(loanIdentifierDoesNotExistError(command.getLoanId()), "No loan exists with id: " + command.getLoanId());
}
if (loan.isActualDisbursedOnDateEarlierOrLaterThanExpected()) {
// recalculate loan schedule using original settings.
}
loan.undoDisbursal(defaultLoanLifecycleStateMachine());
this.loanRepository.save(loan);
// TODO - this may not be wanted.
Note note = Note.loanNote(currentUser.getOrganisation(), loan, "Undo of disbursal.");
this.noteRepository.save(note);
return new EntityIdentifier(loan.getClient().getId());
}
@Transactional
@Override
public EntityIdentifier makeLoanRepayment(final LoanTransactionCommand command) {
@@ -278,16 +146,16 @@ public class WritePlatformServiceJpaRepositoryImpl implements WritePlatformServi
throw new ApplicationDomainRuleException(loanIdentifierDoesNotExistError(command.getLoanId()), "No loan exists with id: " + command.getLoanId());
}
if (this.isBeforeToday(command.getPaymentDate()) && currentUser.canNotMakeRepaymentOnLoanInPast()) {
if (this.isBeforeToday(command.getTransactionDate()) && currentUser.canNotMakeRepaymentOnLoanInPast()) {
ErrorResponse errorResponse = new ErrorResponse("error.msg.no.permission.to.make.repayment.on.loan.in.past", "eventDate");
throw new ApplicationDomainRuleException(Arrays.asList(errorResponse));
}
Money repayment = Money.of(loan.getLoanRepaymentScheduleDetail()
.getPrincipal().getCurrency(),
command.getPaymentAmount());
command.getTransactionAmount());
LoanTransaction loanRepayment = LoanTransaction.repayment(repayment, command.getPaymentDate());
LoanTransaction loanRepayment = LoanTransaction.repayment(repayment, command.getTransactionDate());
loan.makeRepayment(loanRepayment, defaultLoanLifecycleStateMachine());
this.loanTransactionRepository.save(loanRepayment);
this.loanRepository.save(loan);
@@ -323,12 +191,12 @@ public class WritePlatformServiceJpaRepositoryImpl implements WritePlatformServi
Money transactionAmount = Money.of(loan
.getLoanRepaymentScheduleDetail().getPrincipal()
.getCurrency(), command.getPaymentAmount());
.getCurrency(), command.getTransactionAmount());
// adjustment is only supported for repayments and waivers at present
LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmount, command.getPaymentDate());
LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmount, command.getTransactionDate());
if (transactionToAdjust.isWaiver()) {
newTransactionDetail = LoanTransaction.waiver(transactionAmount, command.getPaymentDate());
newTransactionDetail = LoanTransaction.waiver(transactionAmount, command.getTransactionDate());
}
loan.adjustExistingTransaction(transactionToAdjust, newTransactionDetail, defaultLoanLifecycleStateMachine());
@@ -363,9 +231,9 @@ public class WritePlatformServiceJpaRepositoryImpl implements WritePlatformServi
Money waived = Money.of(loan.getLoanRepaymentScheduleDetail()
.getPrincipal().getCurrency(),
command.getPaymentAmount());
command.getTransactionAmount());
LoanTransaction waiver = LoanTransaction.waiver(waived, command.getPaymentDate());
LoanTransaction waiver = LoanTransaction.waiver(waived, command.getTransactionDate());
loan.waive(waiver, defaultLoanLifecycleStateMachine());
@@ -7,6 +7,7 @@ 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;
@@ -18,10 +19,13 @@ 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.LoanRepaymentData;
import org.mifosng.data.LoanSchedule;
import org.mifosng.data.NewLoanWorkflowStepOneData;
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;
@@ -159,9 +163,12 @@ public class LoansApiResource {
@Path("{loanId}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({ MediaType.APPLICATION_JSON})
public Response rejectLoanApplication(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam,
public Response stateTransitions(@PathParam("loanId") final Long loanId,
@QueryParam("command") final String commandParam,
final LoanStateTransitionCommand command) {
hardcodeUserIntoSecurityContext();
LocalDate eventDate = this.apiDataConversionService.convertFrom(command.getEventDateFormatted(), "eventDateFormatted", command.getDateFormat());
command.setLoanId(loanId);
@@ -176,21 +183,21 @@ public class LoansApiResource {
} else if (StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase("approve")) {
EntityIdentifier identifier = this.loanWritePlatformService.approveLoanApplication(command);
return Response.ok().entity(identifier).build();
} else if (StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase("undoapproval")) {
UndoLoanApprovalCommand undoCommand = new UndoLoanApprovalCommand(loanId);
EntityIdentifier identifier = this.loanWritePlatformService.undoLoanApproval(undoCommand);
} else if (StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase("disburse")) {
EntityIdentifier identifier = this.loanWritePlatformService.disburseLoan(command);
return Response.ok().entity(identifier).build();
}
return Response.ok().build();
}
@POST
@Path("{loanId}/undo")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({ MediaType.APPLICATION_JSON})
public Response rejectLoanApplication(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam) {
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON})
public Response undoStateTransitions(@PathParam("loanId") final Long loanId, @QueryParam("command") final String commandParam) {
hardcodeUserIntoSecurityContext();
if (StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase("undoapproval")) {
UndoLoanApprovalCommand undoCommand = new UndoLoanApprovalCommand(loanId);
@@ -198,10 +205,87 @@ public class LoansApiResource {
return Response.ok().entity(identifier).build();
} else if (StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase("undodisbursal")) {
UndoLoanDisbursalCommand undoCommand = new UndoLoanDisbursalCommand(loanId);
EntityIdentifier identifier = this.writePlatformService.undloLoanDisbursal(undoCommand);
EntityIdentifier identifier = this.loanWritePlatformService.undloLoanDisbursal(undoCommand);
return Response.ok().entity(identifier).build();
}
return Response.ok().build();
}
@POST
@Path("{loanId}/transactions")
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON})
public Response executeLoanTransaction(@PathParam("loanId") final Long loanId, @QueryParam("transactionType") final String transactionType, final LoanTransactionCommand command) {
hardcodeUserIntoSecurityContext();
command.setLoanId(loanId);
LocalDate transactionDate = apiDataConversionService.convertFrom(command.getTransactionDateFormatted(), "transactionDateFormatted", command.getDateFormat());
command.setTransactionDate(transactionDate);
BigDecimal transactionAmount = apiDataConversionService.convertFrom(command.getTransactionAmountFormatted(), "transactionAmountFormatted", Locale.UK);
command.setTransactionAmount(transactionAmount);
if (StringUtils.isNotBlank(transactionType) && transactionType.trim().equalsIgnoreCase("waiver")) {
EntityIdentifier identifier = this.writePlatformService.waiveLoanAmount(command);
return Response.ok().entity(identifier).build();
}
EntityIdentifier identifier = this.writePlatformService.makeLoanRepayment(command);
return Response.ok().entity(identifier).build();
}
@GET
@Path("{loanId}/transactions/template")
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON})
public Response retrieveNewRepaymentDetails(@PathParam("loanId") final Long loanId, @QueryParam("type") final String transactionType) {
hardcodeUserIntoSecurityContext();
if (StringUtils.isNotBlank(transactionType) && transactionType.trim().equalsIgnoreCase("waiver")) {
LoanRepaymentData loanWaiverData = this.readPlatformService.retrieveNewLoanWaiverDetails(loanId);
return Response.ok().entity(loanWaiverData).build();
}
LoanRepaymentData loanRepaymentData = this.readPlatformService.retrieveNewLoanRepaymentDetails(loanId);
return Response.ok().entity(loanRepaymentData).build();
}
@GET
@Path("{loanId}/transactions/{repaymentId}")
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON})
public Response retrieveRepaymentDetails(@PathParam("loanId") final Long loanId, @PathParam("repaymentId") final Long repaymentId) {
hardcodeUserIntoSecurityContext();
LoanRepaymentData loanRepaymentData = this.readPlatformService.retrieveLoanRepaymentDetails(loanId, repaymentId);
return Response.ok().entity(loanRepaymentData).build();
}
@PUT
@Path("{loanId}/transactions/{repaymentId}")
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response adjustLoanTransaction(@PathParam("loanId") final Long loanId, @PathParam("repaymentId") final Long repaymentId, final AdjustLoanTransactionCommand command) {
hardcodeUserIntoSecurityContext();
command.setLoanId(loanId);
command.setRepaymentId(repaymentId);
LocalDate transactionDate = apiDataConversionService.convertFrom(command.getTransactionDateFormatted(), "transactionDateFormatted", command.getDateFormat());
command.setTransactionDate(transactionDate);
BigDecimal transactionAmount = apiDataConversionService.convertFrom(command.getTransactionAmountFormatted(), "transactionAmountFormatted", Locale.UK);
command.setTransactionAmount(transactionAmount);
EntityIdentifier identifier = this.writePlatformService.adjustLoanTransaction(command);
return Response.ok().entity(identifier).build();
}
}
@@ -4,6 +4,7 @@ import org.mifosng.data.EntityIdentifier;
import org.mifosng.data.command.LoanStateTransitionCommand;
import org.mifosng.data.command.SubmitLoanApplicationCommand;
import org.mifosng.data.command.UndoLoanApprovalCommand;
import org.mifosng.data.command.UndoLoanDisbursalCommand;
import org.springframework.security.access.prepost.PreAuthorize;
public interface LoanWritePlatformService {
@@ -25,4 +26,10 @@ public interface LoanWritePlatformService {
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_WITHDRAW_LOAN_ROLE', 'CAN_WITHDRAW_LOAN_IN_THE_PAST_ROLE')")
EntityIdentifier withdrawLoan(LoanStateTransitionCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_DISBURSE_LOAN_ROLE', 'CAN_DISBURSE_LOAN_IN_THE_PAST_ROLE')")
EntityIdentifier disburseLoan(LoanStateTransitionCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_UNDO_LOAN_DISBURSAL_ROLE')")
public EntityIdentifier undloLoanDisbursal(UndoLoanDisbursalCommand command);
}
@@ -3,6 +3,7 @@ package org.mifosng.platform.loan.service;
import static org.mifosng.platform.Specifications.loansThatMatch;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
@@ -11,15 +12,18 @@ 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.CalculateLoanScheduleCommand;
import org.mifosng.data.command.LoanStateTransitionCommand;
import org.mifosng.data.command.SubmitLoanApplicationCommand;
import org.mifosng.data.command.UndoLoanApprovalCommand;
import org.mifosng.data.command.UndoLoanDisbursalCommand;
import org.mifosng.platform.LoanStateTransitionCommandValidator;
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.NoAuthorizationException;
import org.mifosng.platform.exceptions.PlatformDomainRuleException;
import org.mifosng.platform.exceptions.PlatformResourceNotFoundException;
@@ -54,19 +58,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanStatusRepository loanStatusRepository;
private final ClientRepository clientRepository;
private final NoteRepository noteRepository;
private final CalculationPlatformService calculationPlatformService;
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) {
final ClientRepository clientRepository, final NoteRepository noteRepository, final CalculationPlatformService calculationPlatformService) {
this.context = context;
this.loanRepository = loanRepository;
this.loanProductRepository = loanProductRepository;
this.clientRepository = clientRepository;
this.loanStatusRepository = loanStatusRepository;
this.noteRepository = noteRepository;
this.calculationPlatformService = calculationPlatformService;
}
private boolean isBeforeToday(final LocalDate date) {
@@ -279,4 +284,120 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
return new EntityIdentifier(loan.getClient().getId());
}
@Transactional
@Override
public EntityIdentifier disburseLoan(final LoanStateTransitionCommand command) {
AppUser currentUser = context.authenticatedUser();
LoanStateTransitionCommandValidator validator = new LoanStateTransitionCommandValidator(command);
validator.validate();
Loan loan = this.loanRepository.findOne(loansThatMatch(currentUser.getOrganisation(), command.getLoanId()));
if (loan == null) {
throw new PlatformResourceNotFoundException("error.msg.loan.id.invalid", "Loan with identifier {0} does not exist", command.getLoanId());
}
if (this.isBeforeToday(command.getEventDate()) && currentUser.canNotDisburseLoanInPast()) {
throw new NoAuthorizationException("User has no authority to disburse loan with a date in the past.");
}
LocalDate disbursedOn = command.getEventDate();
String comment = command.getComment();
LocalDate actualDisbursementDate = new LocalDate(disbursedOn);
if (loan.isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
LocalDate repaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate();
LocalDate interestCalculatedFromDate = loan.getInterestCalculatedFromDate();
Number principalAsDecimal = loan.getLoanRepaymentScheduleDetail().getPrincipal().getAmount();
String currencyCode = loan.getLoanRepaymentScheduleDetail().getPrincipal().getCurrencyCode();
int currencyDigits = loan.getLoanRepaymentScheduleDetail().getPrincipal().getCurrencyDigitsAfterDecimal();
Number interestRatePerYear = loan.getLoanRepaymentScheduleDetail().getAnnualNominalInterestRate();
Integer numberOfInstallments = loan.getLoanRepaymentScheduleDetail().getNumberOfRepayments();
Integer repaidEvery = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
Integer selectedRepaymentFrequency = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType().getValue();
Integer selectedRepaymentSchedule = loan.getLoanRepaymentScheduleDetail().getAmortizationMethod().getValue();
// use annual percentage rate to re-calculate loan schedule for late disbursement
Number interestRatePerPeriod = interestRatePerYear;
Integer interestRateFrequencyMethod = PeriodFrequencyType.YEARS.getValue();
Integer interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod().getValue();
Integer interestCalculationInPeriod = loan.getLoanRepaymentScheduleDetail().getInterestCalculationPeriodMethod().getValue();
CalculateLoanScheduleCommand calculateCommand = new CalculateLoanScheduleCommand(currencyCode, currencyDigits, principalAsDecimal,
interestRatePerPeriod, interestRateFrequencyMethod, interestMethod, interestCalculationInPeriod,
repaidEvery, selectedRepaymentFrequency, numberOfInstallments,
selectedRepaymentSchedule, actualDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate);
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateCommand);
List<LoanRepaymentScheduleInstallment> modifiedLoanRepaymentSchedule = new ArrayList<LoanRepaymentScheduleInstallment>();
for (ScheduledLoanInstallment scheduledLoanInstallment : loanSchedule
.getScheduledLoanInstallments()) {
final MonetaryCurrency monetaryCurrency = new MonetaryCurrency(
scheduledLoanInstallment.getPrincipalDue().getCurrencyCode(),
scheduledLoanInstallment.getPrincipalDue().getCurrencyDigitsAfterDecimal());
Money principal = Money.of(monetaryCurrency,
scheduledLoanInstallment.getPrincipalDue().getAmount());
Money interest = Money.of(monetaryCurrency,
scheduledLoanInstallment.getInterestDue().getAmount());
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
loan, scheduledLoanInstallment.getInstallmentNumber(),
scheduledLoanInstallment.getPeriodStart(),
scheduledLoanInstallment.getPeriodEnd(), principal.getAmount(),
interest.getAmount());
modifiedLoanRepaymentSchedule.add(installment);
}
loan.disburseWithModifiedRepaymentSchedule(disbursedOn, comment, modifiedLoanRepaymentSchedule, defaultLoanLifecycleStateMachine());
} else {
loan.disburse(disbursedOn, defaultLoanLifecycleStateMachine());
}
this.loanRepository.save(loan);
if (StringUtils.isNotBlank(command.getComment())) {
Note note = Note.loanNote(currentUser.getOrganisation(), loan, command.getComment());
this.noteRepository.save(note);
}
return new EntityIdentifier(loan.getClient().getId());
}
@Transactional
@Override
public EntityIdentifier undloLoanDisbursal(final UndoLoanDisbursalCommand command) {
AppUser currentUser = context.authenticatedUser();
Loan loan = this.loanRepository.findOne(loansThatMatch(currentUser.getOrganisation(), command.getLoanId()));
if (loan == null) {
throw new PlatformResourceNotFoundException("error.msg.loan.id.invalid", "Loan with identifier {0} does not exist", command.getLoanId());
}
if (loan.isActualDisbursedOnDateEarlierOrLaterThanExpected()) {
// FIXME - handle this use case - recalculate loan schedule using original settings.
}
loan.undoDisbursal(defaultLoanLifecycleStateMachine());
this.loanRepository.save(loan);
// TODO - this may not be wanted.
Note note = Note.loanNote(currentUser.getOrganisation(), loan, "Undo of disbursal.");
this.noteRepository.save(note);
return new EntityIdentifier(loan.getClient().getId());
}
}
@@ -1,268 +0,0 @@
package org.mifosng.platform.rest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.ws.rs.Consumes;
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;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.mifosng.data.EntityIdentifier;
import org.mifosng.data.ErrorResponse;
import org.mifosng.data.ErrorResponseList;
import org.mifosng.data.LoanRepaymentData;
import org.mifosng.data.command.AdjustLoanTransactionCommand;
import org.mifosng.data.command.LoanStateTransitionCommand;
import org.mifosng.data.command.LoanTransactionCommand;
import org.mifosng.data.command.UndoLoanDisbursalCommand;
import org.mifosng.platform.ReadPlatformService;
import org.mifosng.platform.ReadPlatformServiceImpl;
import org.mifosng.platform.WritePlatformService;
import org.mifosng.platform.exceptions.ApplicationDomainRuleException;
import org.mifosng.platform.exceptions.NewDataValidationException;
import org.mifosng.platform.exceptions.UnAuthenticatedUserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
@Path("/protected/loan")
@Component
@Scope("singleton")
public class LoanResource {
private final static Logger logger = LoggerFactory.getLogger(ReadPlatformServiceImpl.class);
@Autowired
private ReadPlatformService readPlatformService;
@Autowired
private WritePlatformService writePlatformService;
@POST
@Path("disburse")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response disburseLoanApplication(final LoanStateTransitionCommand command) {
try {
EntityIdentifier identifier = this.writePlatformService.disburseLoan(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());
}
}
@POST
@Path("undodisbursal")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response undoLoanDisbursal(final UndoLoanDisbursalCommand command) {
try {
EntityIdentifier identifier = this.writePlatformService.undloLoanDisbursal(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());
}
}
@GET
@Path("{loanId}/repayment")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response retrieveNewRepaymentDetails(@PathParam("loanId") final Long loanId) {
try {
LoanRepaymentData loanRepaymentData = this.readPlatformService.retrieveNewLoanRepaymentDetails(loanId);
return Response.ok().entity(loanRepaymentData).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());
}
}
@GET
@Path("{loanId}/repayment/{repaymentId}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response retrieveRepaymentDetails(@PathParam("loanId") final Long loanId, @PathParam("repaymentId") final Long repaymentId) {
try {
LoanRepaymentData loanRepaymentData = this.readPlatformService.retrieveLoanRepaymentDetails(loanId, repaymentId);
return Response.ok().entity(loanRepaymentData).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("repayment")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response makeLoanRepayment(final LoanTransactionCommand command) {
try {
EntityIdentifier identifier = this.writePlatformService.makeLoanRepayment(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());
} 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("repayment/adjust")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response adjustLoanTransaction(final AdjustLoanTransactionCommand command) {
try {
EntityIdentifier identifier = this.writePlatformService.adjustLoanTransaction(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());
} 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());
}
}
@GET
@Path("{loanId}/waive")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response retrieveNewWaiverDetails(@PathParam("loanId") final Long loanId) {
try {
LoanRepaymentData loanRepaymentData = this.readPlatformService.retrieveNewLoanWaiverDetails(loanId);
return Response.ok().entity(loanRepaymentData).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("waive")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response waiveLoanAmount(final LoanTransactionCommand command) {
try {
EntityIdentifier identifier = this.writePlatformService.waiveLoanAmount(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());
} 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());
}
}
}