Merge branch 'PR378' into develop
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java
index b279163..1ae2609 100755
--- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java
@@ -28,10 +28,12 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CommonConstants;
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
@@ -2214,13 +2216,17 @@
Integer holdTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, String.valueOf(balance - 100),
SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "300",
- SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR);
+ SavingsAccountHelper.TRANSACTION_DATE_PLUS_ONE, CommonConstants.RESPONSE_ERROR);
assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance",
error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
Integer releaseTransactionId = this.savingsAccountHelper.releaseAmount(savingsId, holdTransactionId);
- withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "300",
- SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+ Date today = DateUtils.getDateOfTenant();
+ String todayDate = today.toString();
+ SimpleDateFormat dt1 = new SimpleDateFormat("dd MMM yyyy");
+ todayDate = dt1.format(today).toString();
+ withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "300", todayDate,
+ CommonConstants.RESPONSE_RESOURCE_ID);
withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId);
balance -= new Float("300");
assertEquals("Verifying Withdrawal Amount", new Float("300"), withdrawTransaction.get("amount"));
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
index c2cd4f9..d0752a3 100644
--- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
@@ -69,6 +69,7 @@
public static final String CREATED_DATE_PLUS_ONE = "09 January 2013";
public static final String CREATED_DATE_MINUS_ONE = "07 January 2013";
public static final String TRANSACTION_DATE = "01 March 2013";
+ public static final String TRANSACTION_DATE_PLUS_ONE = "02 March 2013";
public static final String LAST_TRANSACTION_DATE = "01 March 2013";
public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingConstants.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingConstants.java
index 000b3e4..5ed32c3 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingConstants.java
@@ -30,7 +30,4 @@
public static final String PERIODIC_ACCRUAL_ACCOUNTING_RESOURCE_NAME = "periodicaccrual";
public static final String PERIODIC_ACCRUAL_ACCOUNTING_EXECUTION_ERROR_CODE = "execution.failed";
-
- public static final Set<String> LOAN_PERIODIC_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(accrueTillParamName,
- localeParamName, dateFormatParamName));
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/serialization/AccrualAccountingDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/serialization/AccrualAccountingDataValidator.java
index e3f48c7..f024c1c 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/serialization/AccrualAccountingDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/serialization/AccrualAccountingDataValidator.java
@@ -18,14 +18,16 @@
*/
package org.apache.fineract.accounting.accrual.serialization;
-import static org.apache.fineract.accounting.accrual.api.AccrualAccountingConstants.LOAN_PERIODIC_REQUEST_DATA_PARAMETERS;
import static org.apache.fineract.accounting.accrual.api.AccrualAccountingConstants.PERIODIC_ACCRUAL_ACCOUNTING_RESOURCE_NAME;
import static org.apache.fineract.accounting.accrual.api.AccrualAccountingConstants.accrueTillParamName;
import java.lang.reflect.Type;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.fineract.accounting.accrual.api.AccrualAccountingConstants;
@@ -52,6 +54,9 @@
public final class AccrualAccountingDataValidator {
private final FromJsonHelper fromApiJsonHelper;
+ private static final Set<String> LOAN_PERIODIC_REQUEST_DATA_PARAMETERS = new HashSet<>(
+ Arrays.asList(accrueTillParamName, AccrualAccountingConstants.localeParamName,
+ AccrualAccountingConstants.dateFormatParamName));
@Autowired
public AccrualAccountingDataValidator(final FromJsonHelper fromApiJsonfromApiJsonHelper) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryRuntimeException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryRuntimeException.java
new file mode 100644
index 0000000..a0fa0a7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryRuntimeException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.accounting.journalentry.exception;
+
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class JournalEntryRuntimeException extends AbstractPlatformDomainRuleException {
+
+ public JournalEntryRuntimeException(String errorCode, String errorMessage) {
+ super(errorCode, errorMessage);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java
index b556014..ad18582 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java
@@ -52,6 +52,8 @@
import org.apache.fineract.accounting.journalentry.exception.JournalEntriesNotFoundException;
import org.apache.fineract.accounting.journalentry.exception.JournalEntryInvalidException;
import org.apache.fineract.accounting.journalentry.exception.JournalEntryInvalidException.GL_JOURNAL_ENTRY_INVALID_REASON;
+
+import org.apache.fineract.accounting.journalentry.exception.JournalEntryRuntimeException;
import org.apache.fineract.accounting.journalentry.serialization.JournalEntryCommandFromApiJsonDeserializer;
import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
import org.apache.fineract.accounting.provisioning.domain.LoanProductProvisioningEntry;
@@ -255,7 +257,9 @@
}
}
}
- if (credits.length != validCredits.length) { throw new RuntimeException("Invalid credits"); }
+ if (credits.length != validCredits.length) {
+ throw new JournalEntryRuntimeException("error.msg.glJournalEntry.invalid.credits", "Invalid Credits.");
+ }
}
if (debits != null && debits.length > 0) {
@@ -268,9 +272,11 @@
validDebits[i] = debit;
}
}
- }
- if (debits.length != validDebits.length) { throw new RuntimeException("Invalid debits"); }
- }
+ }
+ if (debits.length != validDebits.length) {
+ throw new JournalEntryRuntimeException("error.msg.glJournalEntry.invalid.debits","Invalid Debits");
+ }
+ }
}
private void checkDebitAndCreditAmounts(final SingleDebitOrCreditEntryCommand[] credits, final SingleDebitOrCreditEntryCommand[] debits) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/api/AdHocApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/api/AdHocApiResource.java
new file mode 100644
index 0000000..75380f1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/api/AdHocApiResource.java
@@ -0,0 +1,170 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.api;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.adhocquery.data.AdHocData;
+import org.apache.fineract.adhocquery.service.AdHocReadPlatformService;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/adhocquery")
+@Component
+@Scope("singleton")
+public class AdHocApiResource {
+
+ /**
+ * The set of parameters that are supported in response for {@link AdhocData}
+ */
+ private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "query", "tableName","tableField","isActive","createdBy","createdOn","createdById","updatedById","updatedOn","email"));
+
+ private final PlatformSecurityContext context;
+ private final AdHocReadPlatformService adHocReadPlatformService;
+ private final DefaultToApiJsonSerializer<AdHocData> toApiJsonSerializer;
+ private final ApiRequestParameterHelper apiRequestParameterHelper;
+ private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+ @Autowired
+ public AdHocApiResource(final PlatformSecurityContext context, final AdHocReadPlatformService readPlatformService,
+ final DefaultToApiJsonSerializer<AdHocData> toApiJsonSerializer,
+ final ApiRequestParameterHelper apiRequestParameterHelper,
+ final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+ this.context = context;
+ this.adHocReadPlatformService = readPlatformService;
+ this.toApiJsonSerializer = toApiJsonSerializer;
+ this.apiRequestParameterHelper = apiRequestParameterHelper;
+ this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+ }
+
+ @GET
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveAll(@Context final UriInfo uriInfo) {
+
+ this.context.authenticatedUser();
+ final Collection<AdHocData> adhocs = this.adHocReadPlatformService.retrieveAllAdHocQuery();
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, adhocs, this.RESPONSE_DATA_PARAMETERS);
+ }
+ @GET
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Path("template")
+ public String template(@Context final UriInfo uriInfo) {
+ this.context.authenticatedUser();
+ final AdHocData user = this.adHocReadPlatformService.retrieveNewAdHocDetails();
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, user, this.RESPONSE_DATA_PARAMETERS);
+ }
+ @POST
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String createAdHocQuery(final String apiRequestBodyAsJson) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+ .createAdHoc() //
+ .withJson(apiRequestBodyAsJson) //
+ .build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ @GET
+ @Path("{adHocId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveAdHocQuery(@PathParam("adHocId") final Long adHocId, @Context final UriInfo uriInfo) {
+
+ this.context.authenticatedUser();
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+ final AdHocData adhoc = this.adHocReadPlatformService.retrieveOne(adHocId);
+
+ return this.toApiJsonSerializer.serialize(settings, adhoc, this.RESPONSE_DATA_PARAMETERS);
+ }
+ @PUT
+ @Path("{adHocId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String update(@PathParam("adHocId") final Long adHocId, final String apiRequestBodyAsJson) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+ .updateAdHoc(adHocId) //
+ .withJson(apiRequestBodyAsJson) //
+ .build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+ /**
+ * Delete AdHocQuery
+ *
+ * @param adHocId
+ * @return
+ */
+ @DELETE
+ @Path("{adHocId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String deleteAdHocQuery(@PathParam("adHocId") final Long adHocId) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+ .deleteAdHoc(adHocId) //
+ .build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ private boolean is(final String commandParam, final String commandValue) {
+ return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+ }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/api/AdHocJsonInputParams.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/api/AdHocJsonInputParams.java
new file mode 100644
index 0000000..0cb3384
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/api/AdHocJsonInputParams.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.api;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/***
+ * Enum of all parameters passed in while creating/updating a AdHocQuery
+ ***/
+public enum AdHocJsonInputParams {
+ ID("id"), NAME("name"),QUERY("query"),TABLENAME("tableName"),TABLEFIELD("tableFields"), ISACTIVE("isActive"),EMAIL("email");
+
+ private final String value;
+
+ private AdHocJsonInputParams(final String value) {
+ this.value = value;
+ }
+
+ private static final Set<String> values = new HashSet<>();
+ static {
+ for (final AdHocJsonInputParams type : AdHocJsonInputParams.values()) {
+ values.add(type.value);
+ }
+ }
+
+ public static Set<String> getAllValues() {
+ return values;
+ }
+
+ @Override
+ public String toString() {
+ return name().toString().replaceAll("_", " ");
+ }
+
+ public String getValue() {
+ return this.value;
+ }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/data/AdHocData.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/data/AdHocData.java
new file mode 100644
index 0000000..f0fd7a8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/data/AdHocData.java
@@ -0,0 +1,122 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.useradministration.data.AppUserData;
+import org.apache.fineract.useradministration.data.RoleData;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object represent note or case information AdHocData
+ *
+ */
+public class AdHocData {
+
+
+
+ @SuppressWarnings("unused")
+ private final Long id;
+ @SuppressWarnings("unused")
+ private final String name;
+ @SuppressWarnings("unused")
+ private final String query;
+ @SuppressWarnings("unused")
+ private final String tableName;
+ @SuppressWarnings("unused")
+ private final String tableFields;
+ @SuppressWarnings("unused")
+ private final String email;
+ @SuppressWarnings("unused")
+ private final boolean isActive;
+ @SuppressWarnings("unused")
+ private final DateTime createdOn;
+ @SuppressWarnings("unused")
+ private final Long createdById;
+ @SuppressWarnings("unused")
+ private final Long updatedById;
+ @SuppressWarnings("unused")
+ private final DateTime updatedOn;
+ @SuppressWarnings("unused")
+ private final String createdBy;
+
+
+
+
+ public AdHocData(final Long id, final String name,final String query, final String tableName,final String tableFields,
+ final boolean isActive, final DateTime createdOn, final Long createdById,final Long updatedById,
+ final DateTime updatedOn,final String createdBy,final String email
+ ) {
+ this.id = id;
+ this.name=name;
+ this.query=query;
+ this.tableName = tableName;
+ this.tableFields = tableFields;
+ this.isActive = isActive;
+ this.createdOn = createdOn;
+ this.createdById = createdById;
+ this.updatedById=updatedById;
+ this.updatedOn=updatedOn;
+ this.createdBy=createdBy;
+ this.email=email;
+ }
+ public static AdHocData template() {
+ AdHocData adHocData = new AdHocData(null,null,null,null,null,false,null,null,null,null,null,null);
+ return adHocData;
+ }
+ public Long getId() {
+ return this.id;
+ }
+ public String getName() {
+ return this.name;
+ }
+ public String getQuery() {
+ return this.query;
+ }
+ public String getTableName() {
+ return this.tableName;
+ }
+ public String getTableFields() {
+ return this.tableFields;
+ }
+ public String getEmail() {
+ return this.email;
+ }
+ public boolean isActive() {
+ return this.isActive;
+ }
+ public DateTime getCreatedOn() {
+ return this.createdOn;
+ }
+ public Long getCreatedById() {
+ return this.createdById;
+ }
+ public Long getUpdatedById() {
+ return this.updatedById;
+ }
+ public DateTime getUpdatedOn() {
+ return this.updatedOn;
+ }
+ public String getCreatedBy() {
+ return this.createdBy;
+ }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHoc.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHoc.java
new file mode 100644
index 0000000..be315ba
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHoc.java
@@ -0,0 +1,153 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.adhocquery.api.AdHocJsonInputParams;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+
+@Entity
+@Table(name = "m_adhoc")
+public class AdHoc extends AbstractAuditableCustom<AppUser, Long> {
+
+ @Column(name = "name", length = 100)
+ private String name;
+
+ @Column(name = "query", length = 2000)
+ private String query;
+
+ @Column(name = "table_name", length = 100)
+ private String tableName;
+
+
+ @Column(name = "table_fields", length = 2000)
+ private String tableFields;
+
+ @Column(name = "email", length = 500)
+ private String email;
+
+
+ @Column(name = "IsActive", nullable = false)
+ private boolean isActive = false;
+
+ private AdHoc(final String name, final String query,final String tableName,final String tableFields ,final String email,final boolean isActive) {
+ this.name = StringUtils.defaultIfEmpty(name, null);
+ this.query=StringUtils.defaultIfEmpty(query,null);
+ this.tableName=StringUtils.defaultIfEmpty(tableName,null);
+ this.tableFields=StringUtils.defaultIfEmpty(tableFields,null);
+ this.email=StringUtils.defaultIfEmpty(email,null);
+ this.isActive = BooleanUtils.toBooleanDefaultIfNull(isActive, false);
+
+ }
+ public static AdHoc fromJson(final JsonCommand command) {
+ final String name = command.stringValueOfParameterNamed(AdHocJsonInputParams.NAME.getValue());
+
+ String commandQuery=command.stringValueOfParameterNamed(AdHocJsonInputParams.QUERY.getValue());
+
+ SQLInjectionValidator.validateAdhocQuery(commandQuery);
+ final String query = commandQuery;
+ final String tableName = command.stringValueOfParameterNamed(AdHocJsonInputParams.TABLENAME.getValue());
+ final String tableFields = command.stringValueOfParameterNamed(AdHocJsonInputParams.TABLEFIELD.getValue());
+ final String email = command.stringValueOfParameterNamed(AdHocJsonInputParams.EMAIL.getValue());
+ final boolean isActive = command.booleanPrimitiveValueOfParameterNamed(AdHocJsonInputParams.ISACTIVE.getValue());
+ return new AdHoc(name,query,tableName,tableFields ,email,isActive);
+ }
+
+ public Map<String, Object> update(final JsonCommand command) {
+
+ final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+ final String nameParamName = "name";
+ if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
+ final String newValue = command.stringValueOfParameterNamed(nameParamName);
+ actualChanges.put(nameParamName, newValue);
+ this.name = newValue;
+ }
+
+ final String descriptionParamName = "query";
+ if (command.isChangeInStringParameterNamed(descriptionParamName, this.query)) {
+ final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+ actualChanges.put(descriptionParamName, newValue);
+ this.query = newValue;
+ }
+ final String tableName = "tableName";
+ if (command.isChangeInStringParameterNamed(tableName, this.tableName)) {
+ final String newValue = command.stringValueOfParameterNamed(tableName);
+ actualChanges.put(tableName, newValue);
+ this.tableName = newValue;
+ }
+ final String tableField = "tableField";
+ if (command.isChangeInStringParameterNamed(tableField, this.tableFields)) {
+ final String newValue = command.stringValueOfParameterNamed(tableField);
+ actualChanges.put(tableField, newValue);
+ this.tableFields = newValue;
+ }
+ final String email = "email";
+ if (command.isChangeInStringParameterNamed(email, this.email)) {
+ final String newValue = command.stringValueOfParameterNamed(email);
+ actualChanges.put(email, newValue);
+ this.email = newValue;
+ }
+ final String paramisActive = "isActive";
+ if (command.isChangeInBooleanParameterNamed(paramisActive, this.isActive)) {
+ final Boolean newValue = command.booleanObjectValueOfParameterNamed(paramisActive);
+ actualChanges.put(paramisActive, newValue);
+ this.isActive = newValue;
+ }
+ return actualChanges;
+ }
+
+ public String getName() {
+ return name;
+ }
+ public String getQuery() {
+ return query;
+ }
+ public String getTableName() {
+ return tableName;
+ }
+ public String getTableFields() {
+ return tableFields;
+ }
+ public boolean isActive() {
+ return this.isActive;
+ }
+ public String getEmail() {
+ return email;
+ }
+ public void disableActive() {
+ this.isActive = true;
+ }
+ public void enableActive() {
+ this.isActive = false;
+ }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHocRepository.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHocRepository.java
new file mode 100644
index 0000000..fc31eb7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHocRepository.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface AdHocRepository extends JpaRepository<AdHoc, Long>, JpaSpecificationExecutor<AdHoc> {
+ // no added behaviour
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/exception/AdHocNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/exception/AdHocNotFoundException.java
new file mode 100644
index 0000000..7de67f5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/exception/AdHocNotFoundException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when AdHoc resources are not
+ * found.
+ */
+public class AdHocNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+ public AdHocNotFoundException(final Long id) {
+ super("error.msg.adhocquery.adhoc.id.invalid", "Adhoc Record with identifier " + id + " does not exist", id);
+ }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/CreateAdHocCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/CreateAdHocCommandHandler.java
new file mode 100644
index 0000000..73f09dc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/CreateAdHocCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.handler;
+
+import org.apache.fineract.adhocquery.service.AdHocWritePlatformService;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ADHOC", action = "CREATE")
+public class CreateAdHocCommandHandler implements NewCommandSourceHandler {
+
+ private final AdHocWritePlatformService writePlatformService;
+
+ @Autowired
+ public CreateAdHocCommandHandler(final AdHocWritePlatformService writePlatformService) {
+ this.writePlatformService = writePlatformService;
+ }
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+
+ return this.writePlatformService.createAdHocQuery(command);
+ }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/DeleteAdHocCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/DeleteAdHocCommandHandler.java
new file mode 100644
index 0000000..eb8e9f6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/DeleteAdHocCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.adhocquery.service.AdHocWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ADHOC", action = "DELETE")
+public class DeleteAdHocCommandHandler implements NewCommandSourceHandler {
+
+ private final AdHocWritePlatformService writePlatformService;
+
+ @Autowired
+ public DeleteAdHocCommandHandler(final AdHocWritePlatformService writePlatformService) {
+ this.writePlatformService = writePlatformService;
+ }
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+
+ return this.writePlatformService.deleteAdHocQuery(command.entityId());
+ }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/UpdateAdHocCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/UpdateAdHocCommandHandler.java
new file mode 100644
index 0000000..db9d4ac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/UpdateAdHocCommandHandler.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.adhocquery.service.AdHocWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ADHOC", action = "UPDATE")
+public class UpdateAdHocCommandHandler implements NewCommandSourceHandler {
+
+ private final AdHocWritePlatformService writePlatformService;
+
+ @Autowired
+ public UpdateAdHocCommandHandler(final AdHocWritePlatformService writePlatformService) {
+ this.writePlatformService = writePlatformService;
+ }
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+
+ final Long adHocId = command.entityId();
+ return this.writePlatformService.updateAdHocQuery(adHocId, command);
+ }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocDataValidator.java
new file mode 100644
index 0000000..2cdd294
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocDataValidator.java
@@ -0,0 +1,127 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.service;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public final class AdHocDataValidator {
+
+ /**
+ * The parameters supported for this command.
+ */
+ private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("name","query","tableName","tableFields","email","isActive"));
+
+ private final FromJsonHelper fromApiJsonHelper;
+
+ @Autowired
+ public AdHocDataValidator(final FromJsonHelper fromApiJsonHelper) {
+ this.fromApiJsonHelper = fromApiJsonHelper;
+ }
+
+ public void validateForCreate(final String json) {
+ if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+ final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+ this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
+
+ final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("adhoc");
+
+ final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+ final String name = this.fromApiJsonHelper.extractStringNamed("name", element);
+ baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(100);
+
+ final String description = this.fromApiJsonHelper.extractStringNamed("query", element);
+ baseDataValidator.reset().parameter("query").value(description).notBlank().notExceedingLengthOf(2000);
+
+ final String tableName = this.fromApiJsonHelper.extractStringNamed("tableName", element);
+ baseDataValidator.reset().parameter("tableName").value(tableName).notBlank().notExceedingLengthOf(100);
+
+ final String tableFields = this.fromApiJsonHelper.extractStringNamed("tableFields", element);
+ baseDataValidator.reset().parameter("tableFields").value(tableFields).notBlank().notExceedingLengthOf(1000);
+
+ final String email = this.fromApiJsonHelper.extractStringNamed("email", element);
+ baseDataValidator.reset().parameter("email").value(email).notBlank().notExceedingLengthOf(500);
+ throwExceptionIfValidationWarningsExist(dataValidationErrors);
+ }
+
+ public void validateForUpdate(final String json) {
+ if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+ final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+ this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
+
+ final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("adhoc");
+
+ final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+ if (this.fromApiJsonHelper.parameterExists("name", element)) {
+ final String name = this.fromApiJsonHelper.extractStringNamed("name", element);
+ baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(100);
+ }
+
+ if (this.fromApiJsonHelper.parameterExists("query", element)) {
+ final String query = this.fromApiJsonHelper.extractStringNamed("query", element);
+ baseDataValidator.reset().parameter("query").value(query).notBlank().notExceedingLengthOf(2000);
+ }
+ if (this.fromApiJsonHelper.parameterExists("tableName", element)) {
+ final String tableName = this.fromApiJsonHelper.extractStringNamed("tableName", element);
+ baseDataValidator.reset().parameter("tableName").value(tableName).notBlank().notExceedingLengthOf(100);
+ }
+ if (this.fromApiJsonHelper.parameterExists("tableFields", element)) {
+ final String tableField = this.fromApiJsonHelper.extractStringNamed("tableFields", element);
+ baseDataValidator.reset().parameter("tableFields").value(tableField).notBlank().notExceedingLengthOf(2000);
+ }
+ if (this.fromApiJsonHelper.parameterExists("email", element)) {
+ final String email = this.fromApiJsonHelper.extractStringNamed("email", element);
+ baseDataValidator.reset().parameter("email").value(email).notBlank().notExceedingLengthOf(500);
+ }
+ /*if (this.fromApiJsonHelper.parameterExists("isActive", element)) {
+ final Integer isActive = this.fromApiJsonHelper.extractIntegerNamed("isActive", element, Locale.getDefault());
+ baseDataValidator.reset().parameter("isActive").value(isActive).notNull().inMinMaxRange(1, 2);
+ }*/
+
+ throwExceptionIfValidationWarningsExist(dataValidationErrors);
+ }
+
+ private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+ if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+ }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformService.java
new file mode 100644
index 0000000..414a2b2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformService.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.adhocquery.data.AdHocData;
+
+public interface AdHocReadPlatformService {
+
+ Collection<AdHocData> retrieveAllAdHocQuery();
+
+ Collection<AdHocData> retrieveAllActiveAdHocQuery();
+
+ AdHocData retrieveOne(Long adHocId);
+ AdHocData retrieveNewAdHocDetails();
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformServiceImpl.java
new file mode 100644
index 0000000..eb1f633
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformServiceImpl.java
@@ -0,0 +1,109 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.adhocquery.data.AdHocData;
+import org.apache.fineract.adhocquery.exception.AdHocNotFoundException;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.joda.time.DateTime;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AdHocReadPlatformServiceImpl implements AdHocReadPlatformService {
+
+ private final JdbcTemplate jdbcTemplate;
+ private final AdHocMapper adHocRowMapper;
+
+ @Autowired
+ public AdHocReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ this.adHocRowMapper = new AdHocMapper();
+ }
+
+ @Override
+ public Collection<AdHocData> retrieveAllAdHocQuery() {
+ final String sql = "select " + this.adHocRowMapper.schema() + " order by r.id";
+
+ return this.jdbcTemplate.query(sql, this.adHocRowMapper);
+ }
+
+ @Override
+ public Collection<AdHocData> retrieveAllActiveAdHocQuery() {
+ final String sql = "select " + this.adHocRowMapper.schema() + " where r.IsActive = 1 order by r.id";
+
+ return this.jdbcTemplate.query(sql, this.adHocRowMapper);
+ }
+
+ @Override
+ public AdHocData retrieveOne(final Long id) {
+
+ try {
+ final String sql = "select " + this.adHocRowMapper.schema() + " where r.id=?";
+
+ return this.jdbcTemplate.queryForObject(sql, this.adHocRowMapper, new Object[] { id });
+ } catch (final EmptyResultDataAccessException e) {
+ throw new AdHocNotFoundException(id);
+ }
+ }
+
+ protected static final class AdHocMapper implements RowMapper<AdHocData> {
+
+ @Override
+ public AdHocData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+ final Long id = JdbcSupport.getLong(rs, "id");
+ final String name = rs.getString("name");
+ final String query = rs.getString("query");
+ final String tableName=rs.getString("tableName");
+ final String tableFields=rs.getString("tableField");
+ final Boolean isActive = rs.getBoolean("isActive");
+ final DateTime createdDate = JdbcSupport.getDateTime(rs, "createdDate");
+ final Long createdById = JdbcSupport.getLong(rs, "createdById");
+ final Long updatedById=JdbcSupport.getLong(rs, "updatedById");
+ final DateTime updatedOn=JdbcSupport.getDateTime(rs, "updatedOn");
+ final String createdByUsername=rs.getString("createdBy");
+ final String email=rs.getString("email");
+
+ return new AdHocData(id,name,query, tableName,tableFields,isActive,createdDate,createdById,updatedById,updatedOn,createdByUsername,email);
+ }
+
+ public String schema() {
+ return " r.id as id, r.name as name, r.query as query, r.table_name as tableName,r.table_fields as tableField ,r.IsActive as isActive ,r.email as email ,"
+ +" r.created_date as createdDate, r.createdby_id as createdById,cb.username as createdBy,r.lastmodifiedby_id as updatedById ,r.lastmodified_date as updatedOn "
+ + " from m_adhoc r left join m_appuser cb on cb.id=r.createdby_id left join m_appuser mb on mb.id=r.lastmodifiedby_id";
+
+ }
+ }
+
+ @Override
+ public AdHocData retrieveNewAdHocDetails() {
+ return AdHocData.template();
+ }
+
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocScheduledJobRunnerService.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocScheduledJobRunnerService.java
new file mode 100644
index 0000000..88e24a2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocScheduledJobRunnerService.java
@@ -0,0 +1,23 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.service;
+
+public interface AdHocScheduledJobRunnerService {
+ void generateClientSchedule();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocScheduledJobRunnerServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocScheduledJobRunnerServiceImpl.java
new file mode 100644
index 0000000..d10cddd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocScheduledJobRunnerServiceImpl.java
@@ -0,0 +1,77 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.adhocquery.data.AdHocData;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service(value = "adHocScheduledJobRunnerService")
+public class AdHocScheduledJobRunnerServiceImpl implements AdHocScheduledJobRunnerService {
+
+ private final static Logger logger = LoggerFactory.getLogger(AdHocScheduledJobRunnerServiceImpl.class);
+ private final AdHocReadPlatformService adHocReadPlatformService;
+ private final JdbcTemplate jdbcTemplate;
+
+ @Autowired
+ public AdHocScheduledJobRunnerServiceImpl(final RoutingDataSource dataSource,
+ final AdHocReadPlatformService adHocReadPlatformService
+ ) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ this.adHocReadPlatformService = adHocReadPlatformService;
+
+ }
+ @Transactional
+ @Override
+ @CronTarget(jobName = JobName.GENERATE_ADHOCCLIENT_SCEHDULE)
+ public void generateClientSchedule() {
+ final Collection<AdHocData> adhocs = this.adHocReadPlatformService.retrieveAllActiveAdHocQuery();
+ if(adhocs.size()>0){
+ adhocs.forEach(adhoc->{
+ //jdbcTemplate.execute("truncate table "+adhoc.getTableName());
+ final StringBuilder insertSqlBuilder = new StringBuilder(900);
+ insertSqlBuilder
+ .append("INSERT INTO ")
+ .append(adhoc.getTableName()+"(")
+ .append(adhoc.getTableFields()+") ")
+ .append(adhoc.getQuery());
+ if (insertSqlBuilder.length() > 0) {
+ final int result = this.jdbcTemplate.update(insertSqlBuilder.toString());
+ logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by inserted: " + result);
+ }
+ });
+ }else{
+ logger.info(ThreadLocalContextUtil.getTenant().getName() + "Nothing to update ");
+ }
+
+
+
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformService.java
new file mode 100644
index 0000000..23427fc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformService.java
@@ -0,0 +1,35 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface AdHocWritePlatformService {
+
+ CommandProcessingResult createAdHocQuery(JsonCommand command);
+
+ CommandProcessingResult updateAdHocQuery(Long adHocId, JsonCommand command);
+
+ CommandProcessingResult deleteAdHocQuery(Long adHocId);
+
+ CommandProcessingResult disableAdHocQuery(Long adHocId);
+
+ CommandProcessingResult enableAdHocQuery(Long adHocId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..1cec38d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,193 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.service;
+
+import java.util.Map;
+
+import org.apache.fineract.adhocquery.domain.AdHoc;
+import org.apache.fineract.adhocquery.domain.AdHocRepository;
+import org.apache.fineract.adhocquery.exception.AdHocNotFoundException;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePlatformService {
+
+ private final static Logger logger = LoggerFactory.getLogger(AdHocWritePlatformServiceJpaRepositoryImpl.class);
+ private final PlatformSecurityContext context;
+ private final AdHocRepository adHocRepository;
+ private final AdHocDataValidator adHocCommandFromApiJsonDeserializer;
+
+
+ @Autowired
+ public AdHocWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final AdHocRepository adHocRepository,
+ final AdHocDataValidator adHocCommandFromApiJsonDeserializer) {
+ this.context = context;
+ this.adHocRepository = adHocRepository;
+ this.adHocCommandFromApiJsonDeserializer = adHocCommandFromApiJsonDeserializer;
+
+ }
+
+ @Transactional
+ @Override
+ public CommandProcessingResult createAdHocQuery(final JsonCommand command) {
+
+ try {
+ this.context.authenticatedUser();
+
+ this.adHocCommandFromApiJsonDeserializer.validateForCreate(command.json());
+
+ final AdHoc entity = AdHoc.fromJson(command);
+ this.adHocRepository.save(entity);
+
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(entity.getId()).build();
+ } catch (final DataIntegrityViolationException dve) {
+ handleDataIntegrityIssues(command, dve);
+ return new CommandProcessingResultBuilder() //
+ .withCommandId(command.commandId()) //
+ .build();
+ }
+ }
+
+ /*
+ * Guaranteed to throw an exception no matter what the data integrity issue
+ * is.
+ */
+ private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+
+ final Throwable realCause = dve.getMostSpecificCause();
+ if (realCause.getMessage().contains("unq_name")) {
+
+ final String name = command.stringValueOfParameterNamed("name");
+ throw new PlatformDataIntegrityException("error.msg.adhocquery.duplicate.name", "AdHocQuery with name `" + name + "` already exists",
+ "name", name);
+ }
+
+ logAsErrorUnexpectedDataIntegrityException(dve);
+ throw new PlatformDataIntegrityException("error.msg.adhocquery.unknown.data.integrity.issue",
+ "Unknown data integrity issue with resource.");
+ }
+
+ private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
+ logger.error(dve.getMessage(), dve);
+ }
+
+ @Transactional
+ @Override
+ public CommandProcessingResult updateAdHocQuery(final Long adHocId, final JsonCommand command) {
+ try {
+ this.context.authenticatedUser();
+
+ this.adHocCommandFromApiJsonDeserializer.validateForUpdate(command.json());
+
+ final AdHoc adHoc = this.adHocRepository.findOne(adHocId);
+ if (adHoc == null) { throw new AdHocNotFoundException(adHocId); }
+
+ final Map<String, Object> changes = adHoc.update(command);
+ if (!changes.isEmpty()) {
+ this.adHocRepository.saveAndFlush(adHoc);
+ }
+
+ return new CommandProcessingResultBuilder() //
+ .withCommandId(command.commandId()) //
+ .withEntityId(adHocId) //
+ .with(changes) //
+ .build();
+ } catch (final DataIntegrityViolationException dve) {
+ handleDataIntegrityIssues(command, dve);
+ return new CommandProcessingResultBuilder() //
+ .withCommandId(command.commandId()) //
+ .build();
+ }
+ }
+ /**
+ * Method for Delete adhoc
+ */
+ @Transactional
+ @Override
+ public CommandProcessingResult deleteAdHocQuery(Long adHocId) {
+
+ try {
+ /**
+ * Checking the adhocQuery present in DB or not using adHocId
+ */
+ final AdHoc adHoc = this.adHocRepository.findOne(adHocId);
+ if (adHoc == null) { throw new AdHocNotFoundException(adHocId); }
+
+ this.adHocRepository.delete(adHoc);
+ return new CommandProcessingResultBuilder().withEntityId(adHocId).build();
+ } catch (final DataIntegrityViolationException e) {
+ throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+ "Unknown data integrity issue with resource: " + e.getMostSpecificCause());
+ }
+ }
+
+ /**
+ * Method for disabling the adhocquery
+ */
+ @Transactional
+ @Override
+ public CommandProcessingResult disableAdHocQuery(Long adHocId) {
+ try {
+ /**
+ * Checking the adhocquery present in DB or not using adHocId
+ */
+ final AdHoc adHoc = this.adHocRepository.findOne(adHocId);
+ if (adHoc == null) { throw new AdHocNotFoundException(adHocId); }
+ adHoc.disableActive();
+ this.adHocRepository.save(adHoc);
+ return new CommandProcessingResultBuilder().withEntityId(adHocId).build();
+
+ } catch (final DataIntegrityViolationException e) {
+ throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+ "Unknown data integrity issue with resource: " + e.getMostSpecificCause());
+ }
+ }
+
+ /**
+ * Method for Enabling the Active
+ */
+ @Transactional
+ @Override
+ public CommandProcessingResult enableAdHocQuery(Long adHocId) {
+ try {
+ /**
+ * Checking the adHoc present in DB or not using id
+ */
+ final AdHoc adHoc = this.adHocRepository.findOne(adHocId);
+ if (adHoc == null) { throw new AdHocNotFoundException(adHocId); }
+ adHoc.enableActive();
+ this.adHocRepository.save(adHoc);
+ return new CommandProcessingResultBuilder().withEntityId(adHocId).build();
+
+ } catch (final DataIntegrityViolationException e) {
+ throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+ "Unknown data integrity issue with resource: " + e.getMostSpecificCause());
+ }
+ }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index a940150..0ad3612 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -3002,4 +3002,43 @@
this.href = "/savingsaccounts/" + accountId + "?command=unblock";
return this;
}
+ public CommandWrapperBuilder disableAdHoc(Long adHocId) {
+ this.actionName = "DISABLE";
+ this.entityName = "ADHOC";
+ this.entityId = adHocId;
+ this.href = "/adhoc/" + adHocId + "/disbale";
+ this.json = "{}";
+ return this;
+ }
+
+ public CommandWrapperBuilder enableAdHoc(Long adHocId) {
+ this.actionName = "ENABLE";
+ this.entityName = "ADHOC";
+ this.entityId = adHocId;
+ this.href = "/adhoc/" + adHocId + "/enable";
+ this.json = "{}";
+ return this;
+ }
+ public CommandWrapperBuilder createAdHoc() {
+ this.actionName = "CREATE";
+ this.entityName = "ADHOC";
+ this.href = "/adhocquery/template";
+ return this;
+ }
+ public CommandWrapperBuilder updateAdHoc(final Long adHocId) {
+ this.actionName = "UPDATE";
+ this.entityName = "ADHOC";
+ this.entityId = adHocId;
+ this.href = "/adhocquery/" + adHocId;
+ return this;
+ }
+
+ public CommandWrapperBuilder deleteAdHoc(Long adHocId) {
+ this.actionName = "DELETE";
+ this.entityName = "ADHOC";
+ this.entityId = adHocId;
+ this.href = "/adhocquery/" + adHocId;
+ this.json = "{}";
+ return this;
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/api/AccountNumberFormatsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/api/AccountNumberFormatsApiResource.java
index a420e2e..5d9601f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/api/AccountNumberFormatsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/api/AccountNumberFormatsApiResource.java
@@ -18,7 +18,10 @@
*/
package org.apache.fineract.infrastructure.accountnumberformat.api;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -58,6 +61,10 @@
private final ToApiJsonSerializer<AccountNumberFormatData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+ private static final Set<String> ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+ AccountNumberFormatConstants.idParamName, AccountNumberFormatConstants.accountTypeParamName,
+ AccountNumberFormatConstants.prefixTypeParamName, AccountNumberFormatConstants.accountTypeOptionsParamName,
+ AccountNumberFormatConstants.prefixTypeOptionsParamName));
@Autowired
public AccountNumberFormatsApiResource(final PlatformSecurityContext context,
@@ -84,8 +91,8 @@
AccountNumberFormatData accountNumberFormatData = this.accountNumberFormatReadPlatformService.retrieveTemplate(accountType);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
- return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
- AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
+ return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
+ ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
}
@GET
@@ -99,8 +106,8 @@
.getAllAccountNumberFormats();
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
- return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
- AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
+ return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
+ ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
}
@GET
@@ -121,8 +128,8 @@
accountNumberFormatData.templateOnTop(templateData.getAccountTypeOptions(), templateData.getPrefixTypeOptions());
}
- return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
- AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
+ return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
+ ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
}
@POST
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java
index 90cac29..a9b90f7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java
@@ -20,6 +20,7 @@
import java.lang.reflect.Type;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -45,6 +46,11 @@
public class AccountNumberFormatDataValidator {
private final FromJsonHelper fromApiJsonHelper;
+ private static final Set<String> ACCOUNT_NUMBER_FORMAT_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+ AccountNumberFormatConstants.accountTypeParamName, AccountNumberFormatConstants.prefixTypeParamName));
+
+ private static final Set<String> ACCOUNT_NUMBER_FORMAT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
+ Arrays.asList(AccountNumberFormatConstants.prefixTypeParamName));
@Autowired
public AccountNumberFormatDataValidator(final FromJsonHelper fromApiJsonHelper) {
@@ -55,8 +61,8 @@
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
- this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
- AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_CREATE_REQUEST_DATA_PARAMETERS);
+ this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+ ACCOUNT_NUMBER_FORMAT_CREATE_REQUEST_DATA_PARAMETERS);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
@@ -137,8 +143,8 @@
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
- this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
- AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_UPDATE_REQUEST_DATA_PARAMETERS);
+ this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+ ACCOUNT_NUMBER_FORMAT_UPDATE_REQUEST_DATA_PARAMETERS);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java
index 4836d8b..722f6be 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java
@@ -49,18 +49,11 @@
public static final String accountTypeOptionsParamName = "accountTypeOptions";
public static final String prefixTypeOptionsParamName = "prefixTypeOptions";
- public static final Set<String> ACCOUNT_NUMBER_FORMAT_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
- accountTypeParamName, prefixTypeParamName));
-
- public static final Set<String> ACCOUNT_NUMBER_FORMAT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(prefixTypeParamName));
-
/**
* These parameters will match the class level parameters of
* {@link AccountNumberFormatData}. Where possible, we try to get response
* parameters to match those of request parameters.
*/
- public static final Set<String> ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
- accountTypeParamName, prefixTypeParamName, accountTypeOptionsParamName, prefixTypeOptionsParamName));
// Error messages codes
public static final String EXCEPTION_DUPLICATE_ACCOUNT_TYPE = "error.msg.account.number.format.duplicate.account.type";
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
index ccbc7fd..7351980 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
@@ -26,6 +26,5 @@
public static final String RESOURCE_NAME = "CACHE";
public static final String cacheTypeParameter = "cacheType";
- public static final Set<String> REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(cacheTypeParameter));
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java
index 7d7e9fc..9cea2c2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java
@@ -20,8 +20,11 @@
import java.lang.reflect.Type;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.fineract.commands.annotation.CommandType;
@@ -47,6 +50,8 @@
public class UpdateCacheCommandHandler implements NewCommandSourceHandler {
private final CacheWritePlatformService cacheService;
+ private static final Set<String> REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(CacheApiConstants
+ .cacheTypeParameter));
@Autowired
public UpdateCacheCommandHandler(final CacheWritePlatformService cacheService) {
@@ -62,7 +67,7 @@
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
- command.checkForUnsupportedParameters(typeOfMap, json, CacheApiConstants.REQUEST_DATA_PARAMETERS);
+ command.checkForUnsupportedParameters(typeOfMap, json, REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsRuntimeException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsRuntimeException.java
new file mode 100644
index 0000000..7ae714b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsRuntimeException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.campaigns.sms.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class SmsRuntimeException extends AbstractPlatformDomainRuleException {
+
+ public SmsRuntimeException(String errorCode, String errorMessage) {
+ super(errorCode, errorMessage);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java
index 42d7b8b..3b0e0ae 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java
@@ -34,6 +34,7 @@
import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType;
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository;
+import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsRuntimeException;
import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
import org.apache.fineract.infrastructure.sms.scheduler.SmsMessageScheduledJobService;
@@ -204,10 +205,12 @@
Office campaignOffice = this.officeRepository.findOne(Long.valueOf(value));
if (campaignOffice
.doesNotHaveAnOfficeInHierarchyWithId(client.getOffice().getId())) {
- throw new RuntimeException();
+ throw new SmsRuntimeException("error.msg.no.office",
+ "Office not found for the id");
}
} else {
- throw new RuntimeException();
+ throw new SmsRuntimeException("error.msg.no.id.attribute",
+ "Office Id attribute is notfound");
}
}
}
@@ -258,10 +261,12 @@
if (key.equals("officeId")) {
Office campaignOffice = this.officeRepository.findOne(Long.valueOf(value));
if (campaignOffice.doesNotHaveAnOfficeInHierarchyWithId(client.getOffice().getId())) {
- throw new RuntimeException();
+ throw new SmsRuntimeException("error.msg.no.office",
+ "Office not found for the id");
}
} else {
- throw new RuntimeException();
+ throw new SmsRuntimeException("error.msg.no.id.attribute",
+ "Office Id attribute is notfound");
}
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
index 9b8356d..76f30c6 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
@@ -122,6 +122,29 @@
this.organisationCreditBureauId=organisationCreditBureauId;
}
+ public static JsonCommand fromJsonElement(final Long resourceId, final JsonElement parsedCommand) {
+ return new JsonCommand(resourceId, parsedCommand);
+ }
+
+ public JsonCommand(final Long resourceId, final JsonElement parsedCommand) {
+ this.parsedCommand = parsedCommand;
+ this.resourceId = resourceId;
+ this.commandId = null;
+ this.jsonCommand = null;
+ this.fromApiJsonHelper = null;
+ this.entityName = null;
+ this.subresourceId = null;
+ this.groupId = null;
+ this.clientId = null;
+ this.loanId = null;
+ this.savingsId = null;
+ this.transactionId = null;
+ this.url = null;
+ this.productId = null;
+ this.creditBureauId=null;
+ this.organisationCreditBureauId=null;
+ }
+
public Long getOrganisationCreditBureauId() {
return this.organisationCreditBureauId;
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
index b0b75b1..f3a160f 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
@@ -45,8 +45,8 @@
EXECUTE_REPORT_MAILING_JOBS("Execute Report Mailing Jobs"),
UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE("Update SMS Outbound with Campaign Message"),
SEND_MESSAGES_TO_SMS_GATEWAY("Send Messages to SMS Gateway"),
- GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY("Get Delivery Reports from SMS Gateway");
-
+ GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY("Get Delivery Reports from SMS Gateway"),
+ GENERATE_ADHOCCLIENT_SCEHDULE("Generate AdhocClient Schedule");
private final String name;
private JobName(final String name) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java
index 03785e0..60c2070 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java
@@ -30,7 +30,7 @@
private final static String[] COMMENTS = { "--", "({", "/*", "#" };
- private final static String SQL_PATTERN = "[a-zA-Z_=,'!><.?\"`% ()0-9]*";
+ private final static String SQL_PATTERN = "[a-zA-Z_=,\\-'!><.?\"`% ()0-9]*";
public final static void validateSQLInput(final String sqlSearch) {
String lowerCaseSQL = sqlSearch.toLowerCase();
@@ -114,4 +114,81 @@
throw new SQLInjectionException();
}
}
+ public final static void validateAdhocQuery(final String sqlSearch) {
+ String lowerCaseSQL = sqlSearch.toLowerCase();
+ for (String ddl : DDL_COMMANDS) {
+ if (lowerCaseSQL.contains(ddl)) {
+ throw new SQLInjectionException();
+ }
+ }
+
+
+ for (String comments : COMMENTS) {
+ if (lowerCaseSQL.contains(comments)) {
+ throw new SQLInjectionException();
+ }
+ }
+
+ //Removing the space before and after '=' operator
+ //String s = " \" OR 1 = 1"; For the cases like this
+ boolean injectionFound = false;
+ String inputSqlString = lowerCaseSQL;
+ while (inputSqlString.indexOf(" =") > 0) { //Don't remove space before = operator
+ inputSqlString = inputSqlString.replaceAll(" =", "=");
+ }
+
+ while (inputSqlString.indexOf("= ") > 0) { //Don't remove space after = operator
+ inputSqlString = inputSqlString.replaceAll("= ", "=");
+ }
+
+ StringTokenizer tokenizer = new StringTokenizer(inputSqlString, " ");
+ while (tokenizer.hasMoreTokens()) {
+ String token = tokenizer.nextToken().trim();
+ if (token.equals("'")) {
+ if (tokenizer.hasMoreElements()) {
+ String nextToken = tokenizer.nextToken().trim();
+ if (!nextToken.equals("'")) {
+ injectionFound = true;
+ break;
+ }
+ } else {
+ injectionFound = true;
+ break ;
+ }
+ }
+ if (token.equals("\"")) {
+ if (tokenizer.hasMoreElements()) {
+ String nextToken = tokenizer.nextToken().trim();
+ if (!nextToken.equals("\"")) {
+ injectionFound = true;
+ break;
+ }
+ } else {
+ injectionFound = true;
+ break ;
+ }
+ } else if (token.indexOf('=') > 0) {
+ StringTokenizer operatorToken = new StringTokenizer(token, "=");
+ String operand = operatorToken.nextToken().trim();
+ if (!operatorToken.hasMoreTokens()) {
+ injectionFound = true;
+ break;
+ }
+ String value = operatorToken.nextToken().trim();
+ if (operand.equals(value)) {
+ injectionFound = true;
+ break;
+ }
+ }
+ }
+ if (injectionFound) {
+ throw new SQLInjectionException();
+ }
+
+ Pattern pattern = Pattern.compile(SQL_PATTERN);
+ Matcher matcher = pattern.matcher(sqlSearch);
+ if (!matcher.matches()) {
+ throw new SQLInjectionException();
+ }
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidayApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidayApiConstants.java
index 27fa538..dc019a5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidayApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidayApiConstants.java
@@ -41,6 +41,7 @@
public static final String repaymentsRescheduledToParamName = "repaymentsRescheduledTo";
public static final String processed = "processed";
public static final String status = "status";
+ public static final String reschedulingType = "reschedulingType";
protected static final Set<String> HOLIDAY_RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(idParamName, nameParamName, fromDateParamName, descriptionParamName, toDateParamName,
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java
index 061f225..e8b22d3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java
@@ -180,4 +180,13 @@
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, holidays, HOLIDAY_RESPONSE_DATA_PARAMETERS);
}
+
+ @GET
+ @Path("/template")
+ @Consumes({MediaType.APPLICATION_JSON})
+ @Produces({MediaType.APPLICATION_JSON})
+ public String retrieveRepaymentScheduleUpdationTyeOptions(@Context final UriInfo uriInfo){
+ this.context.authenticatedUser().validateHasReadPermission(HOLIDAY_RESOURCE_NAME);
+ return this.toApiJsonSerializer.serialize(this.holidayReadPlatformService.retrieveRepaymentScheduleUpdationTyeOptions());
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayData.java
index 70f9839..12c2d0a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayData.java
@@ -39,9 +39,10 @@
private final Long officeId;
@SuppressWarnings("unused")
private final EnumOptionData status;
+ private final Integer reschedulingType;
public HolidayData(final Long id, final String name, final String description, final LocalDate fromDate, final LocalDate toDate,
- final LocalDate repaymentsRescheduledTo, final EnumOptionData status) {
+ final LocalDate repaymentsRescheduledTo, final EnumOptionData status, final Integer reschedulingType) {
this.id = id;
this.name = name;
this.description = description;
@@ -50,5 +51,6 @@
this.repaymentsRescheduledTo = repaymentsRescheduledTo;
this.officeId = null;
this.status = status;
+ this.reschedulingType = reschedulingType;
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayDataValidator.java
index e99eafb..755292c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayDataValidator.java
@@ -23,6 +23,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -33,6 +34,7 @@
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.organisation.holiday.api.HolidayApiConstants;
+import org.apache.fineract.organisation.holiday.domain.RescheduleType;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -50,7 +52,8 @@
Arrays.asList(HolidayApiConstants.localeParamName, HolidayApiConstants.dateFormatParamName,
HolidayApiConstants.nameParamName, HolidayApiConstants.fromDateParamName,
HolidayApiConstants.toDateParamName, HolidayApiConstants.descriptionParamName,
- HolidayApiConstants.officesParamName, HolidayApiConstants.repaymentsRescheduledToParamName));
+ HolidayApiConstants.officesParamName, HolidayApiConstants.repaymentsRescheduledToParamName,
+ HolidayApiConstants.reschedulingType));
@Autowired
public HolidayDataValidator(final FromJsonHelper fromApiJsonHelper) {
@@ -79,11 +82,18 @@
final LocalDate toDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.toDateParamName, element);
baseDataValidator.reset().parameter(HolidayApiConstants.toDateParamName).value(toDate).notNull();
-
- final LocalDate repaymentsRescheduledTo = this.fromApiJsonHelper.extractLocalDateNamed(
- HolidayApiConstants.repaymentsRescheduledToParamName, element);
- baseDataValidator.reset().parameter(HolidayApiConstants.repaymentsRescheduledToParamName).value(repaymentsRescheduledTo).notNull();
-
+
+ Integer reschedulingType = null;
+ if(this.fromApiJsonHelper.parameterExists(HolidayApiConstants.reschedulingType, element)){
+ reschedulingType = this.fromApiJsonHelper.extractIntegerNamed(HolidayApiConstants.reschedulingType, element, Locale.ENGLISH);
+ }
+
+ LocalDate repaymentsRescheduledTo = null;
+ if(reschedulingType == null || reschedulingType.equals(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue())){
+ repaymentsRescheduledTo = this.fromApiJsonHelper.extractLocalDateNamed(
+ HolidayApiConstants.repaymentsRescheduledToParamName, element);
+ baseDataValidator.reset().parameter(HolidayApiConstants.repaymentsRescheduledToParamName).value(repaymentsRescheduledTo).notNull();
+ }
Set<Long> offices = null;
final JsonObject topLevelJsonElement = element.getAsJsonObject();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/Holiday.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/Holiday.java
index be79b25..c25a3bf 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/Holiday.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/Holiday.java
@@ -73,9 +73,12 @@
@Temporal(TemporalType.DATE)
private Date toDate;
- @Column(name = "repayments_rescheduled_to", nullable = false)
+ @Column(name = "repayments_rescheduled_to", nullable = true)
@Temporal(TemporalType.DATE)
private Date repaymentsRescheduledTo;
+
+ @Column(name = "rescheduling_type", nullable = false)
+ private int reschedulingType;
@Column(name = "status_enum", nullable = false)
private Integer status;
@@ -94,13 +97,21 @@
final String name = command.stringValueOfParameterNamed(HolidayApiConstants.nameParamName);
final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName);
final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName);
- final LocalDate repaymentsRescheduledTo = command
- .localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
+ Integer reschedulingType = null;
+ if(command.parameterExists(HolidayApiConstants.reschedulingType)){
+ reschedulingType = command.integerValueOfParameterNamed(HolidayApiConstants.reschedulingType);
+ }
+ LocalDate repaymentsRescheduledTo = null;
+ if(reschedulingType == null || reschedulingType.equals(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue())){
+ repaymentsRescheduledTo = command
+ .localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
+ }
final Integer status = HolidayStatusType.PENDING_FOR_ACTIVATION.getValue();
final boolean processed = false;// default it to false. Only batch job
// should update this field.
final String description = command.stringValueOfParameterNamed(HolidayApiConstants.descriptionParamName);
- return new Holiday(name, fromDate, toDate, repaymentsRescheduledTo, status, processed, description, offices);
+
+ return new Holiday(name, fromDate, toDate, repaymentsRescheduledTo, status, processed, description, offices, reschedulingType);
}
public Map<String, Object> update(final JsonCommand command) {
@@ -125,6 +136,16 @@
actualChanges.put(descriptionParamName, newValue);
this.description = StringUtils.defaultIfEmpty(newValue, null);
}
+
+
+ if (command.isChangeInIntegerParameterNamed(HolidayApiConstants.reschedulingType,this.reschedulingType)) {
+ final Integer newValue =command.integerValueOfParameterNamed(HolidayApiConstants.reschedulingType);
+ actualChanges.put(HolidayApiConstants.reschedulingType, newValue);
+ this.reschedulingType = RescheduleType.fromInt(newValue).getValue();
+ if(newValue.equals(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getValue())){
+ this.repaymentsRescheduledTo = null;
+ }
+ }
if (currentStatus.isPendingActivation()) {
if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDateLocalDate())) {
@@ -205,7 +226,7 @@
}
private Holiday(final String name, final LocalDate fromDate, final LocalDate toDate, final LocalDate repaymentsRescheduledTo,
- final Integer status, final boolean processed, final String description, final Set<Office> offices) {
+ final Integer status, final boolean processed, final String description, final Set<Office> offices, final int reschedulingType) {
if (StringUtils.isNotBlank(name)) {
this.name = name.trim();
}
@@ -234,6 +255,7 @@
if (offices != null) {
this.offices = offices;
}
+ this.reschedulingType = reschedulingType;
}
protected Holiday() {}
@@ -298,4 +320,8 @@
}
this.status = HolidayStatusType.DELETED.getValue();
}
+
+ public RescheduleType getReScheduleType() {
+ return RescheduleType.fromInt(this.reschedulingType);
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/RescheduleType.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/RescheduleType.java
new file mode 100644
index 0000000..a87b658
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/RescheduleType.java
@@ -0,0 +1,64 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.holiday.domain;
+
+public enum RescheduleType {
+
+ INVALID(0, "rescheduletype.invalid"), RESCHEDULETOSPECIFICDATE(2, "rescheduletype.rescheduletospecificdate"), //
+ RESCHEDULETONEXTREPAYMENTDATE(1, "rescheduletype.rescheduletonextrepaymentdate");
+
+ private final Integer value;
+ private final String code;
+
+ private RescheduleType(Integer value, String code) {
+ this.value = value;
+ this.code = code;
+ }
+
+ public static RescheduleType fromInt(int rescheduleTypeValue) {
+ RescheduleType enumerration = RescheduleType.INVALID;
+ switch (rescheduleTypeValue) {
+ case 1:
+ enumerration = RescheduleType.RESCHEDULETONEXTREPAYMENTDATE;
+ break;
+ case 2:
+ enumerration = RescheduleType.RESCHEDULETOSPECIFICDATE;
+ break;
+ }
+ return enumerration;
+ }
+
+ public boolean isRescheduleToSpecificDate(){
+ return this.value.equals(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue());
+ }
+
+ public boolean isResheduleToNextRepaymentDate(){
+ return this.value.equals(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getValue());
+ }
+
+
+ public Integer getValue() {
+ return this.value;
+ }
+
+ public String getCode() {
+ return this.code;
+ }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayEnumerations.java
index 43e22d0..176a3c8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayEnumerations.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayEnumerations.java
@@ -20,6 +20,7 @@
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
+import org.apache.fineract.organisation.holiday.domain.RescheduleType;
public class HolidayEnumerations {
@@ -49,4 +50,29 @@
}
return optionData;
}
+
+ public static EnumOptionData rescheduleType(final int id) {
+ return rescheduleType(RescheduleType.fromInt(id));
+ }
+
+
+ public static EnumOptionData rescheduleType(final RescheduleType type) {
+ EnumOptionData optionData = null;
+ switch (type) {
+ case RESCHEDULETONEXTREPAYMENTDATE:
+ optionData = new EnumOptionData(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getValue().longValue(),
+ RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getCode(), "Reschedule to next repayment date");
+ break;
+ case RESCHEDULETOSPECIFICDATE:
+ optionData = new EnumOptionData(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue().longValue(),
+ RescheduleType.RESCHEDULETOSPECIFICDATE.getCode(), "Reschedule to specified date");
+ break;
+
+ default:
+ optionData = new EnumOptionData(RescheduleType.INVALID.getValue().longValue(),
+ RescheduleType.INVALID.getCode(), "Invalid");
+ break;
+ }
+ return optionData;
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformService.java
index 309af96..9ca4b3f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformService.java
@@ -20,7 +20,9 @@
import java.util.Collection;
import java.util.Date;
+import java.util.List;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.holiday.data.HolidayData;
public interface HolidayReadPlatformService {
@@ -28,4 +30,6 @@
Collection<HolidayData> retrieveAllHolidaysBySearchParamerters(final Long officeId, Date fromDate, Date toDate);
HolidayData retrieveHoliday(final Long holidayId);
+
+ List<EnumOptionData> retrieveRepaymentScheduleUpdationTyeOptions();
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java
index 2601d21..96ccb72 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java
@@ -25,12 +25,14 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
+import java.util.List;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.holiday.data.HolidayData;
+import org.apache.fineract.organisation.holiday.domain.RescheduleType;
import org.apache.fineract.organisation.holiday.exception.HolidayNotFoundException;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
@@ -58,7 +60,7 @@
public HolidayMapper() {
final StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append("h.id as id, h.name as name, h.description as description, h.from_date as fromDate, h.to_date as toDate, ");
- sqlBuilder.append("h.repayments_rescheduled_to as repaymentsScheduleTO, h.status_enum as statusEnum ");
+ sqlBuilder.append("h.repayments_rescheduled_to as repaymentsScheduleTO, h.rescheduling_type as reschedulingType, h.status_enum as statusEnum ");
sqlBuilder.append("from m_holiday h ");
this.schema = sqlBuilder.toString();
}
@@ -76,9 +78,10 @@
final LocalDate toDate = JdbcSupport.getLocalDate(rs, "toDate");
final LocalDate repaymentsScheduleTO = JdbcSupport.getLocalDate(rs, "repaymentsScheduleTO");
final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+ final Integer reschedulingType = JdbcSupport.getInteger(rs, "reschedulingType");
final EnumOptionData status = HolidayEnumerations.holidayStatusType(statusEnum);
- return new HolidayData(id, name, description, fromDate, toDate, repaymentsScheduleTO, status);
+ return new HolidayData(id, name, description, fromDate, toDate, repaymentsScheduleTO, status, reschedulingType);
}
}
@@ -133,5 +136,14 @@
throw new HolidayNotFoundException(holidayId);
}
}
+
+ @Override
+ public List<EnumOptionData> retrieveRepaymentScheduleUpdationTyeOptions(){
+
+ final List<EnumOptionData> repSchUpdationTypeOptions = Arrays.asList(
+ HolidayEnumerations.rescheduleType(RescheduleType.RESCHEDULETOSPECIFICDATE),
+ HolidayEnumerations.rescheduleType(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE));
+ return repSchUpdationTypeOptions;
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java
index c026ac1..ba729f0 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java
@@ -21,6 +21,7 @@
import java.util.List;
import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.joda.time.LocalDate;
public class HolidayUtil {
@@ -56,4 +57,20 @@
return false;
}
+
+ public static Holiday getApplicableHoliday(final LocalDate repaymentDate, final List<Holiday> holidays) {
+ Holiday referedHoliday = null;
+ for (final Holiday holiday : holidays) {
+ if (!repaymentDate.isBefore(holiday.getFromDateLocalDate()) && !repaymentDate.isAfter(holiday.getToDateLocalDate())) {
+ referedHoliday = holiday;
+ }
+ }
+ return referedHoliday;
+ }
+ public static void updateRepaymentRescheduleDateToWorkingDayIfItIsHoliday(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
+ final Holiday holiday) {
+ if (holiday.getReScheduleType().isRescheduleToSpecificDate()) {
+ adjustedDateDetailsDTO.setChangedScheduleDate(holiday.getRepaymentsRescheduledToLocalDate());
+ }
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java
index 6bce676..6ff9818 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java
@@ -194,9 +194,18 @@
private void validateInputDates(final JsonCommand command) {
final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName);
final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName);
- final LocalDate repaymentsRescheduledTo = command
- .localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
- this.validateInputDates(fromDate, toDate, repaymentsRescheduledTo);
+ Integer reshedulingType = null;
+ if(command.parameterExists(HolidayApiConstants.reschedulingType)){
+ reshedulingType = command.integerValueOfParameterNamed(HolidayApiConstants.reschedulingType);
+ }
+ LocalDate repaymentsRescheduledTo = null;
+ if(reshedulingType != null && reshedulingType.equals(2)){
+ repaymentsRescheduledTo = command
+ .localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
+ }
+ if(repaymentsRescheduledTo != null){
+ this.validateInputDates(fromDate, toDate, repaymentsRescheduledTo);
+ }
}
private void validateInputDates(final LocalDate fromDate, final LocalDate toDate, final LocalDate repaymentsRescheduledTo) {
@@ -208,37 +217,40 @@
throw new HolidayDateException("to.date.cannot.be.before.from.date", defaultUserMessage, fromDate.toString(),
toDate.toString());
}
+ if(repaymentsRescheduledTo != null){
+ if ((repaymentsRescheduledTo.isEqual(fromDate) || repaymentsRescheduledTo.isEqual(toDate)
+ || (repaymentsRescheduledTo.isAfter(fromDate) && repaymentsRescheduledTo.isBefore(toDate)))) {
- if (repaymentsRescheduledTo.isEqual(fromDate) || repaymentsRescheduledTo.isEqual(toDate)
- || (repaymentsRescheduledTo.isAfter(fromDate) && repaymentsRescheduledTo.isBefore(toDate))) {
+ defaultUserMessage = "Repayments rescheduled date should be before from date or after to date.";
+ throw new HolidayDateException("repayments.rescheduled.date.should.be.before.from.date.or.after.to.date", defaultUserMessage,
+ repaymentsRescheduledTo.toString());
+ }
- defaultUserMessage = "Repayments rescheduled date should be before from date or after to date.";
- throw new HolidayDateException("repayments.rescheduled.date.should.be.before.from.date.or.after.to.date", defaultUserMessage,
- repaymentsRescheduledTo.toString());
+ final WorkingDays workingDays = this.daysRepositoryWrapper.findOne();
+ final Boolean isRepaymentOnWorkingDay = WorkingDaysUtil.isWorkingDay(workingDays, repaymentsRescheduledTo);
+
+ if (!isRepaymentOnWorkingDay) {
+ defaultUserMessage = "Repayments rescheduled date should not fall on non working days";
+ throw new HolidayDateException("repayments.rescheduled.date.should.not.fall.on.non.working.day", defaultUserMessage,
+ repaymentsRescheduledTo.toString());
+ }
+
+ // validate repaymentsRescheduledTo date
+ // 1. should be within a 7 days date range.
+ // 2. Alternative date should not be an exist holiday.//TBD
+ // 3. Holiday should not be on an repaymentsRescheduledTo date of
+ // another holiday.//TBD
+
+ // restricting repaymentsRescheduledTo date to be within 7 days range
+ // before or after from date and to date.
+ if (repaymentsRescheduledTo.isBefore(fromDate.minusDays(7)) || repaymentsRescheduledTo.isAfter(toDate.plusDays(7))) {
+ defaultUserMessage = "Repayments Rescheduled to date must be within 7 days before or after from and to dates";
+ throw new HolidayDateException("repayments.rescheduled.to.must.be.within.range", defaultUserMessage, fromDate.toString(),
+ toDate.toString(), repaymentsRescheduledTo.toString());
+ }
}
- final WorkingDays workingDays = this.daysRepositoryWrapper.findOne();
- final Boolean isRepaymentOnWorkingDay = WorkingDaysUtil.isWorkingDay(workingDays, repaymentsRescheduledTo);
- if (!isRepaymentOnWorkingDay) {
- defaultUserMessage = "Repayments rescheduled date should not fall on non working days";
- throw new HolidayDateException("repayments.rescheduled.date.should.not.fall.on.non.working.day", defaultUserMessage,
- repaymentsRescheduledTo.toString());
- }
-
- // validate repaymentsRescheduledTo date
- // 1. should be within a 7 days date range.
- // 2. Alternative date should not be an exist holiday.//TBD
- // 3. Holiday should not be on an repaymentsRescheduledTo date of
- // another holiday.//TBD
-
- // restricting repaymentsRescheduledTo date to be within 7 days range
- // before or after from date and to date.
- if (repaymentsRescheduledTo.isBefore(fromDate.minusDays(7)) || repaymentsRescheduledTo.isAfter(toDate.plusDays(7))) {
- defaultUserMessage = "Repayments Rescheduled to date must be within 7 days before or after from and to dates";
- throw new HolidayDateException("repayments.rescheduled.to.must.be.within.range", defaultUserMessage, fromDate.toString(),
- toDate.toString(), repaymentsRescheduledTo.toString());
- }
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/AdjustedDateDetailsDTO.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/AdjustedDateDetailsDTO.java
new file mode 100644
index 0000000..916c2fd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/AdjustedDateDetailsDTO.java
@@ -0,0 +1,76 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.workingdays.data;
+
+import org.joda.time.LocalDate;
+
+
+public class AdjustedDateDetailsDTO {
+
+ /**
+ * Variable tracks the current schedule date that has been changed
+ */
+ LocalDate changedScheduleDate;
+ /**
+ * Variable tracks If the meeting has been changed , i.e future schedule
+ * also changes along with the current repayments date.
+ */
+ LocalDate changedActualRepaymentDate;
+
+ /**
+ * Variable tracks the next repayment period due date
+ */
+ LocalDate nextRepaymentPeriodDueDate;
+
+ public AdjustedDateDetailsDTO(final LocalDate changedScheduleDate, final LocalDate changedActualRepaymentDate) {
+ this.changedScheduleDate = changedScheduleDate;
+ this.changedActualRepaymentDate = changedActualRepaymentDate;
+ }
+
+ public AdjustedDateDetailsDTO(final LocalDate changedScheduleDate, final LocalDate changedActualRepaymentDate,
+ final LocalDate nextRepaymentPeriodDueDate) {
+ this.changedScheduleDate = changedScheduleDate;
+ this.changedActualRepaymentDate = changedActualRepaymentDate;
+ this.nextRepaymentPeriodDueDate = nextRepaymentPeriodDueDate;
+ }
+
+ public LocalDate getChangedScheduleDate() {
+ return this.changedScheduleDate;
+ }
+
+ public LocalDate getChangedActualRepaymentDate() {
+ return this.changedActualRepaymentDate;
+ }
+
+ public void setChangedScheduleDate(final LocalDate changedScheduleDate) {
+ this.changedScheduleDate = changedScheduleDate;
+ }
+
+ public void setChangedActualRepaymentDate(final LocalDate changedActualRepaymentDate) {
+ this.changedActualRepaymentDate = changedActualRepaymentDate;
+ }
+
+ public LocalDate getNextRepaymentPeriodDueDate() {
+ return this.nextRepaymentPeriodDueDate;
+ }
+
+ public void setNextRepaymentPeriodDueDate(final LocalDate nextRepaymentPeriodDueDate) {
+ this.nextRepaymentPeriodDueDate = nextRepaymentPeriodDueDate;
+ }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/RepaymentRescheduleType.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/RepaymentRescheduleType.java
index 0194cd7..d658f92 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/RepaymentRescheduleType.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/RepaymentRescheduleType.java
@@ -26,7 +26,8 @@
INVALID(0, "RepaymentRescheduleType.invalid"), SAME_DAY(1, "RepaymentRescheduleType.same.day"), MOVE_TO_NEXT_WORKING_DAY(2,
"RepaymentRescheduleType.move.to.next.working.day"), MOVE_TO_NEXT_REPAYMENT_MEETING_DAY(3,
"RepaymentRescheduleType.move.to.next.repayment.meeting.day"), MOVE_TO_PREVIOUS_WORKING_DAY(4,
- "RepaymentRescheduleType.move.to.previous.working.day");
+ "RepaymentRescheduleType.move.to.previous.working.day"),
+ MOVE_TO_NEXT_MEETING_DAY(5, "RepaymentRescheduleType.move.to.next.meeting.day");
private final Integer value;
private final String code;
@@ -43,6 +44,10 @@
public String getCode() {
return this.code;
}
+
+ public boolean isMoveToNextRepaymentDay() {
+ return this.value.equals(RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY.getValue());
+ }
private static final Map<Integer, RepaymentRescheduleType> intToEnumMap = new HashMap<>();
private static int minValue;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysUtil.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysUtil.java
index ac0fc39..605a891 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysUtil.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysUtil.java
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.organisation.workingdays.service;
+import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
@@ -56,4 +57,15 @@
public static boolean isNonWorkingDay(final WorkingDays workingDays, final LocalDate date) {
return !isWorkingDay(workingDays, date);
}
+
+ public static void updateWorkingDayIfRepaymentDateIsNonWorkingDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO, final WorkingDays workingDays) {
+ final LocalDate changedScheduleDate = getOffSetDateIfNonWorkingDay(adjustedDateDetailsDTO.getChangedScheduleDate(),
+ adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate(), workingDays);
+ adjustedDateDetailsDTO.setChangedScheduleDate(changedScheduleDate);
+ }
+
+ public static RepaymentRescheduleType getRepaymentRescheduleType(final WorkingDays workingDays, final LocalDate date) {
+ RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(workingDays.getRepaymentReschedulingType());
+ return rescheduleType;
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java
index 6360380..c7f10cb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java
@@ -700,4 +700,112 @@
}
return monthOnDay;
}
+
+ public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
+ final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays,
+ boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays, boolean applyWorkingDays) {
+ boolean isCalledFirstTime = true;
+ return getNextRepaymentMeetingDate(recurringRule, seedDate, repaymentDate, loanRepaymentInterval, frequency,
+ workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
+ }
+
+ public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
+ final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency,
+ boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
+ boolean isCalledFirstTime = true;
+ final WorkingDays workingDays = null;
+ boolean applyWorkingDays = false;
+ return getNextRepaymentMeetingDate(recurringRule, seedDate, repaymentDate, loanRepaymentInterval, frequency,
+ workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
+ }
+
+ public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
+ final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays,
+ boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays, boolean isCalledFirstTime, boolean applyWorkingDays) {
+
+ final Recur recur = CalendarUtils.getICalRecur(recurringRule);
+ if (recur == null) { return null; }
+ LocalDate tmpDate = repaymentDate;
+
+ final Integer repaymentInterval = getMeetingIntervalFromFrequency(loanRepaymentInterval, frequency, recur);
+ /*
+ * Recurring dates should follow loanRepaymentInterval.
+ *
+ * e.g. The weekly meeting will have interval of 1, if the loan product
+ * with fortnightly frequency will have interval of 2, to generate right
+ * set of meeting dates reset interval same as loan repayment interval.
+ */
+ int meetingInterval = recur.getInterval();
+ if(meetingInterval < 1){
+ meetingInterval = 1;
+ }
+ int rep = repaymentInterval<meetingInterval ? 1: repaymentInterval / meetingInterval ;
+
+ /*
+ * Recurring dates should follow loanRepayment frequency. //e.g. daily
+ * meeting frequency should support all loan products with any type of
+ * frequency. to generate right set of meeting dates reset frequency
+ * same as loan repayment frequency.
+ */
+ if (recur.getFrequency().equals(Recur.DAILY)) {
+ recur.setFrequency(frequency);
+ }
+
+ /**
+ * Below code modified as discussed with Pramod N
+ */
+ LocalDate newRepaymentDate = tmpDate;
+ int newRepayment = rep;
+ while (newRepayment > 0) {
+ newRepaymentDate = getNextRecurringDate(recur, seedDate, newRepaymentDate);
+ newRepayment--;
+ }
+
+ LocalDate nextRepaymentDate = null;
+ if (applyWorkingDays) {
+ if (WorkingDaysUtil.isNonWorkingDay(workingDays, newRepaymentDate)
+ && WorkingDaysUtil.getRepaymentRescheduleType(workingDays, newRepaymentDate).isMoveToNextRepaymentDay()) {
+ newRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, newRepaymentDate.plusDays(1),
+ loanRepaymentInterval, frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime,
+ applyWorkingDays);
+ } else {
+ newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDate, nextRepaymentDate, workingDays);
+ }
+ }
+
+ if(isCalledFirstTime && newRepaymentDate.equals(repaymentDate)){
+ isCalledFirstTime = false;
+ newRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, repaymentDate.plusDays(1), loanRepaymentInterval,
+ frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
+ }
+
+ if (isSkipRepaymentOnFirstDayOfMonth) {
+ final LocalDate newRepaymentDateTemp = adjustRecurringDate(newRepaymentDate, numberOfDays);
+ if (applyWorkingDays) {
+ if (WorkingDaysUtil.isNonWorkingDay(workingDays, newRepaymentDateTemp)
+ && WorkingDaysUtil.getRepaymentRescheduleType(workingDays, newRepaymentDateTemp).isMoveToNextRepaymentDay()) {
+ newRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, newRepaymentDate.plusDays(1),
+ loanRepaymentInterval, frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays,
+ isCalledFirstTime, applyWorkingDays);
+ } else {
+ newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDateTemp, nextRepaymentDate, workingDays);
+ }
+ }
+ }
+ return newRepaymentDate;
+ }
+
+ public static Integer getMeetingIntervalFromFrequency(final Integer loanRepaymentInterval, final String frequency, final Recur recur) {
+ final Integer interval = 4;
+ Integer repaymentInterval = loanRepaymentInterval;
+ /*
+ * check loanRepaymentInterval equal to 1, if repayments frequency is
+ * monthly and meeting frequency is weekly, then generate repayments
+ * schedule as every 4 weeks
+ */
+ if (frequency.equals(Recur.MONTHLY) && recur.getFrequency().equals(Recur.WEEKLY)) {
+ repaymentInterval = loanRepaymentInterval*interval;
+ }
+ return repaymentInterval;
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.java
index e718265..eb2714c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.java
@@ -20,7 +20,13 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
abstract interface ClientRepository extends JpaRepository<Client, Long>, JpaSpecificationExecutor<Client> {
- // no added behaviour
+
+ public static final String FIND_CLIENT_BY_ACCOUNT_NUMBER = "select client from Client client where client.accountNumber = :accountNumber";
+
+ @Query(FIND_CLIENT_BY_ACCOUNT_NUMBER)
+ Client getClientByAccountNumber(@Param("accountNumber") String accountNumber);
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepositoryWrapper.java
index f4231d1..b3e8e9e 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepositoryWrapper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepositoryWrapper.java
@@ -85,5 +85,12 @@
this.context.validateAccessRights(client.getOffice().getHierarchy());
return client;
}
-
+
+ public Client getClientByAccountNumber(String accountNumber){
+ Client client = this.repository.getClientByAccountNumber(accountNumber);
+ if(client==null){
+ throw new ClientNotFoundException(accountNumber);
+ }
+ return client;
+ }
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNotFoundException.java
index 7a6f034..1c182d4 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNotFoundException.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNotFoundException.java
@@ -28,4 +28,12 @@
public ClientNotFoundException(final Long id) {
super("error.msg.client.id.invalid", "Client with identifier " + id + " does not exist", id);
}
+
+ public ClientNotFoundException() {
+ super("error.msg.client.not.found.with.basic.details", "Client not found with basic details.");
+ }
+
+ public ClientNotFoundException(String accountNumber) {
+ super("error.msg.client.not.found.with.account.number", "Client not found with account number "+accountNumber+".");
+ }
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
index 7751909..b5e8825 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
@@ -249,7 +249,7 @@
if (displayName != null) {
//extraCriteria += " and concat(ifnull(c.firstname, ''), if(c.firstname > '',' ', '') , ifnull(c.lastname, '')) like "
- paramList.add(ApiParameterHelper.sqlEncodeString(ApiParameterHelper.sqlEncodeString("%" + displayName + "%")));
+ paramList.add("%" + displayName + "%");
extraCriteria += " and c.display_name like ? ";
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java
index 97ab973..939a0ac 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java
@@ -35,15 +35,17 @@
@SuppressWarnings("unused")
private final String loanChargeId;
private final BigDecimal chargeAmount;
+ private final BigDecimal waivedChargeAmount;
public DisbursementData(Long id, final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate,
- final BigDecimal principalDisbursed, final String loanChargeId, BigDecimal chargeAmount) {
+ final BigDecimal principalDisbursed, final String loanChargeId, BigDecimal chargeAmount, BigDecimal waivedChargeAmount) {
this.id = id;
this.expectedDisbursementDate = expectedDisbursementDate;
this.actualDisbursementDate = actualDisbursementDate;
this.principal = principalDisbursed;
this.loanChargeId = loanChargeId;
this.chargeAmount = chargeAmount;
+ this.waivedChargeAmount = waivedChargeAmount;
}
public LocalDate disbursementDate() {
@@ -83,4 +85,12 @@
return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToAndInclusive);
}
+
+ public BigDecimal getWaivedChargeAmount() {
+ if(this.waivedChargeAmount == null){
+ return BigDecimal.ZERO;
+ }
+ return this.waivedChargeAmount;
+ }
+
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java
index 446ee3c..12bb038 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java
@@ -75,6 +75,7 @@
}
public DisbursementData disbursementData() {
- return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal, null, null);
+ BigDecimal waivedChargeAmount = null;
+ return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal, null, null, waivedChargeAmount);
}
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index b71205e..480b081 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -5576,9 +5576,9 @@
if (loanDisbursementDetails.actualDisbursementDate() != null) {
actualDisbursementDate = new LocalDate(loanDisbursementDetails.actualDisbursementDate());
}
-
+ BigDecimal waivedChargeAmount = null;
disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate, actualDisbursementDate,
- loanDisbursementDetails.principal(), null, null));
+ loanDisbursementDetails.principal(), null, null, waivedChargeAmount));
}
return disbursementData;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java
index ef70a18..d19b60f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java
@@ -119,7 +119,8 @@
if (this.actualDisbursementDate != null) {
actualDisburseDate = new LocalDate(this.actualDisbursementDate);
}
- return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal, null, null);
+ BigDecimal waivedChargeAmount = null;
+ return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal, null, null, waivedChargeAmount);
}
public void updateActualDisbursementDate(Date actualDisbursementDate) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
index cb42bf5..19a203a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
@@ -34,6 +34,7 @@
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
@@ -121,7 +122,7 @@
boolean isFirstRepayment = true;
LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(
- loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
+ loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment);
final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate,
loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms);
@@ -179,10 +180,12 @@
while (!scheduleParams.getOutstandingBalance().isZero() || !scheduleParams.getDisburseDetailMap().isEmpty()) {
LocalDate previousRepaymentDate = scheduleParams.getActualRepaymentDate();
scheduleParams.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(
- scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO));
+ scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment));
+ AdjustedDateDetailsDTO adjustedDateDetailsDTO = this.scheduledDateGenerator.adjustRepaymentDate(
+ scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO);
+ scheduleParams.setActualRepaymentDate(adjustedDateDetailsDTO.getChangedActualRepaymentDate());
isFirstRepayment = false;
- LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(scheduleParams.getActualRepaymentDate(),
- loanApplicationTerms, holidayDetailDTO);
+ LocalDate scheduledDueDate = adjustedDateDetailsDTO.getChangedScheduleDate();
// calculated interest start date for the period
LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
@@ -1320,7 +1323,7 @@
do {
params.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(params.getActualRepaymentDate(),
- loanApplicationTerms, isFirstRepayment, holidayDetailDTO));
+ loanApplicationTerms, isFirstRepayment));
if (params.getActualRepaymentDate().isAfter(currentDate)) {
params.setActualRepaymentDate(currentDate);
}
@@ -2172,22 +2175,18 @@
ArrayList<LoanTermVariationsData> dueDateVariationsDataList = new ArrayList<>();
- // check for date changes
-
+ // check for date changes
+
do {
actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate,
- loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
+ loanApplicationTerms, isFirstRepayment);
isFirstRepayment = false;
lastInstallmentDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms,
- holidayDetailDTO);
+ holidayDetailDTO).getChangedScheduleDate();
while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) {
LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
if (!variation.isSpecificToInstallment()) {
actualRepaymentDate = variation.getDateValue();
- /*if (!isDueDateChangeApplied) {
- previousRepaymentDate = actualRepaymentDate;
- isDueDateChangeApplied = true;
- }*/
lastInstallmentDate = actualRepaymentDate;
}
dueDateVariationsDataList.add(variation);
@@ -2195,18 +2194,7 @@
loanTermVariationParams = applyExceptionLoanTermVariations(loanApplicationTerms, lastInstallmentDate,
exceptionDataListIterator, instalmentNumber, totalCumulativePrincipal, totalCumulativeInterest, mc);
} while (loanTermVariationParams != null && loanTermVariationParams.isSkipPeriod());
-
- /*if (!lastInstallmentDate.isBefore(rescheduleFrom)) {
- actualRepaymentDate = previousRepaymentDate;
- if(isDueDateChangeApplied){
- int numberOfDateChangesApplied = dueDateVariationsDataList.size();
- while(numberOfDateChangesApplied >0 ){
- loanApplicationTerms.getLoanTermVariations().previousDueDateVariation();
- numberOfDateChangesApplied--;
- }
- }
- break;
- }*/
+
periodNumber++;
for (LoanTermVariationsData dueDateVariation : dueDateVariationsDataList) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
index 93a58ce..5974d3f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
@@ -20,6 +20,7 @@
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.service.HolidayUtil;
+import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
@@ -45,25 +46,26 @@
LocalDate lastRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate();
boolean isFirstRepayment = true;
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
- lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
+ lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment);
isFirstRepayment = false;
}
- lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO);
+ lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
return lastRepaymentDate;
}
@Override
public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, final LoanApplicationTerms loanApplicationTerms,
- boolean isFirstRepayment, final HolidayDetailDTO holidayDetailDTO) {
+ boolean isFirstRepayment) {
final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate();
LocalDate dueRepaymentPeriodDate = null;
- Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
if (isFirstRepayment && firstRepaymentPeriodDate != null) {
dueRepaymentPeriodDate = firstRepaymentPeriodDate;
} else {
+ LocalDate seedDate = null;
+ String reccuringString = null;
+ Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
- loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, null,
- null);
+ loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate);
dueRepaymentPeriodDate = CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
loanApplicationTerms.getRepaymentPeriodFrequencyType());
if (currentCalendar != null) {
@@ -79,8 +81,6 @@
}
// get the start date from the calendar history
- LocalDate seedDate = null;
- String reccuringString = null;
if (calendarHistory == null) {
seedDate = currentCalendar.getStartDateLocalDate();
reccuringString = currentCalendar.getRecurrence();
@@ -89,80 +89,130 @@
reccuringString = calendarHistory.getRecurrence();
}
- dueRepaymentPeriodDate = CalendarUtils.getNewRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate.plusDays(1),
+ dueRepaymentPeriodDate = CalendarUtils.getNextRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate,
loanApplicationTerms.getRepaymentEvery(),
CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()),
- holidayDetailDTO.getWorkingDays(), loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(),
+ loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(),
loanApplicationTerms.getNumberOfdays());
}
}
- if (currentCalendar == null && holidayDetailDTO.getWorkingDays().getExtendTermForRepaymentsOnHolidays()) {
- boolean repaymentDateIsOnHoliday = false;
- // compile the list of holidays into Intervals to see if this date lands on a holiday
- final List<Interval> holidayInterval = new ArrayList<>();
- for (Holiday holiday : holidayDetailDTO.getHolidays()) {
- holidayInterval.add(new Interval(
- holiday.getFromDateLocalDate().toDateTimeAtStartOfDay(),
- holiday.getToDateLocalDate().toDateTime(LocalTime.parse("23:59:59"))
- ));
- }
- while (intervalListContainsDate(holidayInterval, dueRepaymentPeriodDate)) {
- LocalDate previousDate = dueRepaymentPeriodDate;
- dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
- loanApplicationTerms.getRepaymentEvery(), dueRepaymentPeriodDate, loanApplicationTerms.getNthDay(),
- loanApplicationTerms.getWeekDayType());
- }
- }
+
return dueRepaymentPeriodDate;
}
- private boolean intervalListContainsDate(List<Interval> intervalList, LocalDate date) {
- for (Interval interval : intervalList) {
- if (interval.contains(date.toDateTime(LocalTime.parse("11:59:59")))) {
- return true;
- }
- }
- return false;
- }
+
@Override
- public LocalDate adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate, final LoanApplicationTerms loanApplicationTerms,
- final HolidayDetailDTO holidayDetailDTO) {
+ public AdjustedDateDetailsDTO adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate,
+ final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
+ final LocalDate adjustedDate = dueRepaymentPeriodDate;
+ return getAdjustedDateDetailsDTO(dueRepaymentPeriodDate, loanApplicationTerms, holidayDetailDTO, adjustedDate);
+ }
- LocalDate adjustedDate = dueRepaymentPeriodDate;
+ private AdjustedDateDetailsDTO getAdjustedDateDetailsDTO(final LocalDate dueRepaymentPeriodDate,
+ final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final LocalDate adjustedDate) {
+ final boolean isFirstRepayment = false;
+ final LocalDate nextRepaymentPeriodDueDate = generateNextRepaymentDate(adjustedDate, loanApplicationTerms, isFirstRepayment);
+ final AdjustedDateDetailsDTO newAdjustedDateDetailsDTO = new AdjustedDateDetailsDTO(adjustedDate, dueRepaymentPeriodDate,
+ nextRepaymentPeriodDueDate);
+ return recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(newAdjustedDateDetailsDTO,
+ loanApplicationTerms, holidayDetailDTO, isFirstRepayment);
+ }
- LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
- loanApplicationTerms.getRepaymentEvery(), adjustedDate, loanApplicationTerms.getNthDay(),
- loanApplicationTerms.getWeekDayType());
-
- final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(holidayDetailDTO.getWorkingDays()
- .getRepaymentReschedulingType());
+ /**
+ * Recursively checking non working days and holidays and working days
+ * exemption to generate next repayment period date Base on the
+ * configuration
+ *
+ * @param adjustedDateDetailsDTO
+ * @param loanApplicationTerms
+ * @param holidayDetailDTO
+ * @param nextRepaymentPeriodDueDate
+ * @param rescheduleType
+ * @param isFirstRepayment
+ * @return
+ */
+ private AdjustedDateDetailsDTO recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(
+ final AdjustedDateDetailsDTO adjustedDateDetailsDTO, final LoanApplicationTerms loanApplicationTerms,
+ final HolidayDetailDTO holidayDetailDTO, final boolean isFirstRepayment) {
+
+ checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms,
+ isFirstRepayment);
+
+ checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms,
+ isFirstRepayment);
/**
- * Fix for https://mifosforge.jira.com/browse/MIFOSX-1357
+ * Check Changed Schedule Date is holiday or is not a working day Then
+ * re-call this method to get the non holiday and working day
*/
- // recursively check for the next working meeting day.
- while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), nextDueRepaymentPeriodDate)
- && rescheduleType == RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY) {
-
- nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
- loanApplicationTerms.getRepaymentEvery(), nextDueRepaymentPeriodDate, loanApplicationTerms.getNthDay(),
- loanApplicationTerms.getWeekDayType());
- nextDueRepaymentPeriodDate = CalendarUtils.adjustDate(nextDueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
- loanApplicationTerms.getRepaymentPeriodFrequencyType());
-
+ if ((holidayDetailDTO.isHolidayEnabled() && HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(),
+ holidayDetailDTO.getHolidays()) != null)
+ || WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate())) {
+ recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(adjustedDateDetailsDTO,
+ loanApplicationTerms, holidayDetailDTO, isFirstRepayment);
}
- adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate,
- holidayDetailDTO.getWorkingDays());
+ return adjustedDateDetailsDTO;
+ }
+ /**
+ * This method to check and update the working day if repayment date is
+ * holiday
+ *
+ * @param adjustedDateDetailsDTO
+ * @param holidayDetailDTO
+ * @param loanApplicationTerms
+ * @param isFirstRepayment
+ */
+ private void checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
+ final HolidayDetailDTO holidayDetailDTO, final LoanApplicationTerms loanApplicationTerms, final boolean isFirstRepayment) {
if (holidayDetailDTO.isHolidayEnabled()) {
- adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidayDetailDTO.getHolidays());
+ Holiday applicableHolidayForNewAdjustedDate = null;
+ while ((applicableHolidayForNewAdjustedDate = HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(),
+ holidayDetailDTO.getHolidays())) != null) {
+ if (applicableHolidayForNewAdjustedDate.getReScheduleType().isResheduleToNextRepaymentDate()) {
+ LocalDate nextRepaymentPeriodDueDate = adjustedDateDetailsDTO.getChangedActualRepaymentDate();
+ while (!nextRepaymentPeriodDueDate.isAfter(adjustedDateDetailsDTO.getChangedScheduleDate())) {
+ nextRepaymentPeriodDueDate = generateNextRepaymentDate(nextRepaymentPeriodDueDate, loanApplicationTerms,
+ isFirstRepayment);
+ }
+ adjustedDateDetailsDTO.setChangedScheduleDate(nextRepaymentPeriodDueDate);
+ adjustedDateDetailsDTO.setNextRepaymentPeriodDueDate(nextRepaymentPeriodDueDate);
+ adjustedDateDetailsDTO.setChangedActualRepaymentDate(adjustedDateDetailsDTO.getChangedScheduleDate());
+ } else {
+ HolidayUtil.updateRepaymentRescheduleDateToWorkingDayIfItIsHoliday(adjustedDateDetailsDTO,
+ applicableHolidayForNewAdjustedDate);
+ }
+ }
}
+ }
- return adjustedDate;
+ /**
+ * This method to check and update the working day if repayment date is non
+ * working day
+ *
+ * @param adjustedDateDetailsDTO
+ * @param holidayDetailDTO
+ * @param isFirstRepayment
+ * @param loanApplicationTerms
+ */
+ private void checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
+ final HolidayDetailDTO holidayDetailDTO, final LoanApplicationTerms loanApplicationTerms, final boolean isFirstRepayment) {
+ while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate())) {
+ if (WorkingDaysUtil.getRepaymentRescheduleType(holidayDetailDTO.getWorkingDays(),
+ adjustedDateDetailsDTO.getChangedScheduleDate()).isMoveToNextRepaymentDay()) {
+ while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(),
+ adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate())
+ || adjustedDateDetailsDTO.getChangedScheduleDate().isAfter(adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate())) {
+ final LocalDate nextRepaymentPeriodDueDate = generateNextRepaymentDate(
+ adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate(), loanApplicationTerms, isFirstRepayment);
+ adjustedDateDetailsDTO.setNextRepaymentPeriodDueDate(nextRepaymentPeriodDueDate);
+ }
+ }
+ WorkingDaysUtil.updateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO.getWorkingDays());
+ }
}
@Override
- public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate,
- Integer nthDay, DayOfWeekType dayOfWeek) {
+ public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate) {
LocalDate dueRepaymentPeriodDate = startDate;
switch (frequency) {
case DAYS:
@@ -173,9 +223,6 @@
break;
case MONTHS:
dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
- if (!(nthDay == null || dayOfWeek == null || dayOfWeek == DayOfWeekType.INVALID)) {
- dueRepaymentPeriodDate = adjustToNthWeekDay(dueRepaymentPeriodDate, nthDay, dayOfWeek.getValue());
- }
break;
case YEARS:
dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
@@ -282,10 +329,10 @@
LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate();
boolean isFirstRepayment = true;
while (!generatedDate.isAfter(lastRepaymentDate)) {
- generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
+ generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment);
isFirstRepayment = false;
}
- generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO);
+ generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
return generatedDate;
}
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index 8db5aff..d5f08cb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -183,7 +183,7 @@
private Money adjustPrincipalForFlatLoans;
- private final LocalDate seedDate;
+ private LocalDate seedDate;
private final CalendarHistoryDataWrapper calendarHistoryDataWrapper;
@@ -1742,5 +1742,9 @@
public void updateTotalInterestAccounted(Money totalInterestAccounted){
this.totalInterestAccounted = totalInterestAccounted;
}
+
+ public void setSeedDate(LocalDate seedDate) {
+ this.seedDate = seedDate;
+ }
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
index c82301d..2119472 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
+import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
@@ -32,14 +33,12 @@
final LocalDate firstRepaymentDate, final Calendar loanCalendar, final HolidayDetailDTO holidayDetailDTO,
final LoanApplicationTerms loanApplicationTerms);
- LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment,
+ LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment);
+
+ AdjustedDateDetailsDTO adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms,
final HolidayDetailDTO holidayDetailDTO);
- LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms,
- final HolidayDetailDTO holidayDetailDTO);
-
- LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, Integer nthDay,
- DayOfWeekType dayOfWeek);
+ LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate);
Boolean isDateFallsInSchedule(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, LocalDate date);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
index a335e9c..095da8a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
@@ -515,7 +515,8 @@
&& StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
}
- disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal, null, null));
+ BigDecimal waivedChargeAmount = null;
+ disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal, null, null, waivedChargeAmount));
i++;
} while (i < disbursementDataArray.size());
}
@@ -536,7 +537,7 @@
public void validateDisbursementDateWithMeetingDates(final LocalDate expectedDisbursementDate, final Calendar calendar, Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {
// disbursement date should fall on a meeting date
- if (!calendar.isValidRecurringDate(expectedDisbursementDate, isSkipRepaymentOnFirstMonth, numberOfDays)) {
+ if (calendar != null && !calendar.isValidRecurringDate(expectedDisbursementDate, isSkipRepaymentOnFirstMonth, numberOfDays)) {
final String errorMessage = "Expected disbursement date '" + expectedDisbursementDate + "' do not fall on a meeting date";
throw new LoanApplicationDateException("disbursement.date.do.not.match.meeting.date", errorMessage, expectedDisbursementDate);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java
index e5ab38d..ef64ec3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java
@@ -114,10 +114,10 @@
for (LoanTermVariationsData loanTermVariation : loanApplicationTerms.getLoanTermVariations().getDueDateVariation()) {
if (rescheduleFromDate.isBefore(loanTermVariation.getTermApplicableFrom())) {
LocalDate applicableDate = this.scheduledDateGenerator.generateNextRepaymentDate(rescheduleFromDate, loanApplicationTerms,
- false, loanApplicationTerms.getHolidayDetailDTO());
+ false);
if (loanTermVariation.getTermApplicableFrom().equals(applicableDate)) {
LocalDate adjustedDate = this.scheduledDateGenerator.generateNextRepaymentDate(adjustedApplicableDate,
- loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
+ loanApplicationTerms, false);
loanTermVariation.setApplicableFromDate(adjustedDate);
loanTermVariationsData.add(loanTermVariation);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
index 977e7ba..ebffa27 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
@@ -410,9 +410,9 @@
} else if (!activeLoanTermVariation.fetchTermApplicaDate().isBefore(fromScheduleDate)) {
while (currentScheduleDate.isBefore(activeLoanTermVariation.fetchTermApplicaDate())) {
currentScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(currentScheduleDate,
- loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
+ loanApplicationTerms, false);
modifiedScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(modifiedScheduleDate,
- loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
+ loanApplicationTerms, false);
changeMap.put(currentScheduleDate, modifiedScheduleDate);
}
if (changeMap.containsKey(activeLoanTermVariation.fetchTermApplicaDate())) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
index 9fdde8f..cf7b641 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
@@ -51,6 +51,7 @@
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -266,7 +267,7 @@
private void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData, BigDecimal amount, BigDecimal interestportion,
BigDecimal totalAccInterest, BigDecimal feeportion, BigDecimal totalAccFee, BigDecimal penaltyportion,
- BigDecimal totalAccPenalty, final LocalDate accruedTill) throws Exception {
+ BigDecimal totalAccPenalty, final LocalDate accruedTill) throws DataAccessException {
String transactionSql = "INSERT INTO m_loan_transaction (loan_id,office_id,is_reversed,transaction_type_enum,transaction_date,amount,interest_portion_derived,"
+ "fee_charges_portion_derived,penalty_charges_portion_derived, submitted_on_date) VALUES (?, ?, 0, ?, ?, ?, ?, ?, ?, ?)";
this.jdbcTemplate.update(transactionSql, scheduleAccrualData.getLoanId(), scheduleAccrualData.getOfficeId(),
@@ -485,7 +486,7 @@
@Override
@Transactional
- public void addIncomeAndAccrualTransactions(Long loanId) throws Exception {
+ public void addIncomeAndAccrualTransactions(Long loanId) throws LoanNotFoundException {
if (loanId != null) {
Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
if (loan == null) { throw new LoanNotFoundException(loanId); }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
index 8757e01..3b14a92 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
@@ -1086,7 +1086,11 @@
if (loan.isSyncDisbursementWithMeeting() && (loan.isGroupLoan() || loan.isJLGLoan())) {
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
- final Calendar calendar = calendarInstance.getCalendar();
+ Calendar calendar = null;
+ if (calendarInstance != null) {
+ calendar = calendarInstance.getCalendar();
+ }
+ // final Calendar calendar = calendarInstance.getCalendar();
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if (isSkipRepaymentOnFirstMonthEnabled) {
isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), calendar);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 19ea4ae..ab6c310 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -62,7 +62,6 @@
import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService;
import org.apache.fineract.portfolio.charge.data.ChargeData;
-import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
import org.apache.fineract.portfolio.client.data.ClientData;
@@ -93,8 +92,6 @@
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
@@ -1056,7 +1053,10 @@
@Override
public LoanScheduleData extractData(final ResultSet rs) throws SQLException, DataAccessException {
-
+ BigDecimal waivedChargeAmount = BigDecimal.ZERO;
+ for (DisbursementData disbursementDetail : disbursementData) {
+ waivedChargeAmount = waivedChargeAmount.add(disbursementDetail.getWaivedChargeAmount());
+ }
final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(
this.disbursement.disbursementDate(), this.disbursement.amount(), this.totalFeeChargesDueAtDisbursement,
this.disbursement.isDisbursed());
@@ -1095,9 +1095,9 @@
Money totalOutstanding = Money.zero(monCurrency);
// update totals with details of fees charged during disbursement
- totalFeeChargesCharged = totalFeeChargesCharged.plus(disbursementPeriod.feeChargesDue());
- totalRepaymentExpected = totalRepaymentExpected.plus(disbursementPeriod.feeChargesDue());
- totalRepayment = totalRepayment.plus(disbursementPeriod.feeChargesPaid());
+ totalFeeChargesCharged = totalFeeChargesCharged.plus(disbursementPeriod.feeChargesDue().subtract(waivedChargeAmount));
+ totalRepaymentExpected = totalRepaymentExpected.plus(disbursementPeriod.feeChargesDue()).minus(waivedChargeAmount);
+ totalRepayment = totalRepayment.plus(disbursementPeriod.feeChargesPaid()).minus(waivedChargeAmount);
totalOutstanding = totalOutstanding.plus(disbursementPeriod.feeChargesDue()).minus(disbursementPeriod.feeChargesPaid());
Integer loanTermInDays = Integer.valueOf(0);
@@ -1121,7 +1121,7 @@
disbursementChargeAmount, data.isDisbursed());
} else {
periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(data.disbursementDate(), data.amount(),
- disbursementChargeAmount.add(data.getChargeAmount()), data.isDisbursed());
+ disbursementChargeAmount.add(data.getChargeAmount()).subtract(waivedChargeAmount), data.isDisbursed());
}
if (periodData != null) {
periods.add(periodData);
@@ -1543,7 +1543,7 @@
final BigDecimal waivedAmount = rs.getBigDecimal("waivedAmount");
if (chargeAmount != null && waivedAmount != null) chargeAmount = chargeAmount.subtract(waivedAmount);
final DisbursementData disbursementData = new DisbursementData(id, expectedDisbursementdate, actualDisbursementdate, principal,
- loanChargeId, chargeAmount);
+ loanChargeId, chargeAmount, waivedAmount);
return disbursementData;
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 98f86fc..5744b67 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -2421,7 +2421,7 @@
while (!startDate.isAfter(DateUtils.getLocalDateOfTenant())) {
scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue()));
LocalDate scheduleDate = scheduledDateGenerator.getRepaymentPeriodDate(PeriodFrequencyType.fromInt(feeFrequency),
- chargeDefinition.feeInterval(), startDate, null, null);
+ chargeDefinition.feeInterval(), startDate);
startDate = scheduleDate;
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
index c886128..a84a2b3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
@@ -151,6 +151,7 @@
public static final String runningBalanceParamName = "runningBalance";
public static final String reversedParamName = "reversed";
public static final String dateParamName = "date";
+ public static final String accountIdParamName = "accountId";
// recurring deposits update parameters
public static final String effectiveDateParamName = "effectiveDate";
@@ -199,9 +200,6 @@
// template
public static final String chartTemplate = "chartTemplate";
- // allowed column names for sorting the query result
- public final static Set<String> supportedOrderByValues = new HashSet<>(Arrays.asList("id", "accountNumbr", "officeId", "officeName"));
-
/**
* Deposit Product Parameters
*/
@@ -244,7 +242,7 @@
adjustAdvanceTowardsFuturePaymentsParamName, recurringFrequencyTypeParamName, recurringFrequencyParamName,
isCalendarInheritedParamName));
- public static final Set<String> DEPOSIT_PRECLOSURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
+ private static final Set<String> DEPOSIT_PRECLOSURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(preMatureCloseOnDateParamName));
public static final Set<String> FIXED_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = fixedDepositProductRequestData();
@@ -293,13 +291,15 @@
* Depost Account parameters
*/
- public static final Set<String> DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
- dateFormatParamName, monthDayFormatParamName, accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName,
- productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName, nominalAnnualInterestRateParamName,
- interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName,
- interestCalculationDaysInYearTypeParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
- chargesParamName, chartsParamName, depositAmountParamName, depositPeriodParamName, depositPeriodFrequencyIdParamName,
- savingsAccounts, expectedFirstDepositOnDateParamName, SavingsApiConstants.withHoldTaxParamName));
+ private static final Set<String> DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(
+ Arrays.asList(localeParamName, dateFormatParamName, monthDayFormatParamName, accountNoParamName,
+ externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName,
+ fieldOfficerIdParamName, submittedOnDateParamName, nominalAnnualInterestRateParamName,
+ interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName,
+ interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
+ lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, chargesParamName,
+ chartsParamName, depositAmountParamName, depositPeriodParamName, depositPeriodFrequencyIdParamName,
+ savingsAccounts, expectedFirstDepositOnDateParamName, SavingsApiConstants.withHoldTaxParamName));
public static final Set<String> FIXED_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = fixedDepositAccountRequestData();
public static final Set<String> FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = fixedDepositAccountResponseData();
@@ -345,27 +345,27 @@
return recurringDepositResponseData;
}
- public static final Set<String> FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
- "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName, runningBalanceParamName,
- reversedParamName));
+ private static final Set<String> RECURRING_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
+ Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName,
+ paymentDetailDataParamName, runningBalanceParamName, reversedParamName));
- public static final Set<String> RECURRING_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
- "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName, runningBalanceParamName,
- reversedParamName));
-
- public static final Set<String> SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+ private static final Set<String> SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList
+ (localeParamName,
dateFormatParamName, activatedOnDateParamName));
- public static final Set<String> SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(chargeIdParamName,
+ private static final Set<String> SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList
+ (chargeIdParamName,
savingsAccountChargeIdParamName, chargeNameParamName, penaltyParamName, chargeTimeTypeParamName, dueAsOfDateParamName,
chargeCalculationTypeParamName, percentageParamName, amountPercentageAppliedToParamName, currencyParamName,
amountWaivedParamName, amountWrittenOffParamName, amountOutstandingParamName, amountOrPercentageParamName, amountParamName,
amountPaidParamName, chargeOptionsParamName));
- public static final Set<String> SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(chargeIdParamName,
+ private static final Set<String> SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList
+ (chargeIdParamName,
amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, feeOnMonthDayParamName, monthDayFormatParamName,
feeIntervalParamName));
- public static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+ private static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays
+ .asList(
amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName));
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java
index 1b8aa53..1140c4d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java
@@ -26,7 +26,7 @@
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
-public class SavingsApiConstants {
+public class SavingsApiConstants {
public static final String SAVINGS_PRODUCT_RESOURCE_NAME = "savingsproduct";
public static final String SAVINGS_ACCOUNT_RESOURCE_NAME = "savingsaccount";
@@ -133,6 +133,7 @@
public static final String minBalanceForInterestCalculationParamName = "minBalanceForInterestCalculation";
public static final String withdrawBalanceParamName = "withdrawBalance";
public static final String onHoldFundsParamName = "onHoldFunds";
+ public static final String savingsAmountOnHold = "savingsAmountOnHold";
public static final String withHoldTaxParamName = "withHoldTax";
public static final String taxGroupIdParamName = "taxGroupId";
@@ -186,7 +187,4 @@
public static final String datatables = "datatables";
public static final String ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE = "not.in.active.state";
-
- public static final Set<String> SAVINGS_ACCOUNT_HOLD_AMOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(
- Arrays.asList(transactionDateParamName, dateFormatParamName, localeParamName, transactionAmountParamName));
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java
index fadf019..d0785f9 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java
@@ -18,7 +18,10 @@
*/
package org.apache.fineract.portfolio.savings.api;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@@ -62,7 +65,13 @@
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
- private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+ private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+ private static final Set<String> FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
+ Arrays.asList(DepositsApiConstants.idParamName, DepositsApiConstants.accountIdParamName,
+ DepositsApiConstants.accountNoParamName, DepositsApiConstants.currencyParamName,
+ DepositsApiConstants.amountParamName, DepositsApiConstants.dateParamName,
+ DepositsApiConstants.paymentDetailDataParamName, DepositsApiConstants.runningBalanceParamName,
+ DepositsApiConstants.reversedParamName));
@Autowired
public FixedDepositAccountTransactionsApiResource(final PlatformSecurityContext context,
@@ -120,8 +129,8 @@
transactionData = SavingsAccountTransactionData.templateOnTop(transactionData, paymentTypeOptions);
}
- return this.toApiJsonSerializer.serialize(settings, transactionData,
- DepositsApiConstants.FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS);
+ return this.toApiJsonSerializer.serialize(settings, transactionData,
+ FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS);
}
@POST
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java
index 24353c7..089dc2b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java
@@ -18,7 +18,10 @@
*/
package org.apache.fineract.portfolio.savings.api;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@@ -65,6 +68,12 @@
private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
private final DepositAccountReadPlatformService depositAccountReadPlatformService;
private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+ private static final Set<String> FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
+ Arrays.asList(DepositsApiConstants.idParamName, DepositsApiConstants.accountIdParamName,
+ DepositsApiConstants.accountNoParamName, DepositsApiConstants.currencyParamName,
+ DepositsApiConstants.amountParamName, DepositsApiConstants.dateParamName,
+ DepositsApiConstants.paymentDetailDataParamName, DepositsApiConstants.runningBalanceParamName,
+ DepositsApiConstants.reversedParamName));
@Autowired
public RecurringDepositAccountTransactionsApiResource(final PlatformSecurityContext context,
@@ -141,8 +150,8 @@
transactionData = SavingsAccountTransactionData.templateOnTop(transactionData, paymentTypeOptions);
}
- return this.toApiJsonSerializer.serialize(settings, transactionData,
- DepositsApiConstants.FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS);
+ return this.toApiJsonSerializer.serialize(settings, transactionData,
+ FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS);
}
@POST
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java
index 70ac7b4..36d319a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java
@@ -65,7 +65,7 @@
"interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions",
"lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee", "annualFee",
onHoldFundsParamName, nominalAnnualInterestRateOverdraftParamName,
- minOverdraftForInterestCalculationParamName, datatables));
+ minOverdraftForInterestCalculationParamName, datatables, savingsAmountOnHold));
protected static final Set<String> SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName,
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java
index d06bbe9..32a0630 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java
@@ -76,6 +76,7 @@
private final Integer daysToInactive;
private final Integer daysToDormancy;
private final Integer daysToEscheat;
+ private final BigDecimal savingsAmountOnHold;
// associations
private final SavingsAccountSummaryData summary;
@@ -118,7 +119,7 @@
final BigDecimal onHoldFunds, final BigDecimal nominalAnnualInterestRateOverdraft,
final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup,
final LocalDate lastActiveTransactionDate, final boolean isDormancyTrackingActive, final Integer daysToInactive,
- final Integer daysToDormancy, final Integer daysToEscheat) {
+ final Integer daysToDormancy, final Integer daysToEscheat, final BigDecimal savingsAmountOnHold) {
final Collection<SavingsProductData> productOptions = null;
final Collection<StaffData> fieldOfficerOptions = null;
@@ -142,7 +143,7 @@
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit,
minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup,
- lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
+ lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold);
}
public static SavingsAccountData lookup(final Long accountId, final String accountNo, final EnumOptionData depositType) {
@@ -204,6 +205,7 @@
final Integer daysToInactive = null;
final Integer daysToDormancy = null;
final Integer daysToEscheat = null;
+ final BigDecimal savingsAmountOnHold = null;
return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate,
@@ -214,7 +216,7 @@
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit,
minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup,
- lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
+ lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold);
}
public static SavingsAccountData lookupWithProductDetails(final Long accountId, final String accountNo,
@@ -274,6 +276,7 @@
final Integer daysToInactive = null;
final Integer daysToDormancy = null;
final Integer daysToEscheat = null;
+ final BigDecimal savingsAmountOnHold = null;
return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate,
@@ -284,7 +287,7 @@
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit,
minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup,
- lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
+ lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold);
}
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template,
@@ -320,7 +323,7 @@
account.minBalanceForInterestCalculation, account.onHoldFunds, account.nominalAnnualInterestRateOverdraft,
account.minOverdraftForInterestCalculation, account.withHoldTax, account.taxGroup,
account.lastActiveTransactionDate, account.isDormancyTrackingActive, account.daysToInactive,
- account.daysToDormancy, account.daysToEscheat);
+ account.daysToDormancy, account.daysToEscheat, account.savingsAmountOnHold);
}
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account,
@@ -345,7 +348,7 @@
account.overdraftLimit, account.minRequiredBalance, account.enforceMinRequiredBalance,
account.minBalanceForInterestCalculation, account.onHoldFunds, account.nominalAnnualInterestRateOverdraft,
account.minOverdraftForInterestCalculation, account.withHoldTax, account.taxGroup, account.lastActiveTransactionDate,
- account.isDormancyTrackingActive, account.daysToInactive, account.daysToDormancy, account.daysToEscheat);
+ account.isDormancyTrackingActive, account.daysToInactive, account.daysToDormancy, account.daysToEscheat, account.savingsAmountOnHold);
}
public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId,
@@ -407,6 +410,7 @@
final Integer daysToInactive = null;
final Integer daysToDormancy = null;
final Integer daysToEscheat = null;
+ final BigDecimal savingsAmountOnHold = null;
return new SavingsAccountData(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate,
@@ -417,7 +421,7 @@
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit,
minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup,
- lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
+ lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold);
}
private SavingsAccountData(final Long id, final String accountNo, final EnumOptionData depositType, final String externalId,
@@ -441,7 +445,7 @@
final BigDecimal onHoldFunds, final BigDecimal nominalAnnualInterestRateOverdraft,
final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup,
final LocalDate lastActiveTransactionDate, final boolean isDormancyTrackingActive, final Integer daysToInactive,
- final Integer daysToDormancy, final Integer daysToEscheat) {
+ final Integer daysToDormancy, final Integer daysToEscheat, final BigDecimal savingsAmountOnHold) {
this.id = id;
this.accountNo = accountNo;
this.depositType = depositType;
@@ -507,6 +511,7 @@
this.daysToInactive = daysToInactive;
this.daysToDormancy = daysToDormancy;
this.daysToEscheat = daysToEscheat;
+ this.savingsAmountOnHold = savingsAmountOnHold;
}
private SavingsAccountChargeData getWithdrawalFee() {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java
index fcd7a88..b386858 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java
@@ -29,7 +29,6 @@
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.transactionAmountParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.transactionDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawBalanceParamName;
-import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_HOLD_AMOUNT_REQUEST_DATA_PARAMETERS;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
import java.lang.reflect.Type;
@@ -67,6 +66,9 @@
public class SavingsAccountTransactionDataValidator {
private final FromJsonHelper fromApiJsonHelper;
+ private static final Set<String> SAVINGS_ACCOUNT_HOLD_AMOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(
+ Arrays.asList(transactionDateParamName, SavingsApiConstants.dateFormatParamName,
+ SavingsApiConstants.localeParamName, transactionAmountParamName));
@Autowired
public SavingsAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) {
@@ -121,7 +123,7 @@
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
- public void validateClosing(final JsonCommand command) {
+ public void validateClosing(final JsonCommand command, final SavingsAccount account) {
final String json = command.json();
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
@@ -144,6 +146,11 @@
baseDataValidator.reset().parameter(withdrawBalanceParamName).value(withdrawBalance).isOneOfTheseValues(true, false);
}
+ if (account.getSavingsHoldAmount().compareTo(BigDecimal.ZERO) == 1) {
+ baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+ "amount.is.on.hold.release.the.amount.to.continue", account.getId());
+ }
+
validatePaymentTypeDetails(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
@@ -192,10 +199,9 @@
baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName)
.failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE);
}
- account.holdFunds(amount);
- if (account.getWithdrawableBalance().compareTo(BigDecimal.ZERO) == -1) {
+ account.holdAmount(amount);
+ if (account.getWithdrawableBalance().compareTo(BigDecimal.ZERO)==-1){
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient balance", account.getId());
- baseDataValidator.failWithCode("validation.msg.savingsaccount.insufficient balance", "Insufficient balance");
}
LocalDate lastTransactionDate = account.retrieveLastTransactionDate();
// compare two dates now
@@ -206,7 +212,8 @@
throwExceptionIfValidationWarningsExist(dataValidationErrors);
final PaymentDetail paymentDetails = null;
- Date createdDate = DateUtils.getDateOfTenant();
+ Date createdDate = new Date();
+
SavingsAccountTransaction transaction = SavingsAccountTransaction.holdAmount(account, account.office(), paymentDetails,
transactionDate, Money.of(account.getCurrency(), amount), createdDate, createdUser);
return transaction;
@@ -217,15 +224,24 @@
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
- if (holdTransaction == null) {
- baseDataValidator.failWithCode("validation.msg.validation.errors.exist", "Transaction not found");
- } else if (holdTransaction.getReleaseIdOfHoldAmountTransaction() != null) {
- baseDataValidator.parameter(SavingsApiConstants.amountParamName).value(holdTransaction.getAmount()).failWithCode("validation.msg.amount.is.not.on.hold",
- "Transaction amount is not on hold");
- }
+ if (holdTransaction == null) {
+ baseDataValidator.failWithCode("validation.msg.validation.errors.exist", "Transaction not found");
+ } else if (holdTransaction.getReleaseIdOfHoldAmountTransaction() != null) {
+ baseDataValidator.parameter(SavingsApiConstants.amountParamName).value(holdTransaction.getAmount())
+ .failWithCode("validation.msg.amount.is.not.on.hold", "Transaction amount is not on hold");
+ }
+
+ if (holdTransaction != null) {
+ boolean isActive = holdTransaction.getSavingsAccount().isActive();
+ if (!isActive) {
+ baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName)
+ .failWithCodeNoParameterAddedToErrorCode(
+ SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE);
+ }
+ }
throwExceptionIfValidationWarningsExist(dataValidationErrors);
- Date createdDate = DateUtils.getDateOfTenant();
+ Date createdDate = new Date();
LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
SavingsAccountTransaction transaction = SavingsAccountTransaction.releaseAmount(holdTransaction, transactionDate, createdDate,
createdUser);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
index f904b09..d7a6298 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
@@ -325,6 +325,8 @@
@JoinColumn(name = "tax_group_id")
private TaxGroup taxGroup;
+ @Column(name = "total_savings_amount_on_hold", scale = 6, precision = 19, nullable = true)
+ private BigDecimal savingsOnHoldAmount;
protected SavingsAccount() {
//
}
@@ -1051,19 +1053,16 @@
Money runningBalance = Money.zero(this.currency);
Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
LocalDate lastSavingsDate = null;
+ final BigDecimal withdrawalFee = null;
for (final SavingsAccountTransaction transaction : transactionsSortedByDate) {
if (transaction.isNotReversed() && transaction.isCredit()) {
runningBalance = runningBalance.plus(transaction.getAmount(this.currency));
} else if (transaction.isNotReversed() && transaction.isDebit()) {
runningBalance = runningBalance.minus(transaction.getAmount(this.currency));
- } else if(transaction.isAmountOnHold() && transaction.getReleaseIdOfHoldAmountTransaction() == null){
- runningBalance = runningBalance.minus(transaction.getAmount(this.currency));
}else {
continue;
}
- final BigDecimal withdrawalFee = null;
-
/*
* Loop through the onHold funds and see if we need to deduct or add
* to minimum required balance and the point in time the transaction
@@ -1093,6 +1092,12 @@
lastSavingsDate = transaction.transactionLocalDate();
}
+ if (this.getSavingsHoldAmount().compareTo(BigDecimal.ZERO) == 1) {
+ if (runningBalance.minus(this.getSavingsHoldAmount()).isLessThanZero()) {
+ throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), withdrawalFee,
+ transactionAmount);
+ }
+ }
}
public void validateAccountBalanceDoesNotBecomeNegative(final String transactionAction,
@@ -2753,7 +2758,7 @@
}
public BigDecimal getWithdrawableBalance() {
- return getAccountBalance().subtract(minRequiredBalanceDerived(getCurrency()).getAmount()).subtract(this.getOnHoldFunds());
+ return getAccountBalance().subtract(minRequiredBalanceDerived(getCurrency()).getAmount()).subtract(this.getOnHoldFunds()).subtract(this.getSavingsHoldAmount());
}
public TaxGroup getTaxGroup() {
@@ -2891,14 +2896,17 @@
baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName)
.failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE);
}
- if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))
- || SavingsAccountSubStatusEnum.BLOCK_DEBIT.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) {
+ if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) {
baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName).value(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))
.failWithCodeNoParameterAddedToErrorCode("currently.set");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
- this.sub_status = SavingsAccountSubStatusEnum.BLOCK_CREDIT.getValue();
+ if (SavingsAccountSubStatusEnum.BLOCK_DEBIT.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) {
+ this.sub_status = SavingsAccountSubStatusEnum.BLOCK.getValue();
+ } else {
+ this.sub_status = SavingsAccountSubStatusEnum.BLOCK_CREDIT.getValue();
+ }
actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status));
return actualChanges;
@@ -2918,12 +2926,17 @@
}
final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.sub_status);
- if (!SavingsAccountSubStatusEnum.BLOCK_CREDIT.hasStateOf(currentSubStatus)) {
- baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName)
- .failWithCodeNoParameterAddedToErrorCode("credits.are.not.blocked");
- }
+ if (!(SavingsAccountSubStatusEnum.BLOCK_CREDIT.hasStateOf(currentSubStatus)
+ || SavingsAccountSubStatusEnum.BLOCK.hasStateOf(currentSubStatus))) {
+ baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName)
+ .failWithCodeNoParameterAddedToErrorCode("credits.are.not.blocked");
+ }
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
- this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue();
+ if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(currentSubStatus)) {
+ this.sub_status = SavingsAccountSubStatusEnum.BLOCK_DEBIT.getValue();
+ } else {
+ this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue();
+ }
actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status));
return actualChanges;
}
@@ -2942,15 +2955,17 @@
.failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE);
}
- if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))
- || SavingsAccountSubStatusEnum.BLOCK_CREDIT.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) {
+ if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) {
baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName).value(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))
.failWithCodeNoParameterAddedToErrorCode("currently.set");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
-
- this.sub_status = SavingsAccountSubStatusEnum.BLOCK_DEBIT.getValue();
+ if (SavingsAccountSubStatusEnum.BLOCK_CREDIT.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) {
+ this.sub_status = SavingsAccountSubStatusEnum.BLOCK.getValue();
+ } else {
+ this.sub_status = SavingsAccountSubStatusEnum.BLOCK_DEBIT.getValue();
+ }
actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status));
return actualChanges;
@@ -2973,12 +2988,17 @@
}
final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.sub_status);
- if (!SavingsAccountSubStatusEnum.BLOCK_DEBIT.hasStateOf(currentSubStatus)) {
- baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName)
- .failWithCodeNoParameterAddedToErrorCode("debits.are.not.blocked");
- }
+ if (!(SavingsAccountSubStatusEnum.BLOCK_DEBIT.hasStateOf(currentSubStatus)
+ || SavingsAccountSubStatusEnum.BLOCK.hasStateOf(currentSubStatus))) {
+ baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName)
+ .failWithCodeNoParameterAddedToErrorCode("debits.are.not.blocked");
+ }
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
- this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue();
+ if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(currentSubStatus)) {
+ this.sub_status = SavingsAccountSubStatusEnum.BLOCK_CREDIT.getValue();
+ } else {
+ this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue();
+ }
actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status));
return actualChanges;
}
@@ -3006,9 +3026,27 @@
public LocalDate retrieveLastTransactionDate() {
final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
- SavingsAccountTransaction lastTransaction = transactionsSortedByDate.get(transactionsSortedByDate.size() - 1);
- return lastTransaction.transactionLocalDate();
+ SavingsAccountTransaction lastTransaction = null;
+ if (transactionsSortedByDate.size() > 0) {
+ lastTransaction = transactionsSortedByDate.get(transactionsSortedByDate.size() - 1);
+ }
+ LocalDate lastransactionDate = null;
+ if (lastTransaction != null) {
+ lastransactionDate = lastTransaction.transactionLocalDate();
+ }
+ return lastransactionDate;
}
-
-
+
+ public BigDecimal getSavingsHoldAmount() {
+ return this.savingsOnHoldAmount == null ? BigDecimal.ZERO : this.savingsOnHoldAmount;
+ }
+
+ public void holdAmount(BigDecimal amount) {
+ this.savingsOnHoldAmount = getSavingsHoldAmount().add(amount);
+ }
+
+ public void releaseAmount(BigDecimal amount) {
+ this.savingsOnHoldAmount = getSavingsHoldAmount().subtract(amount);
+ }
+
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
index 12de527..cf5ae03 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
@@ -787,4 +787,8 @@
public Long getReleaseIdOfHoldAmountTransaction() {
return this.releaseIdOfHoldAmountTransaction;
}
+
+ public boolean isAmountOnHoldNotReleased() {
+ return (isAmountOnHold() && getReleaseIdOfHoldAmountTransaction() == null);
+ }
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java
index 011256a..b38ca37 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java
@@ -22,8 +22,11 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.data.PaginationParameters;
@@ -122,6 +125,9 @@
private final CalendarReadPlatformService calendarReadPlatformService;
private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+ // allowed column names for sorting the query result
+ private final static Set<String> supportedOrderByValues = new HashSet<>(Arrays.asList("id", "accountNumbr",
+ "officeId", "officeName"));
@Autowired
public DepositAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
@@ -178,7 +184,7 @@
public Page<DepositAccountData> retrieveAllPaged(final DepositAccountType depositAccountType,
final PaginationParameters paginationParameters) {
- this.paginationParametersDataValidator.validateParameterValues(paginationParameters, DepositsApiConstants.supportedOrderByValues,
+ this.paginationParametersDataValidator.validateParameterValues(paginationParameters, supportedOrderByValues,
depositAccountType.resourceName());
final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
index 1b68e3f..a9704eb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
@@ -304,6 +304,7 @@
sqlBuilder.append("sa.withhold_tax as withHoldTax, ");
sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax, ");
sqlBuilder.append("sa.last_interest_calculation_date as lastInterestCalculationDate, ");
+ sqlBuilder.append("sa.total_savings_amount_on_hold as onHoldAmount, ");
sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, ");
sqlBuilder.append("(select IFNULL(max(sat.transaction_date),sa.activatedon_date) ");
sqlBuilder.append("from m_savings_account_transaction as sat ");
@@ -511,12 +512,19 @@
"minBalanceForInterestCalculation");
final BigDecimal onHoldFunds = rs.getBigDecimal("onHoldFunds");
+ final BigDecimal onHoldAmount = rs.getBigDecimal("onHoldAmount");
+
BigDecimal availableBalance = accountBalance;
if (availableBalance != null && onHoldFunds != null) {
availableBalance = availableBalance.subtract(onHoldFunds);
}
+ if (availableBalance != null && onHoldAmount != null) {
+
+ availableBalance = availableBalance.subtract(onHoldAmount);
+ }
+
BigDecimal interestNotPosted = BigDecimal.ZERO;
LocalDate lastInterestCalculationDate = null;
if(totalInterestEarned != null){
@@ -542,7 +550,7 @@
interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers,
summary, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax,
- taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
+ taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, onHoldAmount);
}
}
@@ -1074,7 +1082,8 @@
// final LocalDate annualFeeNextDueDate = null;
final SavingsAccountSummaryData summary = null;
final BigDecimal onHoldFunds = null;
-
+ final BigDecimal savingsAmountOnHold = null;
+
final SavingsAccountSubStatusEnumData subStatus = null;
final LocalDate lastActiveTransactionDate = null;
final boolean isDormancyTrackingActive = false;
@@ -1090,7 +1099,7 @@
interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers,
summary, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax,
- taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
+ taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold);
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
index f9e52b2..03409ed 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
@@ -612,8 +612,9 @@
public CommandProcessingResult close(final Long savingsId, final JsonCommand command) {
final AppUser user = this.context.authenticatedUser();
- this.savingsAccountTransactionDataValidator.validateClosing(command);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
+ this.savingsAccountTransactionDataValidator.validateClosing(command, account);
+
final boolean isLinkedWithAnyActiveLoan = this.accountAssociationsReadPlatformService.isLinkedWithAnyActiveAccount(savingsId);
if (isLinkedWithAnyActiveLoan) {
@@ -1403,7 +1404,7 @@
.validateReleaseAmountAndAssembleForm(holdTransaction, submittedBy);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
checkClientOrGroupActive(account);
- account.releaseFunds(transaction.getAmount());
+ account.releaseAmount(transaction.getAmount());
this.savingsAccountTransactionRepository.save(transaction);
holdTransaction.updateReleaseId(transaction.getId());
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/SelfServiceApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/SelfServiceApiConstants.java
new file mode 100644
index 0000000..2362523
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/SelfServiceApiConstants.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.registration;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SelfServiceApiConstants {
+
+ public static final String accountNumberParamName = "accountNumber";
+ public static final String passwordParamName = "password";
+ public static final String firstNameParamName = "firstName";
+ public static final String mobileNumberParamName = "mobileNumber";
+ public static final String lastNameParamName = "lastName";
+ public static final String emailParamName = "email";
+ public static final String usernameParamName = "username";
+ public static final String authenticationTokenParamName = "authenticationToken";
+ public static final String authenticationModeParamName = "authenticationMode";
+ public static final String emailModeParamName = "email";
+ public static final String mobileModeParamName = "mobile";
+ public static final String requestIdParamName = "requestId";
+ public static final String createRequestSuccessMessage = "Self service request created.";
+ public static final Set<String> REGISTRATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(usernameParamName,
+ accountNumberParamName, passwordParamName, firstNameParamName, mobileNumberParamName, lastNameParamName, emailParamName,
+ authenticationModeParamName));
+ public static final Set<String> CREATE_USER_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(requestIdParamName,
+ authenticationTokenParamName));
+ public static final Object[] SUPPORTED_AUTHENTICATION_MODE_PARAMETERS = new Object[] {emailModeParamName,
+ mobileModeParamName};
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/api/SelfServiceRegistrationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/api/SelfServiceRegistrationApiResource.java
new file mode 100644
index 0000000..64dcfa9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/api/SelfServiceRegistrationApiResource.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fineract.portfolio.self.registration.api;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.portfolio.note.data.NoteData;
+import org.apache.fineract.portfolio.self.registration.SelfServiceApiConstants;
+import org.apache.fineract.portfolio.self.registration.service.SelfServiceRegistrationWritePlatformService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/self/registration")
+@Component
+@Scope("singleton")
+public class SelfServiceRegistrationApiResource {
+
+ private final SelfServiceRegistrationWritePlatformService selfServiceRegistrationWritePlatformService;
+ private final DefaultToApiJsonSerializer<AppUser> toApiJsonSerializer;
+
+ @Autowired
+ public SelfServiceRegistrationApiResource(
+ final SelfServiceRegistrationWritePlatformService selfServiceRegistrationWritePlatformService,
+ final DefaultToApiJsonSerializer<AppUser> toApiJsonSerializer) {
+ this.selfServiceRegistrationWritePlatformService = selfServiceRegistrationWritePlatformService;
+ this.toApiJsonSerializer = toApiJsonSerializer;
+ }
+
+ @POST
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String createSelfServiceRegistrationRequest(final String apiRequestBodyAsJson) {
+ this.selfServiceRegistrationWritePlatformService.createRegistrationRequest(apiRequestBodyAsJson);
+ return SelfServiceApiConstants.createRequestSuccessMessage;
+ }
+
+ @POST
+ @Path("user")
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String createSelfServiceUser(final String apiRequestBodyAsJson) {
+ AppUser user = this.selfServiceRegistrationWritePlatformService.createUser(apiRequestBodyAsJson);
+ return this.toApiJsonSerializer.serialize(CommandProcessingResult.resourceResult(user.getId(), null));
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/data/SelfServiceRegistrationData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/data/SelfServiceRegistrationData.java
new file mode 100644
index 0000000..9f62500
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/data/SelfServiceRegistrationData.java
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.registration.data;
+
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.joda.time.LocalDate;
+
+public class SelfServiceRegistrationData {
+
+ @SuppressWarnings("unused")
+ private final Long id;
+ @SuppressWarnings("unused")
+ private Client client;
+ @SuppressWarnings("unused")
+ private String firstName;
+ @SuppressWarnings("unused")
+ private String lastName;
+ @SuppressWarnings("unused")
+ private String mobileNumber;
+ @SuppressWarnings("unused")
+ private String email;
+ @SuppressWarnings("unused")
+ private String authenticationToken;
+ @SuppressWarnings("unused")
+ private String username;
+ @SuppressWarnings("unused")
+ private String password;
+ @SuppressWarnings("unused")
+ private LocalDate createdDate;
+
+ public SelfServiceRegistrationData(final Long id, final Client client, final String firstName, final String lastName,
+ final String mobileNumber, final String email, final String authenticationToken, final String username, final String password,
+ final LocalDate createdDate) {
+ this.id = id;
+ this.client = client;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.mobileNumber = mobileNumber;
+ this.email = email;
+ this.authenticationToken = authenticationToken;
+ this.username = username;
+ this.password = password;
+ this.createdDate = createdDate;
+ }
+
+ public static SelfServiceRegistrationData getData(final Long id, final Client client, final String firstName, final String lastName,
+ final String mobileNumber, final String email, final String authenticationToken, final String username, final String password,
+ final LocalDate createdDate) {
+ return new SelfServiceRegistrationData(id, client, firstName, lastName, mobileNumber, email, authenticationToken, username,
+ password, createdDate);
+
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/domain/SelfServiceRegistration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/domain/SelfServiceRegistration.java
new file mode 100644
index 0000000..7c226c5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/domain/SelfServiceRegistration.java
@@ -0,0 +1,131 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.registration.domain;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+import org.apache.fineract.portfolio.client.domain.Client;
+
+@Entity
+@Table(name = "request_audit_table")
+public class SelfServiceRegistration extends AbstractPersistableCustom<Long> {
+
+ @ManyToOne
+ @JoinColumn(name = "client_id", nullable = false)
+ private Client client;
+
+ @Column(name = "account_number", length = 100, nullable = false)
+ private String accountNumber;
+
+ @Column(name = "firstname", length = 100, nullable = false)
+ private String firstName;
+
+ @Column(name = "lastname", length = 100, nullable = false)
+ private String lastName;
+
+ @Column(name = "mobile_number", length = 50, nullable = true)
+ private String mobileNumber;
+
+ @Column(name = "email", length = 100, nullable = false)
+ private String email;
+
+ @Column(name = "authentication_token", length = 100, nullable = true)
+ private String authenticationToken;
+
+ @Column(name = "username", length = 100, nullable = false)
+ private String username;
+
+ @Column(name = "password", length = 100, nullable = false)
+ private String password;
+
+ @Column(name = "created_date", nullable = false)
+ @Temporal(TemporalType.DATE)
+ private Date createdDate;
+
+ public SelfServiceRegistration(final Client client, String accountNumber, final String firstName, final String lastName,
+ final String mobileNumber, final String email, final String authenticationToken, final String username, final String password) {
+ this.client = client;
+ this.accountNumber = accountNumber;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.mobileNumber = mobileNumber;
+ this.email = email;
+ this.authenticationToken = authenticationToken;
+ this.username = username;
+ this.password = password;
+ this.createdDate = new Date();
+ }
+
+ public static SelfServiceRegistration instance(final Client client, final String accountNumber, final String firstname,
+ final String lastName, final String mobileNumber, final String email, final String authenticationToken, final String username,
+ final String password) {
+ return new SelfServiceRegistration(client, accountNumber, firstname, lastName, mobileNumber, email, authenticationToken, username,
+ password);
+ }
+
+ public Client getClient() {
+ return this.client;
+ }
+
+ public String getFirstName() {
+ return this.firstName;
+ }
+
+ public String getLastName() {
+ return this.lastName;
+ }
+
+ public String getMobileNumber() {
+ return this.mobileNumber;
+ }
+
+ public String getEmail() {
+ return this.email;
+ }
+
+ public String getAuthenticationToken() {
+ return this.authenticationToken;
+ }
+
+ public Date getCreatedDate() {
+ return this.createdDate;
+ }
+
+ public String getUsername() {
+ return this.username;
+ }
+
+ public String getPassword() {
+ return this.password;
+ }
+
+ public String getAccountNumber() {
+ return this.accountNumber;
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/domain/SelfServiceRegistrationRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/domain/SelfServiceRegistrationRepository.java
new file mode 100644
index 0000000..2f93482
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/domain/SelfServiceRegistrationRepository.java
@@ -0,0 +1,36 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.registration.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+public interface SelfServiceRegistrationRepository extends JpaRepository<SelfServiceRegistration, Long>,
+ JpaSpecificationExecutor<SelfServiceRegistration> {
+
+ public static final String FIND_BY_REQUEST_AND_AUTHENTICATION_TOKEN = "select request from SelfServiceRegistration request where request.id = :id and "
+ + "request.authenticationToken = :authenticationToken";
+
+ @Query(FIND_BY_REQUEST_AND_AUTHENTICATION_TOKEN)
+ SelfServiceRegistration getRequestByIdAndAuthenticationToken(@Param("id") Long id,
+ @Param("authenticationToken") String authenticationToken);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/exception/SelfServiceRegistrationNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/exception/SelfServiceRegistrationNotFoundException.java
new file mode 100644
index 0000000..888a827
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/exception/SelfServiceRegistrationNotFoundException.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.registration.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class SelfServiceRegistrationNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+ public SelfServiceRegistrationNotFoundException(Long requestId, String authenticationToken) {
+ super("error.msg.self.service.registration.not.found", "Self service registration not found with request id : " + requestId
+ + " and authentication token :" + authenticationToken);
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationReadPlatformService.java
new file mode 100644
index 0000000..9ff45b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationReadPlatformService.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.registration.service;
+
+public interface SelfServiceRegistrationReadPlatformService {
+
+ public boolean isClientExist(String accountNumber, String firstName, String lastName, String mobileNumber,
+ boolean isEmailAuthenticationMode);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationReadPlatformServiceImpl.java
new file mode 100644
index 0000000..ce265e6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationReadPlatformServiceImpl.java
@@ -0,0 +1,50 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.registration.service;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SelfServiceRegistrationReadPlatformServiceImpl implements SelfServiceRegistrationReadPlatformService {
+
+ private final JdbcTemplate jdbcTemplate;
+
+ @Autowired
+ public SelfServiceRegistrationReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ @Override
+ public boolean isClientExist(String accountNumber, String firstName, String lastName, String mobileNumber,
+ boolean isEmailAuthenticationMode) {
+ String sql = "select count(*) from m_client where account_no = ? and firstname = ? and lastname = ?";
+ Object[] params = new Object[] { accountNumber, firstName, lastName };
+ if (!isEmailAuthenticationMode) {
+ sql = sql + " and mobile_no = ?";
+ params = new Object[] { accountNumber, firstName, lastName, mobileNumber };
+ }
+ Integer count = this.jdbcTemplate.queryForObject(sql, params, Integer.class);
+ if (count == 0) { return false; }
+ return true;
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformService.java
new file mode 100644
index 0000000..7827c44
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformService.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.registration.service;
+
+import org.apache.fineract.portfolio.self.registration.domain.SelfServiceRegistration;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+public interface SelfServiceRegistrationWritePlatformService {
+
+ public SelfServiceRegistration createRegistrationRequest(String apiRequestBodyAsJson);
+
+ public AppUser createUser(String apiRequestBodyAsJson);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
new file mode 100644
index 0000000..7797b2f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
@@ -0,0 +1,293 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.registration.service;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.PersistenceException;
+
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.fineract.infrastructure.campaigns.sms.data.SmsProviderData;
+import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
+import org.apache.fineract.infrastructure.campaigns.sms.service.SmsCampaignDropdownReadPlatformService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.domain.EmailDetail;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.GmailBackedPlatformEmailService;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType;
+import org.apache.fineract.infrastructure.sms.scheduler.SmsMessageScheduledJobService;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.self.registration.SelfServiceApiConstants;
+import org.apache.fineract.portfolio.self.registration.domain.SelfServiceRegistration;
+import org.apache.fineract.portfolio.self.registration.domain.SelfServiceRegistrationRepository;
+import org.apache.fineract.portfolio.self.registration.exception.SelfServiceRegistrationNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.domain.PasswordValidationPolicy;
+import org.apache.fineract.useradministration.domain.PasswordValidationPolicyRepository;
+import org.apache.fineract.useradministration.domain.Role;
+import org.apache.fineract.useradministration.domain.UserDomainService;
+import org.apache.fineract.useradministration.service.AppUserReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Service
+public class SelfServiceRegistrationWritePlatformServiceImpl implements SelfServiceRegistrationWritePlatformService {
+
+ private final SelfServiceRegistrationRepository selfServiceRegistrationRepository;
+ private final FromJsonHelper fromApiJsonHelper;
+ private final SelfServiceRegistrationReadPlatformService selfServiceRegistrationReadPlatformService;
+ private final ClientRepositoryWrapper clientRepository;
+ private final PasswordValidationPolicyRepository passwordValidationPolicy;
+ private final UserDomainService userDomainService;
+ private final GmailBackedPlatformEmailService gmailBackedPlatformEmailService;
+ private final SmsMessageRepository smsMessageRepository;
+ private SmsMessageScheduledJobService smsMessageScheduledJobService;
+ private final SmsCampaignDropdownReadPlatformService smsCampaignDropdownReadPlatformService;
+ private final AppUserReadPlatformService appUserReadPlatformService;
+
+ @Autowired
+ public SelfServiceRegistrationWritePlatformServiceImpl(final SelfServiceRegistrationRepository selfServiceRegistrationRepository,
+ final FromJsonHelper fromApiJsonHelper,
+ final SelfServiceRegistrationReadPlatformService selfServiceRegistrationReadPlatformService,
+ final ClientRepositoryWrapper clientRepository, final PasswordValidationPolicyRepository passwordValidationPolicy,
+ final UserDomainService userDomainService, final GmailBackedPlatformEmailService gmailBackedPlatformEmailService,
+ final SmsMessageRepository smsMessageRepository, SmsMessageScheduledJobService smsMessageScheduledJobService,
+ final SmsCampaignDropdownReadPlatformService smsCampaignDropdownReadPlatformService,
+ final AppUserReadPlatformService appUserReadPlatformService) {
+ this.selfServiceRegistrationRepository = selfServiceRegistrationRepository;
+ this.fromApiJsonHelper = fromApiJsonHelper;
+ this.selfServiceRegistrationReadPlatformService = selfServiceRegistrationReadPlatformService;
+ this.clientRepository = clientRepository;
+ this.passwordValidationPolicy = passwordValidationPolicy;
+ this.userDomainService = userDomainService;
+ this.gmailBackedPlatformEmailService = gmailBackedPlatformEmailService;
+ this.smsMessageRepository = smsMessageRepository;
+ this.smsMessageScheduledJobService = smsMessageScheduledJobService;
+ this.smsCampaignDropdownReadPlatformService = smsCampaignDropdownReadPlatformService;
+ this.appUserReadPlatformService = appUserReadPlatformService;
+ }
+
+ @Override
+ public SelfServiceRegistration createRegistrationRequest(String apiRequestBodyAsJson) {
+ Gson gson = new Gson();
+ final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+ final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("user");
+ this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, apiRequestBodyAsJson,
+ SelfServiceApiConstants.REGISTRATION_REQUEST_DATA_PARAMETERS);
+ JsonElement element = gson.fromJson(apiRequestBodyAsJson.toString(), JsonElement.class);
+
+ String accountNumber = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.accountNumberParamName, element);
+ baseDataValidator.reset().parameter(SelfServiceApiConstants.accountNumberParamName).value(accountNumber).notNull().notBlank()
+ .notExceedingLengthOf(100);
+
+ String firstName = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.firstNameParamName, element);
+ baseDataValidator.reset().parameter(SelfServiceApiConstants.firstNameParamName).value(firstName).notBlank()
+ .notExceedingLengthOf(100);
+
+ String lastName = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.lastNameParamName, element);
+ baseDataValidator.reset().parameter(SelfServiceApiConstants.lastNameParamName).value(lastName).notBlank().notExceedingLengthOf(100);
+
+ String username = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.usernameParamName, element);
+ baseDataValidator.reset().parameter(SelfServiceApiConstants.usernameParamName).value(username).notBlank().notExceedingLengthOf(100);
+
+ // validate password policy
+ String password = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.passwordParamName, element);
+ final PasswordValidationPolicy validationPolicy = this.passwordValidationPolicy.findActivePasswordValidationPolicy();
+ final String regex = validationPolicy.getRegex();
+ final String description = validationPolicy.getDescription();
+ baseDataValidator.reset().parameter(SelfServiceApiConstants.passwordParamName).value(password)
+ .matchesRegularExpression(regex, description).notExceedingLengthOf(100);
+
+ String authenticationMode = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.authenticationModeParamName, element);
+ baseDataValidator.reset().parameter(SelfServiceApiConstants.authenticationModeParamName).value(authenticationMode).notBlank()
+ .isOneOfTheseStringValues(SelfServiceApiConstants.emailModeParamName, SelfServiceApiConstants.mobileModeParamName);
+
+ String email = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.emailParamName, element);
+ baseDataValidator.reset().parameter(SelfServiceApiConstants.emailParamName).value(email).notNull().notBlank()
+ .notExceedingLengthOf(100);
+
+ boolean isEmailAuthenticationMode = authenticationMode.equalsIgnoreCase(SelfServiceApiConstants.emailModeParamName);
+ String mobileNumber = null;
+ if (!isEmailAuthenticationMode) {
+ mobileNumber = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.mobileNumberParamName, element);
+ baseDataValidator.reset().parameter(SelfServiceApiConstants.mobileNumberParamName).value(mobileNumber).notNull()
+ .validatePhoneNumber();
+ }
+ validateForDuplicateUsername(username);
+
+ throwExceptionIfValidationError(dataValidationErrors, accountNumber, firstName, lastName, mobileNumber, isEmailAuthenticationMode);
+
+ String authenticationToken = randomAuthorizationTokenGeneration();
+ Client client = this.clientRepository.getClientByAccountNumber(accountNumber);
+ SelfServiceRegistration selfServiceRegistration = SelfServiceRegistration.instance(client, accountNumber, firstName, lastName,
+ mobileNumber, email, authenticationToken, username, password);
+ this.selfServiceRegistrationRepository.saveAndFlush(selfServiceRegistration);
+ sendAuthorizationToken(selfServiceRegistration, isEmailAuthenticationMode);
+ return selfServiceRegistration;
+
+ }
+
+ public void validateForDuplicateUsername(String username) {
+ boolean isDuplicateUserName = this.appUserReadPlatformService.isUsernameExist(username);
+ if (isDuplicateUserName) {
+ final StringBuilder defaultMessageBuilder = new StringBuilder("User with username ").append(username)
+ .append(" already exists.");
+ throw new PlatformDataIntegrityException("error.msg.user.duplicate.username", defaultMessageBuilder.toString(),
+ SelfServiceApiConstants.usernameParamName, username);
+ }
+ }
+
+ public void sendAuthorizationToken(SelfServiceRegistration selfServiceRegistration, Boolean isEmailAuthenticationMode) {
+ if (isEmailAuthenticationMode) {
+ sendAuthorizationMail(selfServiceRegistration);
+ } else {
+ sendAuthorizationMessage(selfServiceRegistration);
+ }
+ }
+
+ private void sendAuthorizationMessage(SelfServiceRegistration selfServiceRegistration) {
+ Collection<SmsProviderData> smsProviders = this.smsCampaignDropdownReadPlatformService.retrieveSmsProviders();
+ if (smsProviders.isEmpty()) { throw new PlatformDataIntegrityException("error.msg.mobile.service.provider.not.available",
+ "Mobile service provider not available."); }
+ Long providerId = (new ArrayList<>(smsProviders)).get(0).getId();
+ final String message = "Hi " + selfServiceRegistration.getFirstName() + "," + "\n"
+ + "To create user, please use following details \n" + "Request Id : " + selfServiceRegistration.getId()
+ + "\n Authentication Token : " + selfServiceRegistration.getAuthenticationToken();
+ String externalId = null;
+ Group group = null;
+ Staff staff = null;
+ SmsCampaign smsCampaign = null;
+ SmsMessage smsMessage = SmsMessage.instance(externalId, group, selfServiceRegistration.getClient(), staff,
+ SmsMessageStatusType.PENDING, message, selfServiceRegistration.getMobileNumber(), smsCampaign);
+ this.smsMessageRepository.save(smsMessage);
+ this.smsMessageScheduledJobService.sendTriggeredMessage(new ArrayList<>(Arrays.asList(smsMessage)), providerId);
+ }
+
+ private void sendAuthorizationMail(SelfServiceRegistration selfServiceRegistration) {
+ final String subject = "Authorization token ";
+ final String body = "Hi " + selfServiceRegistration.getFirstName() + "," + "\n" + "To create user, please use following details\n"
+ + "Request Id : " + selfServiceRegistration.getId() + "\n Authentication Token : "
+ + selfServiceRegistration.getAuthenticationToken();
+
+ final EmailDetail emailDetail = new EmailDetail(subject, body, selfServiceRegistration.getEmail(),
+ selfServiceRegistration.getFirstName());
+ this.gmailBackedPlatformEmailService.sendDefinedEmail(emailDetail);
+ }
+
+ private void throwExceptionIfValidationError(final List<ApiParameterError> dataValidationErrors, String accountNumber,
+ String firstName, String lastName, String mobileNumber, boolean isEmailAuthenticationMode) {
+ if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+ boolean isClientExist = this.selfServiceRegistrationReadPlatformService.isClientExist(accountNumber, firstName, lastName,
+ mobileNumber, isEmailAuthenticationMode);
+ if (!isClientExist) { throw new ClientNotFoundException(); }
+ }
+
+ public static String randomAuthorizationTokenGeneration() {
+ Integer randomPIN = (int) (Math.random() * 9000) + 1000;
+ return randomPIN.toString();
+ }
+
+ @Override
+ public AppUser createUser(String apiRequestBodyAsJson) {
+ JsonCommand command = null;
+ String username = null;
+ try {
+ Gson gson = new Gson();
+ final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+ final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("user");
+ this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, apiRequestBodyAsJson,
+ SelfServiceApiConstants.CREATE_USER_REQUEST_DATA_PARAMETERS);
+ JsonElement element = gson.fromJson(apiRequestBodyAsJson.toString(), JsonElement.class);
+
+ Long id = this.fromApiJsonHelper.extractLongNamed(SelfServiceApiConstants.requestIdParamName, element);
+ baseDataValidator.reset().parameter(SelfServiceApiConstants.requestIdParamName).value(id).notNull().integerGreaterThanZero();
+ command = JsonCommand.fromJsonElement(id, element);
+ String authenticationToken = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.authenticationTokenParamName,
+ element);
+ baseDataValidator.reset().parameter(SelfServiceApiConstants.authenticationTokenParamName).value(authenticationToken).notBlank()
+ .notNull().notExceedingLengthOf(100);
+
+ if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+ SelfServiceRegistration selfServiceRegistration = this.selfServiceRegistrationRepository.getRequestByIdAndAuthenticationToken(
+ id, authenticationToken);
+ if (selfServiceRegistration == null) { throw new SelfServiceRegistrationNotFoundException(id, authenticationToken); }
+ username = selfServiceRegistration.getUsername();
+ Client client = selfServiceRegistration.getClient();
+ final boolean passwordNeverExpire = true;
+ final boolean isSelfServiceUser = true;
+ final Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
+ authorities.add(new SimpleGrantedAuthority("DUMMY_ROLE_NOT_USED_OR_PERSISTED_TO_AVOID_EXCEPTION"));
+ final Set<Role> allRoles = new HashSet<>();
+ List<Client> clients = new ArrayList<>(Arrays.asList(client));
+ User user = new User(selfServiceRegistration.getUsername(), selfServiceRegistration.getPassword(), authorities);
+ AppUser appUser = new AppUser(client.getOffice(), user, allRoles, selfServiceRegistration.getEmail(), client.getFirstname(),
+ client.getLastname(), null, passwordNeverExpire, isSelfServiceUser, clients);
+ this.userDomainService.create(appUser, true);
+ return appUser;
+
+ } catch (final DataIntegrityViolationException dve) {
+ handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve, username);
+ return null;
+ } catch (final PersistenceException | AuthenticationServiceException dve) {
+ Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
+ handleDataIntegrityIssues(command, throwable, dve, username);
+ return null;
+ }
+
+ }
+
+ private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dve, String username) {
+ if (realCause.getMessage().contains("'username_org'")) {
+ final StringBuilder defaultMessageBuilder = new StringBuilder("User with username ").append(username)
+ .append(" already exists.");
+ throw new PlatformDataIntegrityException("error.msg.user.duplicate.username", defaultMessageBuilder.toString(), "username",
+ username);
+ }
+ throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue", "Unknown data integrity issue with resource.");
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformService.java
index 067c725..b17f6a1 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformService.java
@@ -31,4 +31,6 @@
AppUserData retrieveNewUserDetails();
AppUserData retrieveUser(Long userId);
+
+ boolean isUsernameExist(String username);
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformServiceImpl.java
index 21e064f..13cbbd8 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformServiceImpl.java
@@ -212,4 +212,13 @@
+ " join m_office o on o.id = u.office_id where o.hierarchy like ? and u.is_deleted=0 order by u.username";
}
}
+
+ @Override
+ public boolean isUsernameExist(String username) {
+ String sql = "select count(*) from m_appuser where username = ?";
+ Object[] params = new Object[] { username };
+ Integer count = this.jdbcTemplate.queryForObject(sql, params, Integer.class);
+ if (count == 0) { return false; }
+ return true;
+ }
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/META-INF/spring/appContext.xml b/fineract-provider/src/main/resources/META-INF/spring/appContext.xml
index 9fe95d4..dffd1b2 100644
--- a/fineract-provider/src/main/resources/META-INF/spring/appContext.xml
+++ b/fineract-provider/src/main/resources/META-INF/spring/appContext.xml
@@ -55,7 +55,8 @@
org.apache.fineract.template.*,
org.apache.fineract.template.service.*,
org.apache.fineract.useradministration.*,
- org.apache.fineract.batch">
+ org.apache.fineract.batch,
+ org.apache.fineract.adhocquery.*">
<context:exclude-filter expression="org.springframework.stereotype.Controller"
type="annotation" />
@@ -80,6 +81,7 @@
<jpa:repositories base-package="org.apache.fineract.scheduledjobs.domain" />
<jpa:repositories base-package="org.apache.fineract.template.domain" />
<jpa:repositories base-package="org.apache.fineract.infrastructure.campaigns.sms.domain" />
+ <jpa:repositories base-package="org.apache.fineract.adhocquery.domain" />
<import resource="infrastructure.xml" />
diff --git a/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml b/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
index ee4fb6d..f03220f 100644
--- a/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
+++ b/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
@@ -38,6 +38,10 @@
method="POST" requires-channel="https" />
<intercept-url pattern="/api/*/self/authentication" access="permitAll"
method="POST" requires-channel="https" />
+ <intercept-url pattern="/api/*/self/registration" access="permitAll"
+ method="POST" requires-channel="https" />
+ <intercept-url pattern="/api/*/self/registration/user" access="permitAll"
+ method="POST" requires-channel="https" />
<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
method="GET" requires-channel="https" />
<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V330__savings_account_transaction_releaseId.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V330__savings_account_transaction_releaseId.sql
index cf9e24c..c20ea9f 100644
--- a/fineract-provider/src/main/resources/sql/migrations/core_db/V330__savings_account_transaction_releaseId.sql
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V330__savings_account_transaction_releaseId.sql
@@ -40,5 +40,9 @@
ALTER TABLE `m_savings_account_transaction` ADD COLUMN `release_id_of_hold_amount` BIGINT(20) NULL DEFAULT NULL;
+-- modify `m_savings_account`
+
+ALTER TABLE `m_savings_account` ADD COLUMN `total_savings_amount_on_hold` DECIMAL(19,6) NULL DEFAULT NULL;
+
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V331__holiday_schema_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V331__holiday_schema_changes.sql
new file mode 100644
index 0000000..94ae19a
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V331__holiday_schema_changes.sql
@@ -0,0 +1,23 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied. See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+--
+
+
+ALTER TABLE m_holiday
+MODIFY COLUMN repayments_rescheduled_to DATETIME NULL DEFAULT NULL,
+ADD COLUMN `rescheduling_type` INT(5) NOT NULL DEFAULT '2';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V332__self_service_registration_schema.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V332__self_service_registration_schema.sql
new file mode 100644
index 0000000..0f93700
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V332__self_service_registration_schema.sql
@@ -0,0 +1,38 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied. See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+--
+
+CREATE TABLE `request_audit_table` (
+ `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+ `lastname` VARCHAR(100) NOT NULL,
+ `username` VARCHAR(100) NOT NULL,
+ `mobile_number` VARCHAR(50) NULL DEFAULT NULL,
+ `firstname` VARCHAR(100) NOT NULL,
+ `authentication_token` VARCHAR(100) NULL DEFAULT NULL,
+ `password` VARCHAR(250) NOT NULL,
+ `email` VARCHAR(100) NOT NULL,
+ `client_id` BIGINT(20) NOT NULL,
+ `created_date` DATE NOT NULL,
+ `account_number` VARCHAR(100) NOT NULL,
+ PRIMARY KEY (`id`),
+ INDEX `FK1_request_audit_table_m_client` (`client_id`),
+ CONSTRAINT `FK1_request_audit_table_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V333__adhocquery.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V333__adhocquery.sql
new file mode 100644
index 0000000..1da45cf
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V333__adhocquery.sql
@@ -0,0 +1,49 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied. See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+--
+
+CREATE TABLE `m_adhoc` (
+ `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(100) NULL DEFAULT NULL,
+ `query` VARCHAR(2000) NULL DEFAULT NULL,
+ `table_name` VARCHAR(100) NULL DEFAULT NULL,
+ `table_fields` VARCHAR(1000) NULL DEFAULT NULL,
+ `email` VARCHAR(500) NOT NULL,
+ `IsActive` TINYINT(1) NOT NULL DEFAULT '0',
+ `created_date` DATETIME NULL DEFAULT NULL,
+ `createdby_id` BIGINT NOT NULL,
+ `lastmodifiedby_id` BIGINT(20) NOT NULL,
+ `lastmodified_date` DATETIME NULL DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ CONSTRAINT `createdby_id` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+ CONSTRAINT `lastmodifiedby_id` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+)
+COLLATE='latin1_swedish_ci'
+ENGINE=InnoDB
+;
+
+INSERT INTO `m_permission`
+(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) VALUES
+('authorisation','UPDATE_ADHOC','ADHOC','UPDATE',1),
+('authorisation','UPDATE_ADHOC_CHECKER','ADHOC','UPDATE',0),
+('authorisation','DELETE_ADHOC','ADHOC','DELETE',1),
+('authorisation','DELETE_ADHOC_CHECKER','ADHOC','DELETE',0),
+('authorisation','CREATE_ADHOC','ADHOC','CREATE',1),
+('authorisation','CREATE_ADHOC_CHECKER','ADHOC','CREATE',0);
+
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`) VALUES ('Generate AdhocClient Schedule', 'Generate AdhocClient Schedule', '0 0 12 1/1 * ? *', now());