harden up validation around savings application process.

This commit is contained in:
Keith Woodlock
2013-07-16 17:59:22 +01:00
parent 49ac64020a
commit 2c42d926cc
8 changed files with 220 additions and 124 deletions
@@ -89,6 +89,14 @@ public class DataValidatorBuilder {
dataValidationErrors.add(error); dataValidationErrors.add(error);
} }
public void failWithCodeNoParameterAddedToErrorCode(final String errorCode) {
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(errorCode);
StringBuilder defaultEnglishMessage = new StringBuilder("Failed data validation due to: ").append(errorCode).append(".");
ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
this.parameter, this.value);
dataValidationErrors.add(error);
}
public DataValidatorBuilder equalToParameter(final String linkedParameterName, final Object linkedValue) { public DataValidatorBuilder equalToParameter(final String linkedParameterName, final Object linkedValue) {
if (value == null && linkedValue == null && ignoreNullValue) { return this; } if (value == null && linkedValue == null && ignoreNullValue) { return this; }
@@ -4,19 +4,26 @@ import java.util.Date;
public class JobDetailData { public class JobDetailData {
private Long jobId; @SuppressWarnings("unused")
private final Long jobId;
private String displayName; @SuppressWarnings("unused")
private final String displayName;
private Date nextRunTime; @SuppressWarnings("unused")
private final Date nextRunTime;
private String initializingError; @SuppressWarnings("unused")
private final String initializingError;
private boolean active; @SuppressWarnings("unused")
private final boolean active;
private boolean currentlyRunning; @SuppressWarnings("unused")
private final boolean currentlyRunning;
private JobDetailHistoryData lastRunHistory; @SuppressWarnings("unused")
private final JobDetailHistoryData lastRunHistory;
public JobDetailData(final Long jobId, final String displayName, final Date nextRunTime, final String initializingError, public JobDetailData(final Long jobId, final String displayName, final Date nextRunTime, final String initializingError,
final boolean active, final boolean currentlyRunning, final JobDetailHistoryData lastRunHistory) { final boolean active, final boolean currentlyRunning, final JobDetailHistoryData lastRunHistory) {
@@ -28,5 +35,4 @@ public class JobDetailData {
this.lastRunHistory = lastRunHistory; this.lastRunHistory = lastRunHistory;
this.currentlyRunning = currentlyRunning; this.currentlyRunning = currentlyRunning;
} }
}
}
@@ -4,13 +4,20 @@ import java.util.Date;
public class JobDetailHistoryData { public class JobDetailHistoryData {
private Long version; @SuppressWarnings("unused")
private Date jobRunStartTime; private final Long version;
private Date jobRunEndTime; @SuppressWarnings("unused")
private String status; private final Date jobRunStartTime;
private String jobRunErrorMessage; @SuppressWarnings("unused")
private String triggerType; private final Date jobRunEndTime;
private String jobRunErrorLog; @SuppressWarnings("unused")
private final String status;
@SuppressWarnings("unused")
private final String jobRunErrorMessage;
@SuppressWarnings("unused")
private final String triggerType;
@SuppressWarnings("unused")
private final String jobRunErrorLog;
public JobDetailHistoryData(final Long version, final Date jobRunStartTime, final Date jobRunEndTime, final String status, public JobDetailHistoryData(final Long version, final Date jobRunStartTime, final Date jobRunEndTime, final String status,
final String jobRunErrorMessage, final String triggerType, final String jobRunErrorLog) { final String jobRunErrorMessage, final String triggerType, final String jobRunErrorLog) {
@@ -19,6 +19,15 @@ public class SavingsApiConstants {
public static final String SAVINGS_ACCOUNT_RESOURCE_NAME = "savingsaccount"; public static final String SAVINGS_ACCOUNT_RESOURCE_NAME = "savingsaccount";
public static final String SAVINGS_ACCOUNT_TRANSACTION_RESOURCE_NAME = "savingsaccount.transaction"; public static final String SAVINGS_ACCOUNT_TRANSACTION_RESOURCE_NAME = "savingsaccount.transaction";
// actions
public static String summitalAction = ".summital";
public static String approvalAction = ".approval";
public static String undoApprovalAction = ".undoApproval";
public static String rejectAction = ".reject";
public static String withdrawnByApplicantAction = ".withdrawnByApplicant";
public static String activateAction = ".activate";
public static String modifyApplicationAction = ".modify";
// general // general
public static final String localeParamName = "locale"; public static final String localeParamName = "locale";
public static final String dateFormatParamName = "dateFormat"; public static final String dateFormatParamName = "dateFormat";
@@ -33,9 +42,15 @@ public class SavingsApiConstants {
public static final String groupIdParamName = "groupId"; public static final String groupIdParamName = "groupId";
public static final String productIdParamName = "productId"; public static final String productIdParamName = "productId";
public static final String fieldOfficerIdParamName = "fieldOfficerId"; public static final String fieldOfficerIdParamName = "fieldOfficerId";
public static final String submittedOnDateParamName = "submittedOnDate"; public static final String submittedOnDateParamName = "submittedOnDate";
public static final String activeParamName = "active"; public static final String rejectedOnDateParamName = "rejectedOnDate";
public static final String withdrawnOnDateParamName = "withdrawnOnDate";
public static final String approvedOnDateParamName = "approvedOnDate";
public static final String activatedOnDateParamName = "activatedOnDate"; public static final String activatedOnDateParamName = "activatedOnDate";
public static final String closedOnDateParamName = "closedOnDate";
public static final String activeParamName = "active";
public static final String nameParamName = "name"; public static final String nameParamName = "name";
public static final String descriptionParamName = "description"; public static final String descriptionParamName = "description";
public static final String currencyCodeParamName = "currencyCode"; public static final String currencyCodeParamName = "currencyCode";
@@ -71,7 +71,6 @@ import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType;
import org.mifosplatform.portfolio.savings.domain.interest.PostingPeriod; import org.mifosplatform.portfolio.savings.domain.interest.PostingPeriod;
import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException; import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException;
import org.mifosplatform.portfolio.savings.exception.InvalidSavingsAccountStateTransitionException;
import org.mifosplatform.portfolio.savings.service.SavingsEnumerations; import org.mifosplatform.portfolio.savings.service.SavingsEnumerations;
import org.mifosplatform.useradministration.domain.AppUser; import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.data.jpa.domain.AbstractPersistable; import org.springframework.data.jpa.domain.AbstractPersistable;
@@ -825,9 +824,30 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return this.summary.getAccountBalance(this.currency).getAmount(); return this.summary.getAccountBalance(this.currency).getAmount();
} }
public void update(final JsonCommand command, final Map<String, Object> actualChanges) { public void modifyApplication(final JsonCommand command, final Map<String, Object> actualChanges) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.modifyApplicationAction);
SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
final String localeAsInput = command.locale(); final String localeAsInput = command.locale();
final String dateFormat = command.dateFormat();
if (command.isChangeInLocalDateParameterNamed(SavingsApiConstants.submittedOnDateParamName, getSubmittedOnLocalDate())) {
final LocalDate newValue = command.localDateValueOfParameterNamed(SavingsApiConstants.submittedOnDateParamName);
final String newValueAsString = command.stringValueOfParameterNamed(SavingsApiConstants.submittedOnDateParamName);
actualChanges.put(SavingsApiConstants.submittedOnDateParamName, newValueAsString);
actualChanges.put(SavingsApiConstants.localeParamName, localeAsInput);
actualChanges.put(SavingsApiConstants.dateFormatParamName, dateFormat);
this.submittedOnDate = newValue.toDate();
}
if (command.isChangeInStringParameterNamed(SavingsApiConstants.accountNoParamName, this.accountNumber)) { if (command.isChangeInStringParameterNamed(SavingsApiConstants.accountNoParamName, this.accountNumber)) {
final String newValue = command.stringValueOfParameterNamed(SavingsApiConstants.accountNoParamName); final String newValue = command.stringValueOfParameterNamed(SavingsApiConstants.accountNoParamName);
@@ -1137,20 +1157,25 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final LocalDate submittedOn = getSubmittedOnLocalDate(); final LocalDate submittedOn = getSubmittedOnLocalDate();
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.summitalAction);
if (submittedOn.isAfter(todayDateOfTenant)) { if (submittedOn.isAfter(todayDateOfTenant)) {
final String errorMessage = "Savings account application submitted on date cannot be in the future."; baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(submittedOn)
throw new InvalidSavingsAccountStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, submittedOn); .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date");
} }
if (client != null && client.isActivatedAfter(submittedOn)) { if (client != null && client.isActivatedAfter(submittedOn)) {
final String errorMessage = "Savings account application submitted on date cannot be earlier than client's activation date."; baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(client.getActivationLocalDate())
throw new InvalidSavingsAccountStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date");
submittedOn);
} else if (group != null && group.isActivatedAfter(submittedOn)) { } else if (group != null && group.isActivatedAfter(submittedOn)) {
final String errorMessage = "Savings account application submitted on date cannot be earlier than group's activation date.";
throw new InvalidSavingsAccountStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage, baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(group.getActivationLocalDate())
submittedOn); .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date");
} }
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
private LocalDate getSubmittedOnLocalDate() { private LocalDate getSubmittedOnLocalDate() {
@@ -1177,43 +1202,49 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.approvalAction);
SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) { if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) {
final String errorMessage = "Savings account application can only be approved when in 'Submitted and pending approval' state. "; baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName)
throw new InvalidSavingsAccountStateTransitionException("approval", "not.in.submittedandpendingapproval.state", errorMessage); .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
this.status = SavingsAccountStatusType.APPROVED.getValue(); this.status = SavingsAccountStatusType.APPROVED.getValue();
actualChanges.put("status", SavingsEnumerations.status(this.status)); actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
// only do below if status has changed in the 'approval' case // only do below if status has changed in the 'approval' case
LocalDate approvedOn = command.localDateValueOfParameterNamed("approvedOnDate"); LocalDate approvedOn = command.localDateValueOfParameterNamed(SavingsApiConstants.approvedOnDateParamName);
String approvedOnDateChange = command.stringValueOfParameterNamed("approvedOnDate"); String approvedOnDateChange = command.stringValueOfParameterNamed(SavingsApiConstants.approvedOnDateParamName);
if (approvedOn == null) {
approvedOn = command.localDateValueOfParameterNamed("eventDate");
approvedOnDateChange = command.stringValueOfParameterNamed("eventDate");
}
this.approvedOnDate = approvedOn.toDate(); this.approvedOnDate = approvedOn.toDate();
this.approvedBy = currentUser; this.approvedBy = currentUser;
actualChanges.put("locale", command.locale()); actualChanges.put(SavingsApiConstants.localeParamName, command.locale());
actualChanges.put("dateFormat", command.dateFormat()); actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat());
actualChanges.put("approvedOnDate", approvedOnDateChange); actualChanges.put(SavingsApiConstants.approvedOnDateParamName, approvedOnDateChange);
// FIXME - kw - format dates based on based locale when doing error final LocalDate submittalDate = getSubmittedOnLocalDate();
// messages related dates.
final LocalDate submittalDate = new LocalDate(this.submittedOnDate);
if (approvedOn.isBefore(submittalDate)) { if (approvedOn.isBefore(submittalDate)) {
final String errorMessage = "Savings account application cannot be approved on date before its submittal date: "
+ submittalDate.toString(); final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
throw new InvalidSavingsAccountStateTransitionException("approval", "cannot.be.before.submittal.date", errorMessage, final String submittalDateAsString = formatter.print(submittalDate);
getApprovedOnLocalDate(), submittalDate);
baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName).value(submittalDateAsString)
.failWithCodeNoParameterAddedToErrorCode("cannot.be.before.submittal.date");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
if (approvedOn.isAfter(tenantsTodayDate)) { if (approvedOn.isAfter(tenantsTodayDate)) {
final String errorMessage = "Savings account application cannot be approved on date in the future.";
throw new InvalidSavingsAccountStateTransitionException("approval", "cannot.be.a.future.date", errorMessage, baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName)
getApprovedOnLocalDate()); .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
// FIXME - kw - support field officer history for savings accounts // FIXME - kw - support field officer history for savings accounts
@@ -1230,14 +1261,21 @@ public class SavingsAccount extends AbstractPersistable<Long> {
public Map<String, Object> undoApplicationApproval() { public Map<String, Object> undoApplicationApproval() {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.undoApprovalAction);
SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
if (!SavingsAccountStatusType.APPROVED.hasStateOf(currentStatus)) { if (!SavingsAccountStatusType.APPROVED.hasStateOf(currentStatus)) {
final String errorMessage = "Savings account application can only undo approval when in 'Approved' state. ";
throw new InvalidSavingsAccountStateTransitionException("approval", "not.in.approved.state", errorMessage); baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName)
.failWithCodeNoParameterAddedToErrorCode("not.in.approved.state");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
this.status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getValue(); this.status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getValue();
actualChanges.put("status", SavingsEnumerations.status(this.status)); actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
this.approvedOnDate = null; this.approvedOnDate = null;
this.approvedBy = null; this.approvedBy = null;
@@ -1247,7 +1285,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.withdrawnBy = null; this.withdrawnBy = null;
this.closedOnDate = null; this.closedOnDate = null;
this.closedBy = null; this.closedBy = null;
actualChanges.put("approvedOnDate", ""); actualChanges.put(SavingsApiConstants.approvedOnDateParamName, "");
// FIXME - kw - support field officer history for savings accounts // FIXME - kw - support field officer history for savings accounts
// this.loanOfficerHistory.clear(); // this.loanOfficerHistory.clear();
@@ -1259,19 +1297,24 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.rejectAction);
SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) { if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) {
final String errorMessage = "Savings account application can only be rejected when in 'Submitted and pending approval' state. ";
throw new InvalidSavingsAccountStateTransitionException("reject", "not.in.submittedandpendingapproval.state", errorMessage); baseDataValidator.reset().parameter(SavingsApiConstants.rejectedOnDateParamName)
.failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
this.status = SavingsAccountStatusType.REJECTED.getValue(); this.status = SavingsAccountStatusType.REJECTED.getValue();
actualChanges.put("status", SavingsEnumerations.status(this.status)); actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
LocalDate rejectedOn = command.localDateValueOfParameterNamed("rejectedOnDate"); LocalDate rejectedOn = command.localDateValueOfParameterNamed(SavingsApiConstants.rejectedOnDateParamName);
final String rejectedOnAsString = command.stringValueOfParameterNamed(SavingsApiConstants.rejectedOnDateParamName);
final Locale locale = new Locale(command.locale());
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
this.rejectedOnDate = rejectedOn.toDate(); this.rejectedOnDate = rejectedOn.toDate();
this.rejectedBy = currentUser; this.rejectedBy = currentUser;
@@ -1280,23 +1323,29 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.closedOnDate = rejectedOn.toDate(); this.closedOnDate = rejectedOn.toDate();
this.closedBy = currentUser; this.closedBy = currentUser;
actualChanges.put("locale", command.locale()); actualChanges.put(SavingsApiConstants.localeParamName, command.locale());
actualChanges.put("dateFormat", command.dateFormat()); actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat());
actualChanges.put("rejectedOnDate", rejectedOn.toString(fmt)); actualChanges.put(SavingsApiConstants.rejectedOnDateParamName, rejectedOnAsString);
actualChanges.put("closedOnDate", rejectedOn.toString(fmt)); actualChanges.put(SavingsApiConstants.closedOnDateParamName, rejectedOnAsString);
final LocalDate submittalDate = getSubmittedOnLocalDate(); final LocalDate submittalDate = getSubmittedOnLocalDate();
if (rejectedOn.isBefore(submittalDate)) { if (rejectedOn.isBefore(submittalDate)) {
final String errorMessage = "Savings account application cannot be rejected on date before its submittal date: "
+ submittalDate.toString(); final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
throw new InvalidSavingsAccountStateTransitionException("reject", "cannot.be.before.submittal.date", errorMessage, final String submittalDateAsString = formatter.print(submittalDate);
getApprovedOnLocalDate(), submittalDate);
baseDataValidator.reset().parameter(SavingsApiConstants.rejectedOnDateParamName).value(submittalDateAsString)
.failWithCodeNoParameterAddedToErrorCode("cannot.be.before.submittal.date");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
if (rejectedOn.isAfter(tenantsTodayDate)) { if (rejectedOn.isAfter(tenantsTodayDate)) {
final String errorMessage = "Savings account application cannot be rejected on date in the future.";
throw new InvalidSavingsAccountStateTransitionException("reject", "cannot.be.a.future.date", errorMessage, baseDataValidator.reset().parameter(SavingsApiConstants.rejectedOnDateParamName).value(rejectedOn)
getApprovedOnLocalDate()); .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
return actualChanges; return actualChanges;
@@ -1306,20 +1355,24 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final LocalDate tenantsTodayDate) { final LocalDate tenantsTodayDate) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.withdrawnByApplicantAction);
SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) { if (!SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.hasStateOf(currentStatus)) {
final String errorMessage = "Savings account application can only be withdrawn by applicant when in 'Submitted and pending approval' state. ";
throw new InvalidSavingsAccountStateTransitionException("withdrawnByApplicant", "not.in.submittedandpendingapproval.state", baseDataValidator.reset().parameter(SavingsApiConstants.withdrawnOnDateParamName)
errorMessage); .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
this.status = SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getValue(); this.status = SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getValue();
actualChanges.put("status", SavingsEnumerations.status(this.status)); actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status));
LocalDate withdrawnOn = command.localDateValueOfParameterNamed("withdrawnOnDate"); final LocalDate withdrawnOn = command.localDateValueOfParameterNamed(SavingsApiConstants.withdrawnOnDateParamName);
final String withdrawnOnAsString = command.stringValueOfParameterNamed(SavingsApiConstants.withdrawnOnDateParamName);
final Locale locale = new Locale(command.locale());
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
this.rejectedOnDate = null; this.rejectedOnDate = null;
this.rejectedBy = null; this.rejectedBy = null;
@@ -1328,23 +1381,29 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.closedOnDate = withdrawnOn.toDate(); this.closedOnDate = withdrawnOn.toDate();
this.closedBy = currentUser; this.closedBy = currentUser;
actualChanges.put("locale", command.locale()); actualChanges.put(SavingsApiConstants.localeParamName, command.locale());
actualChanges.put("dateFormat", command.dateFormat()); actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat());
actualChanges.put("withdrawnOnDate", withdrawnOn.toString(fmt)); actualChanges.put(SavingsApiConstants.withdrawnOnDateParamName, withdrawnOnAsString);
actualChanges.put("closedOnDate", withdrawnOn.toString(fmt)); actualChanges.put(SavingsApiConstants.closedOnDateParamName, withdrawnOnAsString);
final LocalDate submittalDate = getSubmittedOnLocalDate(); final LocalDate submittalDate = getSubmittedOnLocalDate();
if (withdrawnOn.isBefore(submittalDate)) { if (withdrawnOn.isBefore(submittalDate)) {
final String errorMessage = "Savings account application cannot be withdrawn by applicant on date before its submittal date: "
+ submittalDate.toString(); final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
throw new InvalidSavingsAccountStateTransitionException("withdrawnByApplicant", "cannot.be.before.submittal.date", final String submittalDateAsString = formatter.print(submittalDate);
errorMessage, getApprovedOnLocalDate(), submittalDate);
baseDataValidator.reset().parameter(SavingsApiConstants.withdrawnOnDateParamName).value(submittalDateAsString)
.failWithCodeNoParameterAddedToErrorCode("cannot.be.before.submittal.date");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
if (withdrawnOn.isAfter(tenantsTodayDate)) { if (withdrawnOn.isAfter(tenantsTodayDate)) {
final String errorMessage = "Savings account application cannot be withdrawn by applicant on date in the future.";
throw new InvalidSavingsAccountStateTransitionException("withdrawnByApplicant", "cannot.be.a.future.date", errorMessage, baseDataValidator.reset().parameter(SavingsApiConstants.withdrawnOnDateParamName).value(withdrawnOn)
getApprovedOnLocalDate()); .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
return actualChanges; return actualChanges;
@@ -1355,10 +1414,17 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.activateAction);
SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
if (!SavingsAccountStatusType.APPROVED.hasStateOf(currentStatus)) { if (!SavingsAccountStatusType.APPROVED.hasStateOf(currentStatus)) {
final String errorMessage = "Savings account application can only be activated when in 'approved' state. ";
throw new InvalidSavingsAccountStateTransitionException("activation", "not.in.approved.state", errorMessage); baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName)
.failWithCodeNoParameterAddedToErrorCode("not.in.approved.state");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
@@ -1382,24 +1448,34 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate()); this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate());
if (this.client.isActivatedAfter(activationDate)) { if (this.client.isActivatedAfter(activationDate)) {
final String errorMessage = "The date on which a savings account is activate cannot be before its client activated date: "
+ this.client.getActivationLocalDate().toString(); final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
throw new InvalidSavingsAccountStateTransitionException("activate", "cannot.be.before.client.activation.date", errorMessage, final String dateAsString = formatter.print(client.getActivationLocalDate());
activationDate, this.client.getActivationLocalDate());
baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(dateAsString)
.failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
final LocalDate submittalDate = getSubmittedOnLocalDate(); final LocalDate approvalDate = getApprovedOnLocalDate();
if (activationDate.isBefore(submittalDate)) { if (activationDate.isBefore(approvalDate)) {
final String errorMessage = "Savings account application cannot be withdrawn by applicant on date before its submittal date: "
+ submittalDate.toString(); final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
throw new InvalidSavingsAccountStateTransitionException("activate", "cannot.be.before.submittal.date", final String dateAsString = formatter.print(approvalDate);
errorMessage, getApprovedOnLocalDate(), submittalDate);
baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(dateAsString)
.failWithCodeNoParameterAddedToErrorCode("cannot.be.before.approval.date");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
if (activationDate.isAfter(tenantsTodayDate)) { if (activationDate.isAfter(tenantsTodayDate)) {
final String errorMessage = "Savings account application cannot be withdrawn by applicant on date in the future.";
throw new InvalidSavingsAccountStateTransitionException("activate", "cannot.be.a.future.date", errorMessage, baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(activationDate)
getApprovedOnLocalDate()); .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
// auto enter deposit for minimum required opening balance when // auto enter deposit for minimum required opening balance when
@@ -85,6 +85,6 @@ public enum SavingsAccountStatusType {
} }
public boolean isClosed() { public boolean isClosed() {
return this.value.equals(SavingsAccountStatusType.CLOSED.getValue()); return this.value.equals(SavingsAccountStatusType.CLOSED.getValue()) || isRejected() || isApplicationWithdrawnByApplicant();
} }
} }
@@ -1,17 +0,0 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.savings.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class InvalidSavingsAccountStateTransitionException extends AbstractPlatformDomainRuleException {
public InvalidSavingsAccountStateTransitionException(final String action, final String postFix, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super("error.msg.saving." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -162,7 +162,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId);
account.update(command, changes); account.modifyApplication(command, changes);
account.validateNewApplicationState(DateUtils.getLocalDateOfTenant());
if (!changes.isEmpty()) { if (!changes.isEmpty()) {