Mifosx-43 DepositAccount Approval

This commit is contained in:
Madhukar
2012-08-23 17:03:24 +05:30
parent 47a215ec4f
commit bbe525d8a8
17 changed files with 609 additions and 34 deletions
@@ -20,7 +20,10 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.mifosng.platform.api.commands.DepositAccountCommand;
import org.mifosng.platform.api.commands.DepositStateTransitionCommand;
import org.mifosng.platform.api.commands.UndoStateTransitionCommand;
import org.mifosng.platform.api.data.DepositAccountData;
import org.mifosng.platform.api.data.DepositProductLookup;
import org.mifosng.platform.api.data.EntityIdentifier;
@@ -28,6 +31,7 @@ import org.mifosng.platform.api.data.EnumOptionData;
import org.mifosng.platform.api.infrastructure.ApiDataConversionService;
import org.mifosng.platform.api.infrastructure.ApiJsonSerializerService;
import org.mifosng.platform.api.infrastructure.ApiParameterHelper;
import org.mifosng.platform.exceptions.UnrecognizedQueryParamException;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
import org.mifosng.platform.saving.service.DepositAccountReadPlatformService;
import org.mifosng.platform.saving.service.DepositAccountWritePlatformService;
@@ -191,4 +195,52 @@ public class DepositAccountsApiResource {
return new DepositAccountData(account, interestCompoundedEveryPeriodTypeOptions, productOptions);
}
@POST
@Path("{accountId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response SubmitDepositApplication(@PathParam("accountId") final Long accountId,
@QueryParam("command") final String commandParam, final String jsonRequestBody) {
DepositStateTransitionCommand command=apiDataConversionService.convertJsonToDepositStateTransitionCommand(accountId, jsonRequestBody);
Response response=null;
if (is(commandParam, "approve")) {
EntityIdentifier identifier = this.depositAccountWritePlatformService.approveDepositApplication(command);
response = Response.ok().entity(identifier).build();
}
else if (is(commandParam, "reject")) {
EntityIdentifier identifier = this.depositAccountWritePlatformService.rejectDepositApplication(command);
response = Response.ok().entity(identifier).build();
} else if (is(commandParam, "withdrewbyclient")) {
EntityIdentifier identifier = this.depositAccountWritePlatformService.withdrawDepositApplication(command);
response = Response.ok().entity(identifier).build();
}
UndoStateTransitionCommand undoCommand = new UndoStateTransitionCommand(accountId);
if (is(commandParam, "undoapproval")) {
EntityIdentifier identifier = this.depositAccountWritePlatformService.undoDepositApproval(undoCommand);
response = Response.ok().entity(identifier).build();
}
if (response == null) {
throw new UnrecognizedQueryParamException("command", commandParam);
}
return response;
}
private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam)
&& commandParam.trim().equalsIgnoreCase(commandValue);
}
}
@@ -187,9 +187,7 @@ public class LoansApiResource {
@QueryParam("command") final String commandParam,
final String jsonRequestBody) {
LoanStateTransitionCommand command = this.apiDataConversionService
.convertJsonToLoanStateTransitionCommand(loanId,
jsonRequestBody);
LoanStateTransitionCommand command = this.apiDataConversionService.convertJsonToLoanStateTransitionCommand(loanId, jsonRequestBody);
Response response = null;
@@ -0,0 +1,23 @@
package org.mifosng.platform.api.commands;
import org.joda.time.LocalDate;
public class DepositStateTransitionCommand {
private final Long accountId;
private final LocalDate eventDate;
public DepositStateTransitionCommand(final Long accountId, final LocalDate enentDate) {
this.accountId=accountId;
this.eventDate=enentDate;
}
public Long getAccountId() {
return accountId;
}
public LocalDate getEventDate() {
return eventDate;
}
}
@@ -12,6 +12,11 @@ public class UndoStateTransitionCommand {
this.loanId = loanId;
this.note = note;
}
public UndoStateTransitionCommand(final Long loanId) {
this.loanId = loanId;
this.note=null;
}
public Long getLoanId() {
return this.loanId;
@@ -6,6 +6,7 @@ import org.mifosng.platform.api.commands.ChargeCommand;
import org.mifosng.platform.api.commands.ClientCommand;
import org.mifosng.platform.api.commands.DepositAccountCommand;
import org.mifosng.platform.api.commands.DepositProductCommand;
import org.mifosng.platform.api.commands.DepositStateTransitionCommand;
import org.mifosng.platform.api.commands.FundCommand;
import org.mifosng.platform.api.commands.GroupCommand;
import org.mifosng.platform.api.commands.LoanProductCommand;
@@ -57,4 +58,6 @@ public interface ApiDataConversionService {
NoteCommand convertJsonToNoteCommand(Long resourceIdentifier, Long clientId, String jsonRequestBody);
DepositAccountCommand convertJsonToDepositAccountCommand(Long resourceIdentifier, String jsonRequestBody);
DepositStateTransitionCommand convertJsonToDepositStateTransitionCommand(Long resourceIdentifier, String jsonRequestBody);
}
@@ -23,6 +23,7 @@ import org.mifosng.platform.api.commands.ChargeCommand;
import org.mifosng.platform.api.commands.ClientCommand;
import org.mifosng.platform.api.commands.DepositAccountCommand;
import org.mifosng.platform.api.commands.DepositProductCommand;
import org.mifosng.platform.api.commands.DepositStateTransitionCommand;
import org.mifosng.platform.api.commands.FundCommand;
import org.mifosng.platform.api.commands.GroupCommand;
import org.mifosng.platform.api.commands.LoanProductCommand;
@@ -1004,4 +1005,26 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
interestCompoundedEvery, interestCompoundedEveryPeriodType,
canRenew, canPreClose, preClosureInterestRate);
}
@Override
public DepositStateTransitionCommand convertJsonToDepositStateTransitionCommand(
Long resourceIdentifier, String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
Type typeOfMap = new TypeToken<Map<String, Object>>(){}.getType();
Map<String, Object> requestMap = gsonConverter.fromJson(json, typeOfMap);
Set<String> supportedParams = new HashSet<String>( Arrays.asList("eventDate", "dateFormat"));
checkForUnsupportedParameters(requestMap, supportedParams);
Set<String> modifiedParameters = new HashSet<String>();
LocalDate eventDate = extractLocalDateParameter("eventDate", requestMap, modifiedParameters);
return new DepositStateTransitionCommand(resourceIdentifier, eventDate);
}
}
@@ -0,0 +1,11 @@
package org.mifosng.platform.exceptions;
public class InvalidDepositStateTransitionException extends
AbstractPlatformDomainRuleException {
public InvalidDepositStateTransitionException(final String action, String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
super("error.msg.deposit." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
// TODO Auto-generated constructor stub
}
}
@@ -63,11 +63,9 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@Embedded
private final LoanProductRelatedDetail loanRepaymentScheduleDetail;
@SuppressWarnings("unused")
@Column(name = "term_frequency", nullable = false)
private Integer termFrequency;
@SuppressWarnings("unused")
@Enumerated(EnumType.ORDINAL)
@Column(name = "term_period_frequency_enum", nullable = false)
private PeriodFrequencyType termPeriodFrequencyType;
@@ -83,7 +81,6 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "rejectedon_date")
private Date rejectedOnDate;
@SuppressWarnings("unused")
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "withdrawnon_date")
private Date withdrawnOnDate;
@@ -523,7 +520,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
}
}
private void handleLoanOverpayment(@SuppressWarnings("unused") final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
private void handleLoanOverpayment(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue();
@@ -20,20 +20,20 @@ import org.mifosng.platform.api.commands.DepositAccountCommand;
import org.mifosng.platform.client.domain.Client;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.exceptions.InvalidDepositStateTransitionException;
import org.mifosng.platform.infrastructure.AbstractAuditableCustom;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
import org.mifosng.platform.savingproduct.domain.DepositProduct;
import org.springframework.data.jpa.domain.AbstractPersistable;
import org.mifosng.platform.user.domain.AppUser;
@Entity
@Table(name = "m_deposit_account", uniqueConstraints = @UniqueConstraint(name="deposit_acc_external_id", columnNames = { "external_id" }))
public class DepositAccount extends AbstractPersistable<Long> {
public class DepositAccount extends AbstractAuditableCustom<AppUser, Long> {
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "client_id", nullable = false)
private Client client;
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "product_id")
private final DepositProduct product;
@@ -41,81 +41,81 @@ public class DepositAccount extends AbstractPersistable<Long> {
@Column(name = "external_id")
private String externalId;
@SuppressWarnings("unused")
@Embedded
private MonetaryCurrency currency;
@Column(name = "deposit_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal depositAmount;
@SuppressWarnings("unused")
@Column(name = "maturity_nominal_interest_rate", scale = 6, precision = 19, nullable = false)
private BigDecimal interestRate;
@Column(name = "tenure_months", nullable=false)
private Integer tenureInMonths;
@SuppressWarnings("unused")
@Column(name = "interest_compounded_every", nullable = false)
private Integer interestCompoundedEvery;
@SuppressWarnings("unused")
@Enumerated(EnumType.ORDINAL)
@Column(name = "interest_compounded_every_period_enum", nullable = false)
private PeriodFrequencyType interestCompoundedFrequencyType;
@SuppressWarnings("unused")
@Temporal(TemporalType.DATE)
@Column(name = "projected_commencement_date")
private Date projectedCommencementDate;
@SuppressWarnings("unused")
@Temporal(TemporalType.DATE)
@Column(name = "actual_commencement_date")
private Date actualCommencementDate;
@SuppressWarnings("unused")
@Temporal(TemporalType.DATE)
@Column(name = "projected_maturity_date")
private Date projectedMaturityDate;
@SuppressWarnings("unused")
@Temporal(TemporalType.DATE)
@Column(name = "actual_maturity_date")
private Date actualMaturityDate;
@SuppressWarnings("unused")
@Column(name = "projected_interest_accrued_on_maturity", scale = 6, precision = 19, nullable = false)
private BigDecimal projectedInterestAccruedOnMaturity;
@SuppressWarnings("unused")
@Column(name = "actual_interest_accrued", scale = 6, precision = 19, nullable = false)
private BigDecimal interestAccrued;
@SuppressWarnings("unused")
@Column(name = "projected_total_maturity_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal projectedTotalOnMaturity;
@SuppressWarnings("unused")
@Column(name = "actual_total_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal total;
@SuppressWarnings("unused")
@Column(name = "pre_closure_interest_rate", scale = 6, precision = 19, nullable = false)
private BigDecimal preClosureInterestRate;
@SuppressWarnings("unused")
@Column(name = "is_renewal_allowed", nullable = false)
private boolean renewalAllowed = false;
@SuppressWarnings("unused")
@Column(name = "is_preclosure_allowed", nullable = false)
private boolean preClosureAllowed = false;
@Column(name = "is_deleted", nullable=false)
private boolean deleted = false;
public static DepositAccount openNew(
@Column(name = "status_enum", nullable = false)
private Integer depositStatus;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "closedon_date")
private Date closedOnDate;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "rejectedon_date")
private Date rejectedOnDate;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "withdrawnon_date")
private Date withdrawnOnDate;
public DepositAccount openNew(
final Client client, final DepositProduct product,
final String externalId, final Money deposit, final BigDecimal maturityInterestRate, final Integer tenureInMonths,
final Integer interestCompoundedEvery,
@@ -123,20 +123,30 @@ public class DepositAccount extends AbstractPersistable<Long> {
final LocalDate commencementDate,
final boolean renewalAllowed,
final boolean preClosureAllowed,
final FixedTermDepositInterestCalculator fixedTermDepositInterestCalculator) {
final FixedTermDepositInterestCalculator fixedTermDepositInterestCalculator,
final DepositLifecycleStateMachine depositLifecycleStateMachine) {
Money futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, tenureInMonths,
maturityInterestRate, interestCompoundedEvery, interestCompoundedFrequencyPeriodType);
DepositStatus from = null;
if (depositStatus != null) {
from = DepositStatus.fromInt(depositStatus);
}
DepositStatus statusEnum = depositLifecycleStateMachine.transition(DepositEvent.DEPOSIT_CREATED, from);
depositStatus = statusEnum.getValue();
return new DepositAccount(client, product, externalId, deposit, maturityInterestRate, tenureInMonths,
interestCompoundedEvery, interestCompoundedFrequencyPeriodType, commencementDate, renewalAllowed, preClosureAllowed, futureValueOnMaturity);
interestCompoundedEvery, interestCompoundedFrequencyPeriodType, commencementDate, renewalAllowed, preClosureAllowed, futureValueOnMaturity,depositStatus);
}
protected DepositAccount() {
public DepositAccount() {
this.product = null;
}
private DepositAccount(
public DepositAccount(
final Client client, final DepositProduct product,
final String externalId, final Money deposit, final BigDecimal interestRate, final Integer termInMonths,
final Integer interestCompoundedEvery,
@@ -144,7 +154,8 @@ public class DepositAccount extends AbstractPersistable<Long> {
final LocalDate commencementDate,
final boolean renewalAllowed,
final boolean preClosureAllowed,
final Money futureValueOnMaturity) {
final Money futureValueOnMaturity,
final Integer depositStatus) {
this.client = client;
this.product = product;
this.externalId = externalId;
@@ -170,6 +181,7 @@ public class DepositAccount extends AbstractPersistable<Long> {
// derived fields
this.projectedInterestAccruedOnMaturity = futureValueOnMaturity.minus(deposit).getAmount();
this.projectedTotalOnMaturity = futureValueOnMaturity.getAmount();
this.depositStatus=depositStatus;
}
public boolean isDeleted() {
@@ -189,4 +201,103 @@ public class DepositAccount extends AbstractPersistable<Long> {
public void update(final DepositAccountCommand command) {
}
public void approve(final LocalDate actualCommencementDate, DepositLifecycleStateMachine depositLifecycleStateMachine) {
DepositStatus statusEnum = depositLifecycleStateMachine.transition(DepositEvent.DEPOSIT_APPROVED, DepositStatus.fromInt(this.depositStatus));
this.depositStatus = statusEnum.getValue();
this.actualCommencementDate = actualCommencementDate
.toDateTimeAtCurrentTime().toDate();
if(this.actualCommencementDate != null)
this.actualMaturityDate = new LocalDate(this.actualCommencementDate).plusMonths(this.tenureInMonths).toDate();
LocalDate submittalDate = new LocalDate(this.projectedCommencementDate);
if (actualCommencementDate.isBefore(submittalDate)) {
final String errorMessage = "The date on which a deposit is approved cannot be before its submittal date: "
+ submittalDate.toString();
throw new InvalidDepositStateTransitionException("approval",
"cannot.be.before.submittal.date", errorMessage,
getActualCommencementDate(), submittalDate);
}
if (actualCommencementDate.isAfter(new LocalDate())) {
final String errorMessage = "The date on which a deposit is approved cannot be in the future.";
throw new InvalidDepositStateTransitionException("approval",
"cannot.be.a.future.date", errorMessage,
getActualCommencementDate());
}
}
public LocalDate getActualCommencementDate() {
LocalDate date=null;
if(this.actualCommencementDate!=null){
date = new LocalDate(this.actualCommencementDate);
}
return date;
}
public LocalDate getProjectedCommencementDate() {
LocalDate date=null;
if(this.projectedCommencementDate!=null){
date = new LocalDate(this.projectedCommencementDate);
}
return date;
}
public void reject(LocalDate rejectedOn, DepositLifecycleStateMachine depositLifecycleStateMachine) {
DepositStatus statusEnum = depositLifecycleStateMachine.transition(DepositEvent.DEPOSIT_REJECTED, DepositStatus.fromInt(this.depositStatus));
this.depositStatus = statusEnum.getValue();
this.rejectedOnDate = rejectedOn.toDateTimeAtCurrentTime().toDate();
this.closedOnDate = rejectedOn.toDateTimeAtCurrentTime().toDate();
if (rejectedOn.isBefore(getProjectedCommencementDate())) {
final String errorMessage = "The date on which a deposit is rejected cannot be before its submittal date: " + getProjectedCommencementDate().toString();
throw new InvalidDepositStateTransitionException("reject", "cannot.be.before.submittal.date", errorMessage, rejectedOn, getProjectedCommencementDate());
}
if (rejectedOn.isAfter(new LocalDate())) {
final String errorMessage = "The date on which a deposit is rejected cannot be in the future.";
throw new InvalidDepositStateTransitionException("reject", "cannot.be.a.future.date", errorMessage, rejectedOn);
}
}
public void withdraw(LocalDate withdrawnOn, DepositLifecycleStateMachine depositLifecycleStateMachine) {
DepositStatus statusEnum = depositLifecycleStateMachine.transition(DepositEvent.DEPOSIT_WITHDRAWN, DepositStatus.fromInt(this.depositStatus));
this.depositStatus = statusEnum.getValue();
this.withdrawnOnDate = withdrawnOn.toDateTimeAtCurrentTime().toDate();
this.closedOnDate = withdrawnOn.toDateTimeAtCurrentTime().toDate();
if (withdrawnOn.isBefore(getProjectedCommencementDate())) {
final String errorMessage = "The date on which a deposit is rejected cannot be before its submittal date: " + getProjectedCommencementDate().toString();
throw new InvalidDepositStateTransitionException("reject", "cannot.be.before.submittal.date", errorMessage, withdrawnOn, getProjectedCommencementDate());
}
if (withdrawnOn.isAfter(new LocalDate())) {
final String errorMessage = "The date on which a deposit is rejected cannot be in the future.";
throw new InvalidDepositStateTransitionException("reject", "cannot.be.a.future.date", errorMessage, withdrawnOn);
}
}
public void undoDepositApproval(DepositLifecycleStateMachine depositLifecycleStateMachine) {
DepositStatus statusEnum = depositLifecycleStateMachine.transition(DepositEvent.DEPOSIT_APPROVAL_UNDO, DepositStatus.fromInt(this.depositStatus));
this.depositStatus = statusEnum.getValue();
this.actualCommencementDate = null;
this.actualMaturityDate=null;
}
}
@@ -0,0 +1,7 @@
package org.mifosng.platform.saving.domain;
public enum DepositEvent {
DEPOSIT_CREATED, DEPOSIT_REJECTED, DEPOSIT_APPROVED, DEPOSIT_APPROVAL_UNDO, DEPOSIT_WITHDRAWN, DEPOSIT_MATURED;
}
@@ -0,0 +1,8 @@
package org.mifosng.platform.saving.domain;
public interface DepositLifecycleStateMachine {
DepositStatus transition(DepositEvent loanEvent, DepositStatus from);
}
@@ -0,0 +1,81 @@
package org.mifosng.platform.saving.domain;
import java.util.List;
public class DepositLifecycleStateMachineImpl implements
DepositLifecycleStateMachine {
private final List<DepositStatus> allowedDepositStatuses;
public DepositLifecycleStateMachineImpl(List<DepositStatus> allowedDepositStatuses) {
this.allowedDepositStatuses=allowedDepositStatuses;
}
@Override
public DepositStatus transition(DepositEvent depositEvent, DepositStatus from) {
DepositStatus newState = null;
switch (depositEvent) {
case DEPOSIT_CREATED:
if (from == null) {
newState = stateOf(DepositStatus.SUBMITED_AND_PENDING_APPROVAL, allowedDepositStatuses);
}
break;
case DEPOSIT_REJECTED:
if (from.hasStateOf(DepositStatus.SUBMITED_AND_PENDING_APPROVAL)) {
newState = stateOf(DepositStatus.REJECTED, allowedDepositStatuses);
}
break;
case DEPOSIT_APPROVED:
if (from.hasStateOf(DepositStatus.SUBMITED_AND_PENDING_APPROVAL)) {
newState = stateOf(DepositStatus.APPROVED, allowedDepositStatuses);
}
break;
case DEPOSIT_WITHDRAWN:
if (this.anyOfAllowedWhenComingFrom(from, DepositStatus.SUBMITED_AND_PENDING_APPROVAL, DepositStatus.APPROVED)) {
newState = stateOf(DepositStatus.WITHDRAWN_BY_CLIENT, allowedDepositStatuses);
}
break;
case DEPOSIT_APPROVAL_UNDO:
if (from.hasStateOf(DepositStatus.APPROVED)) {
newState = stateOf(DepositStatus.SUBMITED_AND_PENDING_APPROVAL, allowedDepositStatuses);
}
break;
case DEPOSIT_MATURED:
if (from.hasStateOf(DepositStatus.APPROVED) ){
newState = stateOf(DepositStatus.MATURED, allowedDepositStatuses);
}
break;
default:
break;
}
return newState;
}
private DepositStatus stateOf(DepositStatus state, List<DepositStatus> allowedLoanStatuses) {
DepositStatus match = null;
for (DepositStatus depositStatus : allowedLoanStatuses) {
if (depositStatus.hasStateOf(state)) {
match = depositStatus;
break;
}
}
return match;
}
private boolean anyOfAllowedWhenComingFrom(final DepositStatus state, final DepositStatus... allowedStates) {
boolean allowed = false;
for (DepositStatus allowedState : allowedStates) {
if (state.hasStateOf(allowedState)) {
allowed = true;
break;
}
}
return allowed;
}
}
@@ -0,0 +1,89 @@
package org.mifosng.platform.saving.domain;
public enum DepositStatus {
INVALID(0, "depositStatusType.invalid"), //
SUBMITED_AND_PENDING_APPROVAL(100, "depositStatusType.submitted.and.pending.approval"), //
APPROVED(200, "depositStatusType.approved"), //
ACTIVE(300, "depositStatusType.active"), //
WITHDRAWN_BY_CLIENT(400, "depositStatusType.withdrawn.by.client"), //
REJECTED(500, "depositStatusType.rejected"), //
MATURED(700,"depositStatusType.matured"),
CLOSED(600, "depositStatusType.closed");
private final Integer value;
private final String code;
private DepositStatus(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public boolean hasStateOf(final DepositStatus state) {
return this.value.equals(state.getValue());
}
public Integer getValue() {
return value;
}
public String getCode() {
return code;
}
public boolean isSubmittedAndPendingApproval() {
return this.value.equals(DepositStatus.SUBMITED_AND_PENDING_APPROVAL.getValue());
}
public boolean isApproved() {
return this.value.equals(DepositStatus.APPROVED.getValue());
}
public boolean isActive() {
return this.value.equals(DepositStatus.ACTIVE.getValue());
}
public boolean isClosed() {
return this.value.equals(DepositStatus.CLOSED.getValue());
}
public boolean isWithdrawnByClient() {
return this.value.equals(DepositStatus.WITHDRAWN_BY_CLIENT.getValue());
}
public boolean isRejected() {
return this.value.equals(DepositStatus.REJECTED.getValue());
}
public static DepositStatus fromInt(final Integer statusValue) {
DepositStatus enumeration = DepositStatus.INVALID;
switch (statusValue) {
case 100:
enumeration = DepositStatus.SUBMITED_AND_PENDING_APPROVAL;
break;
case 200:
enumeration = DepositStatus.APPROVED;
break;
case 300:
enumeration = DepositStatus.ACTIVE;
break;
case 400:
enumeration = DepositStatus.WITHDRAWN_BY_CLIENT;
break;
case 500:
enumeration = DepositStatus.REJECTED;
break;
case 600:
enumeration = DepositStatus.CLOSED;
break;
case 700:
enumeration = DepositStatus.MATURED;
break;
}
return enumeration;
}
}
@@ -1,6 +1,8 @@
package org.mifosng.platform.saving.service;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import org.mifosng.platform.api.commands.DepositAccountCommand;
import org.mifosng.platform.client.domain.Client;
@@ -10,6 +12,9 @@ import org.mifosng.platform.exceptions.ClientNotFoundException;
import org.mifosng.platform.exceptions.DepositProductNotFoundException;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
import org.mifosng.platform.saving.domain.DepositAccount;
import org.mifosng.platform.saving.domain.DepositLifecycleStateMachine;
import org.mifosng.platform.saving.domain.DepositLifecycleStateMachineImpl;
import org.mifosng.platform.saving.domain.DepositStatus;
import org.mifosng.platform.saving.domain.FixedTermDepositInterestCalculator;
import org.mifosng.platform.savingproduct.domain.DepositProduct;
import org.mifosng.platform.savingproduct.domain.DepositProductRepository;
@@ -80,13 +85,18 @@ public class DepositAccountAssembler {
}
// end of details allowed to be overriden from product
DepositAccount account = DepositAccount.openNew(client, product, command.getExternalId(),
DepositAccount account =new DepositAccount().openNew(client, product, command.getExternalId(),
deposit,
maturityInterestRate,
tenureInMonths, compoundingInterestEvery, compoundingInterestFrequency,
command.getCommencementDate(),
renewalAllowed, preClosureAllowed, this.fixedTermDepositInterestCalculator);
renewalAllowed, preClosureAllowed, this.fixedTermDepositInterestCalculator, defaultDepositLifecycleStateMachine());
return account;
}
private DepositLifecycleStateMachine defaultDepositLifecycleStateMachine() {
List<DepositStatus> allowedDepositStatuses = Arrays.asList(DepositStatus.values());
return new DepositLifecycleStateMachineImpl(allowedDepositStatuses);
}
}
@@ -1,6 +1,8 @@
package org.mifosng.platform.saving.service;
import org.mifosng.platform.api.commands.DepositAccountCommand;
import org.mifosng.platform.api.commands.DepositStateTransitionCommand;
import org.mifosng.platform.api.commands.UndoStateTransitionCommand;
import org.mifosng.platform.api.data.EntityIdentifier;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -14,4 +16,17 @@ public interface DepositAccountWritePlatformService {
@PreAuthorize(value = "hasRole('ORGANISATION_ADMINISTRATION_SUPER_USER_ROLE')")
EntityIdentifier deleteDepositAccount(Long productId);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_APPROVE_DEPOSIT_ROLE', 'CAN_APPROVE_DEPOSIT_IN_THE_PAST_ROLE')")
EntityIdentifier approveDepositApplication(DepositStateTransitionCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_APPROVE_DEPOSIT_ROLE', 'CAN_APPROVE_DEPOSIT_IN_THE_PAST_ROLE')")
EntityIdentifier rejectDepositApplication(DepositStateTransitionCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_APPROVE_DEPOSIT_ROLE', 'CAN_APPROVE_DEPOSIT_IN_THE_PAST_ROLE')")
EntityIdentifier withdrawDepositApplication(DepositStateTransitionCommand command);
@PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE', 'CAN_APPROVE_DEPOSIT_ROLE', 'CAN_APPROVE_DEPOSIT_IN_THE_PAST_ROLE')")
EntityIdentifier undoDepositApproval(UndoStateTransitionCommand undoCommand);
}
@@ -1,13 +1,24 @@
package org.mifosng.platform.saving.service;
import java.util.Arrays;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.commands.DepositAccountCommand;
import org.mifosng.platform.api.commands.DepositStateTransitionCommand;
import org.mifosng.platform.api.commands.UndoStateTransitionCommand;
import org.mifosng.platform.api.data.EntityIdentifier;
import org.mifosng.platform.exceptions.DepositAccountNotFoundException;
import org.mifosng.platform.exceptions.NoAuthorizationException;
import org.mifosng.platform.exceptions.PlatformDataIntegrityException;
import org.mifosng.platform.exceptions.ProductNotFoundException;
import org.mifosng.platform.saving.domain.DepositAccount;
import org.mifosng.platform.saving.domain.DepositAccountRepository;
import org.mifosng.platform.saving.domain.DepositLifecycleStateMachine;
import org.mifosng.platform.saving.domain.DepositLifecycleStateMachineImpl;
import org.mifosng.platform.saving.domain.DepositStatus;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -112,4 +123,104 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
return new EntityIdentifier(accountId);
}
@Transactional
@Override
public EntityIdentifier approveDepositApplication(DepositStateTransitionCommand command) {
AppUser currentUser = context.authenticatedUser();
DepositStateTransitionCommandValidator validator = new DepositStateTransitionCommandValidator(command);
validator.validate();
DepositAccount account = this.depositAccountRepository.findOne(command.getAccountId());
if (account == null || account.isDeleted()) {
throw new DepositAccountNotFoundException(command.getAccountId());
}
LocalDate eventDate = command.getEventDate();
if (this.isBeforeToday(eventDate) && currentUser.canNotApproveLoanInPast()) {
throw new NoAuthorizationException("User has no authority to approve deposit with a date in the past.");
}
account.approve(eventDate, defaultDepositLifecycleStateMachine());
this.depositAccountRepository.save(account);
return new EntityIdentifier(account.getId());
}
private boolean isBeforeToday(final LocalDate date) {
return date.isBefore(new LocalDate());
}
private DepositLifecycleStateMachine defaultDepositLifecycleStateMachine() {
List<DepositStatus> allowedDepositStatuses = Arrays.asList(DepositStatus.values());
return new DepositLifecycleStateMachineImpl(allowedDepositStatuses);
}
@Transactional
@Override
public EntityIdentifier rejectDepositApplication(DepositStateTransitionCommand command) {
AppUser currentUser = context.authenticatedUser();
DepositStateTransitionCommandValidator validator = new DepositStateTransitionCommandValidator(command);
validator.validate();
DepositAccount account = this.depositAccountRepository.findOne(command.getAccountId());
if (account == null || account.isDeleted()) {
throw new DepositAccountNotFoundException(command.getAccountId());
}
LocalDate eventDate = command.getEventDate();
if (this.isBeforeToday(eventDate) && currentUser.canNotApproveLoanInPast()) {
throw new NoAuthorizationException("User has no authority to approve deposit with a date in the past.");
}
account.reject(eventDate, defaultDepositLifecycleStateMachine());
this.depositAccountRepository.save(account);
return new EntityIdentifier(account.getId());
}
@Transactional
@Override
public EntityIdentifier withdrawDepositApplication(DepositStateTransitionCommand command) {
AppUser currentUser = context.authenticatedUser();
DepositStateTransitionCommandValidator validator = new DepositStateTransitionCommandValidator(command);
validator.validate();
DepositAccount account = this.depositAccountRepository.findOne(command.getAccountId());
if (account == null || account.isDeleted()) {
throw new DepositAccountNotFoundException(command.getAccountId());
}
LocalDate eventDate = command.getEventDate();
if (this.isBeforeToday(eventDate) && currentUser.canNotApproveLoanInPast()) {
throw new NoAuthorizationException("User has no authority to approve deposit with a date in the past.");
}
account.withdraw(eventDate, defaultDepositLifecycleStateMachine());
return null;
}
@Transactional
@Override
public EntityIdentifier undoDepositApproval(UndoStateTransitionCommand command) {
context.authenticatedUser();
DepositAccount account = this.depositAccountRepository.findOne(command.getLoanId());
if (account == null || account.isDeleted()) {
throw new DepositAccountNotFoundException(command.getLoanId());
}
account.undoDepositApproval(defaultDepositLifecycleStateMachine());
this.depositAccountRepository.save(account);
return null;
}
}
@@ -0,0 +1,31 @@
package org.mifosng.platform.saving.service;
import java.util.ArrayList;
import java.util.List;
import org.mifosng.platform.DataValidatorBuilder;
import org.mifosng.platform.api.commands.DepositStateTransitionCommand;
import org.mifosng.platform.api.data.ApiParameterError;
import org.mifosng.platform.exceptions.PlatformApiDataValidationException;
public class DepositStateTransitionCommandValidator {
private final DepositStateTransitionCommand command;
public DepositStateTransitionCommandValidator(final DepositStateTransitionCommand command) {
this.command=command;
}
public void validate(){
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("deposit.transition");
baseDataValidator.reset().parameter("accountId").value(command.getAccountId()).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter("eventDate").value(command.getEventDate()).notNull();
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
}
}
}