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.Response;
import javax.ws.rs.core.UriInfo; 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.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.DepositAccountData;
import org.mifosng.platform.api.data.DepositProductLookup; import org.mifosng.platform.api.data.DepositProductLookup;
import org.mifosng.platform.api.data.EntityIdentifier; 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.ApiDataConversionService;
import org.mifosng.platform.api.infrastructure.ApiJsonSerializerService; import org.mifosng.platform.api.infrastructure.ApiJsonSerializerService;
import org.mifosng.platform.api.infrastructure.ApiParameterHelper; import org.mifosng.platform.api.infrastructure.ApiParameterHelper;
import org.mifosng.platform.exceptions.UnrecognizedQueryParamException;
import org.mifosng.platform.loan.domain.PeriodFrequencyType; import org.mifosng.platform.loan.domain.PeriodFrequencyType;
import org.mifosng.platform.saving.service.DepositAccountReadPlatformService; import org.mifosng.platform.saving.service.DepositAccountReadPlatformService;
import org.mifosng.platform.saving.service.DepositAccountWritePlatformService; import org.mifosng.platform.saving.service.DepositAccountWritePlatformService;
@@ -191,4 +195,52 @@ public class DepositAccountsApiResource {
return new DepositAccountData(account, interestCompoundedEveryPeriodTypeOptions, productOptions); 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, @QueryParam("command") final String commandParam,
final String jsonRequestBody) { final String jsonRequestBody) {
LoanStateTransitionCommand command = this.apiDataConversionService LoanStateTransitionCommand command = this.apiDataConversionService.convertJsonToLoanStateTransitionCommand(loanId, jsonRequestBody);
.convertJsonToLoanStateTransitionCommand(loanId,
jsonRequestBody);
Response response = null; 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;
}
}
@@ -13,6 +13,11 @@ public class UndoStateTransitionCommand {
this.note = note; this.note = note;
} }
public UndoStateTransitionCommand(final Long loanId) {
this.loanId = loanId;
this.note=null;
}
public Long getLoanId() { public Long getLoanId() {
return this.loanId; 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.ClientCommand;
import org.mifosng.platform.api.commands.DepositAccountCommand; import org.mifosng.platform.api.commands.DepositAccountCommand;
import org.mifosng.platform.api.commands.DepositProductCommand; 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.FundCommand;
import org.mifosng.platform.api.commands.GroupCommand; import org.mifosng.platform.api.commands.GroupCommand;
import org.mifosng.platform.api.commands.LoanProductCommand; import org.mifosng.platform.api.commands.LoanProductCommand;
@@ -57,4 +58,6 @@ public interface ApiDataConversionService {
NoteCommand convertJsonToNoteCommand(Long resourceIdentifier, Long clientId, String jsonRequestBody); NoteCommand convertJsonToNoteCommand(Long resourceIdentifier, Long clientId, String jsonRequestBody);
DepositAccountCommand convertJsonToDepositAccountCommand(Long resourceIdentifier, 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.ClientCommand;
import org.mifosng.platform.api.commands.DepositAccountCommand; import org.mifosng.platform.api.commands.DepositAccountCommand;
import org.mifosng.platform.api.commands.DepositProductCommand; 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.FundCommand;
import org.mifosng.platform.api.commands.GroupCommand; import org.mifosng.platform.api.commands.GroupCommand;
import org.mifosng.platform.api.commands.LoanProductCommand; import org.mifosng.platform.api.commands.LoanProductCommand;
@@ -1004,4 +1005,26 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
interestCompoundedEvery, interestCompoundedEveryPeriodType, interestCompoundedEvery, interestCompoundedEveryPeriodType,
canRenew, canPreClose, preClosureInterestRate); 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 @Embedded
private final LoanProductRelatedDetail loanRepaymentScheduleDetail; private final LoanProductRelatedDetail loanRepaymentScheduleDetail;
@SuppressWarnings("unused")
@Column(name = "term_frequency", nullable = false) @Column(name = "term_frequency", nullable = false)
private Integer termFrequency; private Integer termFrequency;
@SuppressWarnings("unused")
@Enumerated(EnumType.ORDINAL) @Enumerated(EnumType.ORDINAL)
@Column(name = "term_period_frequency_enum", nullable = false) @Column(name = "term_period_frequency_enum", nullable = false)
private PeriodFrequencyType termPeriodFrequencyType; private PeriodFrequencyType termPeriodFrequencyType;
@@ -83,7 +81,6 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "rejectedon_date") @Column(name = "rejectedon_date")
private Date rejectedOnDate; private Date rejectedOnDate;
@SuppressWarnings("unused")
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
@Column(name = "withdrawnon_date") @Column(name = "withdrawnon_date")
private Date withdrawnOnDate; 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)); LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue(); 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.client.domain.Client;
import org.mifosng.platform.currency.domain.MonetaryCurrency; import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money; 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.loan.domain.PeriodFrequencyType;
import org.mifosng.platform.savingproduct.domain.DepositProduct; import org.mifosng.platform.savingproduct.domain.DepositProduct;
import org.springframework.data.jpa.domain.AbstractPersistable; import org.mifosng.platform.user.domain.AppUser;
@Entity @Entity
@Table(name = "m_deposit_account", uniqueConstraints = @UniqueConstraint(name="deposit_acc_external_id", columnNames = { "external_id" })) @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 @ManyToOne
@JoinColumn(name = "client_id", nullable = false) @JoinColumn(name = "client_id", nullable = false)
private Client client; private Client client;
@SuppressWarnings("unused")
@ManyToOne @ManyToOne
@JoinColumn(name = "product_id") @JoinColumn(name = "product_id")
private final DepositProduct product; private final DepositProduct product;
@@ -41,81 +41,81 @@ public class DepositAccount extends AbstractPersistable<Long> {
@Column(name = "external_id") @Column(name = "external_id")
private String externalId; private String externalId;
@SuppressWarnings("unused")
@Embedded @Embedded
private MonetaryCurrency currency; private MonetaryCurrency currency;
@Column(name = "deposit_amount", scale = 6, precision = 19, nullable = false) @Column(name = "deposit_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal depositAmount; private BigDecimal depositAmount;
@SuppressWarnings("unused")
@Column(name = "maturity_nominal_interest_rate", scale = 6, precision = 19, nullable = false) @Column(name = "maturity_nominal_interest_rate", scale = 6, precision = 19, nullable = false)
private BigDecimal interestRate; private BigDecimal interestRate;
@Column(name = "tenure_months", nullable=false) @Column(name = "tenure_months", nullable=false)
private Integer tenureInMonths; private Integer tenureInMonths;
@SuppressWarnings("unused")
@Column(name = "interest_compounded_every", nullable = false) @Column(name = "interest_compounded_every", nullable = false)
private Integer interestCompoundedEvery; private Integer interestCompoundedEvery;
@SuppressWarnings("unused")
@Enumerated(EnumType.ORDINAL) @Enumerated(EnumType.ORDINAL)
@Column(name = "interest_compounded_every_period_enum", nullable = false) @Column(name = "interest_compounded_every_period_enum", nullable = false)
private PeriodFrequencyType interestCompoundedFrequencyType; private PeriodFrequencyType interestCompoundedFrequencyType;
@SuppressWarnings("unused")
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
@Column(name = "projected_commencement_date") @Column(name = "projected_commencement_date")
private Date projectedCommencementDate; private Date projectedCommencementDate;
@SuppressWarnings("unused")
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
@Column(name = "actual_commencement_date") @Column(name = "actual_commencement_date")
private Date actualCommencementDate; private Date actualCommencementDate;
@SuppressWarnings("unused")
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
@Column(name = "projected_maturity_date") @Column(name = "projected_maturity_date")
private Date projectedMaturityDate; private Date projectedMaturityDate;
@SuppressWarnings("unused")
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
@Column(name = "actual_maturity_date") @Column(name = "actual_maturity_date")
private Date actualMaturityDate; private Date actualMaturityDate;
@SuppressWarnings("unused")
@Column(name = "projected_interest_accrued_on_maturity", scale = 6, precision = 19, nullable = false) @Column(name = "projected_interest_accrued_on_maturity", scale = 6, precision = 19, nullable = false)
private BigDecimal projectedInterestAccruedOnMaturity; private BigDecimal projectedInterestAccruedOnMaturity;
@SuppressWarnings("unused")
@Column(name = "actual_interest_accrued", scale = 6, precision = 19, nullable = false) @Column(name = "actual_interest_accrued", scale = 6, precision = 19, nullable = false)
private BigDecimal interestAccrued; private BigDecimal interestAccrued;
@SuppressWarnings("unused")
@Column(name = "projected_total_maturity_amount", scale = 6, precision = 19, nullable = false) @Column(name = "projected_total_maturity_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal projectedTotalOnMaturity; private BigDecimal projectedTotalOnMaturity;
@SuppressWarnings("unused")
@Column(name = "actual_total_amount", scale = 6, precision = 19, nullable = false) @Column(name = "actual_total_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal total; private BigDecimal total;
@SuppressWarnings("unused")
@Column(name = "pre_closure_interest_rate", scale = 6, precision = 19, nullable = false) @Column(name = "pre_closure_interest_rate", scale = 6, precision = 19, nullable = false)
private BigDecimal preClosureInterestRate; private BigDecimal preClosureInterestRate;
@SuppressWarnings("unused")
@Column(name = "is_renewal_allowed", nullable = false) @Column(name = "is_renewal_allowed", nullable = false)
private boolean renewalAllowed = false; private boolean renewalAllowed = false;
@SuppressWarnings("unused")
@Column(name = "is_preclosure_allowed", nullable = false) @Column(name = "is_preclosure_allowed", nullable = false)
private boolean preClosureAllowed = false; private boolean preClosureAllowed = false;
@Column(name = "is_deleted", nullable=false) @Column(name = "is_deleted", nullable=false)
private boolean deleted = 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 Client client, final DepositProduct product,
final String externalId, final Money deposit, final BigDecimal maturityInterestRate, final Integer tenureInMonths, final String externalId, final Money deposit, final BigDecimal maturityInterestRate, final Integer tenureInMonths,
final Integer interestCompoundedEvery, final Integer interestCompoundedEvery,
@@ -123,20 +123,30 @@ public class DepositAccount extends AbstractPersistable<Long> {
final LocalDate commencementDate, final LocalDate commencementDate,
final boolean renewalAllowed, final boolean renewalAllowed,
final boolean preClosureAllowed, final boolean preClosureAllowed,
final FixedTermDepositInterestCalculator fixedTermDepositInterestCalculator) { final FixedTermDepositInterestCalculator fixedTermDepositInterestCalculator,
final DepositLifecycleStateMachine depositLifecycleStateMachine) {
Money futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, tenureInMonths, Money futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, tenureInMonths,
maturityInterestRate, interestCompoundedEvery, interestCompoundedFrequencyPeriodType); 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, 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; this.product = null;
} }
private DepositAccount( public DepositAccount(
final Client client, final DepositProduct product, final Client client, final DepositProduct product,
final String externalId, final Money deposit, final BigDecimal interestRate, final Integer termInMonths, final String externalId, final Money deposit, final BigDecimal interestRate, final Integer termInMonths,
final Integer interestCompoundedEvery, final Integer interestCompoundedEvery,
@@ -144,7 +154,8 @@ public class DepositAccount extends AbstractPersistable<Long> {
final LocalDate commencementDate, final LocalDate commencementDate,
final boolean renewalAllowed, final boolean renewalAllowed,
final boolean preClosureAllowed, final boolean preClosureAllowed,
final Money futureValueOnMaturity) { final Money futureValueOnMaturity,
final Integer depositStatus) {
this.client = client; this.client = client;
this.product = product; this.product = product;
this.externalId = externalId; this.externalId = externalId;
@@ -170,6 +181,7 @@ public class DepositAccount extends AbstractPersistable<Long> {
// derived fields // derived fields
this.projectedInterestAccruedOnMaturity = futureValueOnMaturity.minus(deposit).getAmount(); this.projectedInterestAccruedOnMaturity = futureValueOnMaturity.minus(deposit).getAmount();
this.projectedTotalOnMaturity = futureValueOnMaturity.getAmount(); this.projectedTotalOnMaturity = futureValueOnMaturity.getAmount();
this.depositStatus=depositStatus;
} }
public boolean isDeleted() { public boolean isDeleted() {
@@ -189,4 +201,103 @@ public class DepositAccount extends AbstractPersistable<Long> {
public void update(final DepositAccountCommand command) { 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; package org.mifosng.platform.saving.service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import org.mifosng.platform.api.commands.DepositAccountCommand; import org.mifosng.platform.api.commands.DepositAccountCommand;
import org.mifosng.platform.client.domain.Client; 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.exceptions.DepositProductNotFoundException;
import org.mifosng.platform.loan.domain.PeriodFrequencyType; import org.mifosng.platform.loan.domain.PeriodFrequencyType;
import org.mifosng.platform.saving.domain.DepositAccount; 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.saving.domain.FixedTermDepositInterestCalculator;
import org.mifosng.platform.savingproduct.domain.DepositProduct; import org.mifosng.platform.savingproduct.domain.DepositProduct;
import org.mifosng.platform.savingproduct.domain.DepositProductRepository; import org.mifosng.platform.savingproduct.domain.DepositProductRepository;
@@ -80,13 +85,18 @@ public class DepositAccountAssembler {
} }
// end of details allowed to be overriden from product // 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, deposit,
maturityInterestRate, maturityInterestRate,
tenureInMonths, compoundingInterestEvery, compoundingInterestFrequency, tenureInMonths, compoundingInterestEvery, compoundingInterestFrequency,
command.getCommencementDate(), command.getCommencementDate(),
renewalAllowed, preClosureAllowed, this.fixedTermDepositInterestCalculator); renewalAllowed, preClosureAllowed, this.fixedTermDepositInterestCalculator, defaultDepositLifecycleStateMachine());
return account; 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; package org.mifosng.platform.saving.service;
import org.mifosng.platform.api.commands.DepositAccountCommand; 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.api.data.EntityIdentifier;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@@ -14,4 +16,17 @@ public interface DepositAccountWritePlatformService {
@PreAuthorize(value = "hasRole('ORGANISATION_ADMINISTRATION_SUPER_USER_ROLE')") @PreAuthorize(value = "hasRole('ORGANISATION_ADMINISTRATION_SUPER_USER_ROLE')")
EntityIdentifier deleteDepositAccount(Long productId); 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; 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.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.api.data.EntityIdentifier;
import org.mifosng.platform.exceptions.DepositAccountNotFoundException; import org.mifosng.platform.exceptions.DepositAccountNotFoundException;
import org.mifosng.platform.exceptions.NoAuthorizationException;
import org.mifosng.platform.exceptions.PlatformDataIntegrityException; import org.mifosng.platform.exceptions.PlatformDataIntegrityException;
import org.mifosng.platform.exceptions.ProductNotFoundException; import org.mifosng.platform.exceptions.ProductNotFoundException;
import org.mifosng.platform.saving.domain.DepositAccount; import org.mifosng.platform.saving.domain.DepositAccount;
import org.mifosng.platform.saving.domain.DepositAccountRepository; 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.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -112,4 +123,104 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
return new EntityIdentifier(accountId); 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);
}
}
}