From 2c42d926cc5d1915b6ea4ff6f77f1921227b1321 Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Tue, 16 Jul 2013 17:59:22 +0100 Subject: [PATCH] harden up validation around savings application process. --- .../core/data/DataValidatorBuilder.java | 8 + .../jobs/data/JobDetailData.java | 24 +- .../jobs/data/JobDetailHistoryData.java | 21 +- .../savings/SavingsApiConstants.java | 17 +- .../savings/domain/SavingsAccount.java | 252 ++++++++++++------ .../domain/SavingsAccountStatusType.java | 2 +- ...avingsAccountStateTransitionException.java | 17 -- ...WritePlatformServiceJpaRepositoryImpl.java | 3 +- 8 files changed, 220 insertions(+), 124 deletions(-) delete mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/InvalidSavingsAccountStateTransitionException.java diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java index 54ef17e91..78bf5a91b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java @@ -89,6 +89,14 @@ public class DataValidatorBuilder { 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) { if (value == null && linkedValue == null && ignoreNullValue) { return this; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/data/JobDetailData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/data/JobDetailData.java index 06d3e3170..0c0c0df03 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/data/JobDetailData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/data/JobDetailData.java @@ -4,19 +4,26 @@ import java.util.Date; 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, final boolean active, final boolean currentlyRunning, final JobDetailHistoryData lastRunHistory) { @@ -28,5 +35,4 @@ public class JobDetailData { this.lastRunHistory = lastRunHistory; this.currentlyRunning = currentlyRunning; } - -} +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/data/JobDetailHistoryData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/data/JobDetailHistoryData.java index dd443df7e..9aeb69d3e 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/data/JobDetailHistoryData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/data/JobDetailHistoryData.java @@ -4,13 +4,20 @@ import java.util.Date; public class JobDetailHistoryData { - private Long version; - private Date jobRunStartTime; - private Date jobRunEndTime; - private String status; - private String jobRunErrorMessage; - private String triggerType; - private String jobRunErrorLog; + @SuppressWarnings("unused") + private final Long version; + @SuppressWarnings("unused") + private final Date jobRunStartTime; + @SuppressWarnings("unused") + private final Date jobRunEndTime; + @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, final String jobRunErrorMessage, final String triggerType, final String jobRunErrorLog) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java index 1d24bdf7e..cd0746793 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/SavingsApiConstants.java @@ -19,6 +19,15 @@ public class SavingsApiConstants { public static final String SAVINGS_ACCOUNT_RESOURCE_NAME = "savingsaccount"; 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 public static final String localeParamName = "locale"; public static final String dateFormatParamName = "dateFormat"; @@ -33,9 +42,15 @@ public class SavingsApiConstants { public static final String groupIdParamName = "groupId"; public static final String productIdParamName = "productId"; public static final String fieldOfficerIdParamName = "fieldOfficerId"; + 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 closedOnDateParamName = "closedOnDate"; + + public static final String activeParamName = "active"; public static final String nameParamName = "name"; public static final String descriptionParamName = "description"; public static final String currencyCodeParamName = "currencyCode"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index 514b8167e..9c7d1ff26 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -71,7 +71,6 @@ import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType; import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType; import org.mifosplatform.portfolio.savings.domain.interest.PostingPeriod; import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceException; -import org.mifosplatform.portfolio.savings.exception.InvalidSavingsAccountStateTransitionException; import org.mifosplatform.portfolio.savings.service.SavingsEnumerations; import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.data.jpa.domain.AbstractPersistable; @@ -825,9 +824,30 @@ public class SavingsAccount extends AbstractPersistable { return this.summary.getAccountBalance(this.currency).getAmount(); } - public void update(final JsonCommand command, final Map actualChanges) { + public void modifyApplication(final JsonCommand command, final Map actualChanges) { + + final List dataValidationErrors = new ArrayList(); + 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 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)) { final String newValue = command.stringValueOfParameterNamed(SavingsApiConstants.accountNoParamName); @@ -1137,20 +1157,25 @@ public class SavingsAccount extends AbstractPersistable { final LocalDate submittedOn = getSubmittedOnLocalDate(); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.summitalAction); + if (submittedOn.isAfter(todayDateOfTenant)) { - final String errorMessage = "Savings account application submitted on date cannot be in the future."; - throw new InvalidSavingsAccountStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, submittedOn); + baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(submittedOn) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date"); } if (client != null && client.isActivatedAfter(submittedOn)) { - final String errorMessage = "Savings account application submitted on date cannot be earlier than client's activation date."; - throw new InvalidSavingsAccountStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, - submittedOn); + baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(client.getActivationLocalDate()) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date"); } 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, - submittedOn); + + baseDataValidator.reset().parameter(SavingsApiConstants.submittedOnDateParamName).value(group.getActivationLocalDate()) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.client.activation.date"); } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } private LocalDate getSubmittedOnLocalDate() { @@ -1177,43 +1202,49 @@ public class SavingsAccount extends AbstractPersistable { final Map actualChanges = new LinkedHashMap(); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.approvalAction); + SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); 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. "; - throw new InvalidSavingsAccountStateTransitionException("approval", "not.in.submittedandpendingapproval.state", errorMessage); + baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName) + .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state"); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } 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 - LocalDate approvedOn = command.localDateValueOfParameterNamed("approvedOnDate"); - String approvedOnDateChange = command.stringValueOfParameterNamed("approvedOnDate"); - if (approvedOn == null) { - approvedOn = command.localDateValueOfParameterNamed("eventDate"); - approvedOnDateChange = command.stringValueOfParameterNamed("eventDate"); - } + LocalDate approvedOn = command.localDateValueOfParameterNamed(SavingsApiConstants.approvedOnDateParamName); + String approvedOnDateChange = command.stringValueOfParameterNamed(SavingsApiConstants.approvedOnDateParamName); this.approvedOnDate = approvedOn.toDate(); this.approvedBy = currentUser; - actualChanges.put("locale", command.locale()); - actualChanges.put("dateFormat", command.dateFormat()); - actualChanges.put("approvedOnDate", approvedOnDateChange); + actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); + actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); + actualChanges.put(SavingsApiConstants.approvedOnDateParamName, approvedOnDateChange); - // FIXME - kw - format dates based on based locale when doing error - // messages related dates. - final LocalDate submittalDate = new LocalDate(this.submittedOnDate); + final LocalDate submittalDate = getSubmittedOnLocalDate(); if (approvedOn.isBefore(submittalDate)) { - final String errorMessage = "Savings account application cannot be approved on date before its submittal date: " - + submittalDate.toString(); - throw new InvalidSavingsAccountStateTransitionException("approval", "cannot.be.before.submittal.date", errorMessage, - getApprovedOnLocalDate(), submittalDate); + + final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale()); + final String submittalDateAsString = formatter.print(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)) { - final String errorMessage = "Savings account application cannot be approved on date in the future."; - throw new InvalidSavingsAccountStateTransitionException("approval", "cannot.be.a.future.date", errorMessage, - getApprovedOnLocalDate()); + + baseDataValidator.reset().parameter(SavingsApiConstants.approvedOnDateParamName) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date"); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } // FIXME - kw - support field officer history for savings accounts @@ -1230,14 +1261,21 @@ public class SavingsAccount extends AbstractPersistable { public Map undoApplicationApproval() { final Map actualChanges = new LinkedHashMap(); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.undoApprovalAction); + SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); 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(); - actualChanges.put("status", SavingsEnumerations.status(this.status)); + actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); this.approvedOnDate = null; this.approvedBy = null; @@ -1247,7 +1285,7 @@ public class SavingsAccount extends AbstractPersistable { this.withdrawnBy = null; this.closedOnDate = null; this.closedBy = null; - actualChanges.put("approvedOnDate", ""); + actualChanges.put(SavingsApiConstants.approvedOnDateParamName, ""); // FIXME - kw - support field officer history for savings accounts // this.loanOfficerHistory.clear(); @@ -1259,19 +1297,24 @@ public class SavingsAccount extends AbstractPersistable { final Map actualChanges = new LinkedHashMap(); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.rejectAction); + SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); 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(); - actualChanges.put("status", SavingsEnumerations.status(this.status)); + actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); - LocalDate rejectedOn = command.localDateValueOfParameterNamed("rejectedOnDate"); - - final Locale locale = new Locale(command.locale()); - final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + LocalDate rejectedOn = command.localDateValueOfParameterNamed(SavingsApiConstants.rejectedOnDateParamName); + final String rejectedOnAsString = command.stringValueOfParameterNamed(SavingsApiConstants.rejectedOnDateParamName); this.rejectedOnDate = rejectedOn.toDate(); this.rejectedBy = currentUser; @@ -1280,23 +1323,29 @@ public class SavingsAccount extends AbstractPersistable { this.closedOnDate = rejectedOn.toDate(); this.closedBy = currentUser; - actualChanges.put("locale", command.locale()); - actualChanges.put("dateFormat", command.dateFormat()); - actualChanges.put("rejectedOnDate", rejectedOn.toString(fmt)); - actualChanges.put("closedOnDate", rejectedOn.toString(fmt)); + actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); + actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); + actualChanges.put(SavingsApiConstants.rejectedOnDateParamName, rejectedOnAsString); + actualChanges.put(SavingsApiConstants.closedOnDateParamName, rejectedOnAsString); final LocalDate submittalDate = getSubmittedOnLocalDate(); if (rejectedOn.isBefore(submittalDate)) { - final String errorMessage = "Savings account application cannot be rejected on date before its submittal date: " - + submittalDate.toString(); - throw new InvalidSavingsAccountStateTransitionException("reject", "cannot.be.before.submittal.date", errorMessage, - getApprovedOnLocalDate(), submittalDate); + + final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale()); + final String submittalDateAsString = formatter.print(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)) { - final String errorMessage = "Savings account application cannot be rejected on date in the future."; - throw new InvalidSavingsAccountStateTransitionException("reject", "cannot.be.a.future.date", errorMessage, - getApprovedOnLocalDate()); + + baseDataValidator.reset().parameter(SavingsApiConstants.rejectedOnDateParamName).value(rejectedOn) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date"); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } return actualChanges; @@ -1306,20 +1355,24 @@ public class SavingsAccount extends AbstractPersistable { final LocalDate tenantsTodayDate) { final Map actualChanges = new LinkedHashMap(); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.withdrawnByApplicantAction); + SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); 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", - errorMessage); + + baseDataValidator.reset().parameter(SavingsApiConstants.withdrawnOnDateParamName) + .failWithCodeNoParameterAddedToErrorCode("not.in.submittedandpendingapproval.state"); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } 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 Locale locale = new Locale(command.locale()); - final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final LocalDate withdrawnOn = command.localDateValueOfParameterNamed(SavingsApiConstants.withdrawnOnDateParamName); + final String withdrawnOnAsString = command.stringValueOfParameterNamed(SavingsApiConstants.withdrawnOnDateParamName); this.rejectedOnDate = null; this.rejectedBy = null; @@ -1328,23 +1381,29 @@ public class SavingsAccount extends AbstractPersistable { this.closedOnDate = withdrawnOn.toDate(); this.closedBy = currentUser; - actualChanges.put("locale", command.locale()); - actualChanges.put("dateFormat", command.dateFormat()); - actualChanges.put("withdrawnOnDate", withdrawnOn.toString(fmt)); - actualChanges.put("closedOnDate", withdrawnOn.toString(fmt)); + actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); + actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); + actualChanges.put(SavingsApiConstants.withdrawnOnDateParamName, withdrawnOnAsString); + actualChanges.put(SavingsApiConstants.closedOnDateParamName, withdrawnOnAsString); final LocalDate submittalDate = getSubmittedOnLocalDate(); if (withdrawnOn.isBefore(submittalDate)) { - final String errorMessage = "Savings account application cannot be withdrawn by applicant on date before its submittal date: " - + submittalDate.toString(); - throw new InvalidSavingsAccountStateTransitionException("withdrawnByApplicant", "cannot.be.before.submittal.date", - errorMessage, getApprovedOnLocalDate(), submittalDate); + + final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale()); + final String submittalDateAsString = formatter.print(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)) { - 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, - getApprovedOnLocalDate()); + + baseDataValidator.reset().parameter(SavingsApiConstants.withdrawnOnDateParamName).value(withdrawnOn) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date"); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } return actualChanges; @@ -1355,10 +1414,17 @@ public class SavingsAccount extends AbstractPersistable { final Map actualChanges = new LinkedHashMap(); + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.activateAction); + SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); 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(); @@ -1382,24 +1448,34 @@ public class SavingsAccount extends AbstractPersistable { this.lockedInUntilDate = calculateDateAccountIsLockedUntil(getActivationLocalDate()); 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(); - throw new InvalidSavingsAccountStateTransitionException("activate", "cannot.be.before.client.activation.date", errorMessage, - activationDate, this.client.getActivationLocalDate()); + + final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale()); + final String dateAsString = formatter.print(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(); - if (activationDate.isBefore(submittalDate)) { - final String errorMessage = "Savings account application cannot be withdrawn by applicant on date before its submittal date: " - + submittalDate.toString(); - throw new InvalidSavingsAccountStateTransitionException("activate", "cannot.be.before.submittal.date", - errorMessage, getApprovedOnLocalDate(), submittalDate); + final LocalDate approvalDate = getApprovedOnLocalDate(); + if (activationDate.isBefore(approvalDate)) { + + final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale()); + final String dateAsString = formatter.print(approvalDate); + + baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(dateAsString) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.approval.date"); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } 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, - getApprovedOnLocalDate()); + + baseDataValidator.reset().parameter(SavingsApiConstants.activatedOnDateParamName).value(activationDate) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.a.future.date"); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } // auto enter deposit for minimum required opening balance when diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountStatusType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountStatusType.java index 84cc7abec..1858e7bc9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountStatusType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountStatusType.java @@ -85,6 +85,6 @@ public enum SavingsAccountStatusType { } public boolean isClosed() { - return this.value.equals(SavingsAccountStatusType.CLOSED.getValue()); + return this.value.equals(SavingsAccountStatusType.CLOSED.getValue()) || isRejected() || isApplicationWithdrawnByApplicant(); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/InvalidSavingsAccountStateTransitionException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/InvalidSavingsAccountStateTransitionException.java deleted file mode 100644 index f8883cdc9..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/exception/InvalidSavingsAccountStateTransitionException.java +++ /dev/null @@ -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); - } - -} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index f079a10fa..ed5bd7846 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/service/SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -162,7 +162,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl final SavingsAccount account = this.savingAccountRepository.findOneWithNotFoundDetection(savingsId); - account.update(command, changes); + account.modifyApplication(command, changes); + account.validateNewApplicationState(DateUtils.getLocalDateOfTenant()); if (!changes.isEmpty()) {