FINERACT-2258: Fix for making the Post Interest for Savings job properly work in a multitenant environment
Fineract Docker build - MariaDB / build (push) Canceled after 0s
Fineract Docker build - PostgreSQL / build (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract ActiveMQ Smoke / build (push) Canceled after 0s
Fineract Build Progressive Loan Embeddable Jar & Test with a Sample Application / build (push) Canceled after 0s
Fineract Build & Test - MariaDB / build (push) Canceled after 0s
Fineract Build & Test - MySQL / build (push) Canceled after 0s
Fineract Build & Test - PostgreSQL / build (push) Canceled after 0s
Fineract Tests / build (push) Canceled after 0s
Fineract Kafka Smoke / build (push) Canceled after 0s
Fineract Docker build - MariaDB / build (push) Canceled after 0s
Fineract Docker build - PostgreSQL / build (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract ActiveMQ Smoke / build (push) Canceled after 0s
Fineract Build Progressive Loan Embeddable Jar & Test with a Sample Application / build (push) Canceled after 0s
Fineract Build & Test - MariaDB / build (push) Canceled after 0s
Fineract Build & Test - MySQL / build (push) Canceled after 0s
Fineract Build & Test - PostgreSQL / build (push) Canceled after 0s
Fineract Tests / build (push) Canceled after 0s
Fineract Kafka Smoke / build (push) Canceled after 0s
This commit is contained in:
committed by
Arnold Gálovics
parent
09879800e0
commit
872f97b6b6
+28
@@ -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.core.jersey.serializer.legacy;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface JsonLocalDateArrayFormat {}
|
||||||
+2
@@ -22,12 +22,14 @@ import java.io.Serializable;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JsonLocalDateArrayFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immutable data object represent the important time-line events of a savings account application.
|
* Immutable data object represent the important time-line events of a savings account application.
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@JsonLocalDateArrayFormat
|
||||||
public class SavingsAccountApplicationTimelineData implements Serializable {
|
public class SavingsAccountApplicationTimelineData implements Serializable {
|
||||||
|
|
||||||
private final LocalDate submittedOnDate;
|
private final LocalDate submittedOnDate;
|
||||||
|
|||||||
+2
@@ -25,6 +25,7 @@ import java.time.MonthDay;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||||
|
import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JsonLocalDateArrayFormat;
|
||||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||||
import org.apache.fineract.portfolio.charge.data.ChargeData;
|
import org.apache.fineract.portfolio.charge.data.ChargeData;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
||||||
@@ -33,6 +34,7 @@ import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
|||||||
* Immutable data object for Savings Account charge data.
|
* Immutable data object for Savings Account charge data.
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
|
@JsonLocalDateArrayFormat
|
||||||
public class SavingsAccountChargeData implements Serializable {
|
public class SavingsAccountChargeData implements Serializable {
|
||||||
|
|
||||||
private final Long id;
|
private final Long id;
|
||||||
|
|||||||
+13
-11
@@ -32,6 +32,7 @@ import lombok.Getter;
|
|||||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||||
|
import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JsonLocalDateArrayFormat;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
|
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
|
||||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||||
import org.apache.fineract.organisation.staff.data.StaffData;
|
import org.apache.fineract.organisation.staff.data.StaffData;
|
||||||
@@ -47,6 +48,7 @@ import org.apache.fineract.portfolio.tax.data.TaxGroupData;
|
|||||||
* Immutable data object representing a savings account.
|
* Immutable data object representing a savings account.
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
|
@JsonLocalDateArrayFormat
|
||||||
public final class SavingsAccountData implements Serializable {
|
public final class SavingsAccountData implements Serializable {
|
||||||
|
|
||||||
private final Long id;
|
private final Long id;
|
||||||
@@ -302,23 +304,23 @@ public final class SavingsAccountData implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Integer getInterestPostingPeriodTypeId() {
|
public Integer getInterestPostingPeriodTypeId() {
|
||||||
return this.interestPostingPeriodType.getId().intValue();
|
return this.interestPostingPeriodType != null ? this.interestPostingPeriodType.getId().intValue() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getDepositTypeId() {
|
public Integer getDepositTypeId() {
|
||||||
return this.depositType.getId().intValue();
|
return this.depositType != null ? this.depositType.getId().intValue() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getInterestCompoundingPeriodTypeId() {
|
public Integer getInterestCompoundingPeriodTypeId() {
|
||||||
return this.interestCompoundingPeriodType.getId().intValue();
|
return this.interestCompoundingPeriodType != null ? this.interestCompoundingPeriodType.getId().intValue() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getInterestCalculationTypeId() {
|
public Integer getInterestCalculationTypeId() {
|
||||||
return this.interestCalculationType.getId().intValue();
|
return this.interestCalculationType != null ? this.interestCalculationType.getId().intValue() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getInterestCalculationDaysInYearTypeId() {
|
public Integer getInterestCalculationDaysInYearTypeId() {
|
||||||
return this.interestCalculationDaysInYearType.getId().intValue();
|
return this.interestCalculationDaysInYearType != null ? this.interestCalculationDaysInYearType.getId().intValue() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SavingsAccountTransactionData findLastTransaction(final LocalDate date) {
|
public SavingsAccountTransactionData findLastTransaction(final LocalDate date) {
|
||||||
@@ -347,14 +349,14 @@ public final class SavingsAccountData implements Serializable {
|
|||||||
|
|
||||||
public LocalDate getActivationLocalDate() {
|
public LocalDate getActivationLocalDate() {
|
||||||
LocalDate activationLocalDate = null;
|
LocalDate activationLocalDate = null;
|
||||||
if (this.timeline.getActivatedOnDate() != null) {
|
if (this.timeline != null && this.timeline.getActivatedOnDate() != null) {
|
||||||
activationLocalDate = this.timeline.getActivatedOnDate();
|
activationLocalDate = this.timeline.getActivatedOnDate();
|
||||||
}
|
}
|
||||||
return activationLocalDate;
|
return activationLocalDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getLockinPeriodFrequencyTypeId() {
|
public Integer getLockinPeriodFrequencyTypeId() {
|
||||||
return this.lockinPeriodFrequencyType.getId().intValue();
|
return this.lockinPeriodFrequencyType != null ? this.lockinPeriodFrequencyType.getId().intValue() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Long> findCurrentTransactionIdsWithPivotDateConfig() {
|
public Collection<Long> findCurrentTransactionIdsWithPivotDateConfig() {
|
||||||
@@ -390,15 +392,15 @@ public final class SavingsAccountData implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<SavingsAccountTransactionData> getSavingsAccountTransactionsWithPivotConfig() {
|
public List<SavingsAccountTransactionData> getSavingsAccountTransactionsWithPivotConfig() {
|
||||||
return this.transactions.stream().toList();
|
return this.transactions != null ? this.transactions.stream().toList() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isAccrualBasedAccountingEnabledOnSavingsProduct() {
|
public Boolean isAccrualBasedAccountingEnabledOnSavingsProduct() {
|
||||||
return this.savingsProductData.isAccrualBasedAccountingEnabled();
|
return this.savingsProductData != null && this.savingsProductData.isAccrualBasedAccountingEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isCashBasedAccountingEnabledOnSavingsProduct() {
|
public Boolean isCashBasedAccountingEnabledOnSavingsProduct() {
|
||||||
return this.savingsProductData.isCashBasedAccountingEnabled();
|
return this.savingsProductData != null && this.savingsProductData.isCashBasedAccountingEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SavingsAccountData importInstanceGroup(Long groupId, Long productId, Long fieldOfficerId, LocalDate submittedOnDate,
|
public static SavingsAccountData importInstanceGroup(Long groupId, Long productId, Long fieldOfficerId, LocalDate submittedOnDate,
|
||||||
@@ -948,7 +950,7 @@ public final class SavingsAccountData implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Collection<SavingsAccountChargeData> charges() {
|
public Collection<SavingsAccountChargeData> charges() {
|
||||||
return (this.charges == null) ? new HashSet<SavingsAccountChargeData>() : this.charges;
|
return this.charges == null ? new HashSet<>() : this.charges;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDatatables(final List<DatatableData> datatables) {
|
public void setDatatables(final List<DatatableData> datatables) {
|
||||||
|
|||||||
+2
@@ -24,6 +24,7 @@ import java.time.LocalDate;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JsonLocalDateArrayFormat;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
@@ -36,6 +37,7 @@ import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod;
|
|||||||
* Immutable data object representing Savings Account summary information.
|
* Immutable data object representing Savings Account summary information.
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
|
@JsonLocalDateArrayFormat
|
||||||
public class SavingsAccountSummaryData implements Serializable {
|
public class SavingsAccountSummaryData implements Serializable {
|
||||||
|
|
||||||
private final CurrencyData currency;
|
private final CurrencyData currency;
|
||||||
|
|||||||
+2
@@ -31,6 +31,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
|
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
|
||||||
|
import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JsonLocalDateArrayFormat;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
@@ -51,6 +52,7 @@ import org.springframework.util.CollectionUtils;
|
|||||||
* Immutable data object representing a savings account transaction.
|
* Immutable data object representing a savings account transaction.
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
|
@JsonLocalDateArrayFormat
|
||||||
public final class SavingsAccountTransactionData implements Serializable {
|
public final class SavingsAccountTransactionData implements Serializable {
|
||||||
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|||||||
+17
-2
@@ -29,8 +29,13 @@ import org.apache.fineract.batch.command.CommandStrategy;
|
|||||||
import org.apache.fineract.batch.command.CommandStrategyUtils;
|
import org.apache.fineract.batch.command.CommandStrategyUtils;
|
||||||
import org.apache.fineract.batch.domain.BatchRequest;
|
import org.apache.fineract.batch.domain.BatchRequest;
|
||||||
import org.apache.fineract.batch.domain.BatchResponse;
|
import org.apache.fineract.batch.domain.BatchResponse;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
|
||||||
import org.apache.fineract.infrastructure.core.api.MutableUriInfo;
|
import org.apache.fineract.infrastructure.core.api.MutableUriInfo;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||||
import org.apache.fineract.portfolio.savings.api.SavingsAccountsApiResource;
|
import org.apache.fineract.portfolio.savings.api.SavingsAccountsApiResource;
|
||||||
|
import org.apache.fineract.portfolio.savings.api.SavingsApiSetConstants;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -39,6 +44,8 @@ import org.springframework.stereotype.Component;
|
|||||||
public class GetSavingsAccountByIdCommandStrategy implements CommandStrategy {
|
public class GetSavingsAccountByIdCommandStrategy implements CommandStrategy {
|
||||||
|
|
||||||
private final SavingsAccountsApiResource savingsAccountsApiResource;
|
private final SavingsAccountsApiResource savingsAccountsApiResource;
|
||||||
|
private final DefaultToApiJsonSerializer<SavingsAccountData> toApiJsonSerializer;
|
||||||
|
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BatchResponse execute(BatchRequest batchRequest, UriInfo uriInfo) {
|
public BatchResponse execute(BatchRequest batchRequest, UriInfo uriInfo) {
|
||||||
@@ -57,6 +64,7 @@ public class GetSavingsAccountByIdCommandStrategy implements CommandStrategy {
|
|||||||
|
|
||||||
String staffInSelectedOfficeOnly = null;
|
String staffInSelectedOfficeOnly = null;
|
||||||
String chargeStatus = null;
|
String chargeStatus = null;
|
||||||
|
String associations = null;
|
||||||
if (!queryParameters.isEmpty()) {
|
if (!queryParameters.isEmpty()) {
|
||||||
if (queryParameters.containsKey("staffInSelectedOfficeOnly")) {
|
if (queryParameters.containsKey("staffInSelectedOfficeOnly")) {
|
||||||
staffInSelectedOfficeOnly = queryParameters.get("staffInSelectedOfficeOnly");
|
staffInSelectedOfficeOnly = queryParameters.get("staffInSelectedOfficeOnly");
|
||||||
@@ -64,10 +72,17 @@ public class GetSavingsAccountByIdCommandStrategy implements CommandStrategy {
|
|||||||
if (queryParameters.containsKey("chargeStatus")) {
|
if (queryParameters.containsKey("chargeStatus")) {
|
||||||
chargeStatus = queryParameters.get("chargeStatus");
|
chargeStatus = queryParameters.get("chargeStatus");
|
||||||
}
|
}
|
||||||
|
if (queryParameters.containsKey("associations")) {
|
||||||
|
associations = queryParameters.get("associations");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final String responseBody = savingsAccountsApiResource.retrieveOne(savingsAccountId,
|
SavingsAccountData savingsAccountData = savingsAccountsApiResource.retrieveOne(savingsAccountId,
|
||||||
Boolean.parseBoolean(staffInSelectedOfficeOnly), chargeStatus, uriInfo);
|
Boolean.parseBoolean(staffInSelectedOfficeOnly), chargeStatus, associations, uriInfo);
|
||||||
|
|
||||||
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
|
final String responseBody = toApiJsonSerializer.serialize(settings, savingsAccountData,
|
||||||
|
SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS);
|
||||||
|
|
||||||
return new BatchResponse().setRequestId(batchRequest.getRequestId()).setStatusCode(HttpStatus.SC_OK).setBody(responseBody)
|
return new BatchResponse().setRequestId(batchRequest.getRequestId()).setStatusCode(HttpStatus.SC_OK).setBody(responseBody)
|
||||||
.setHeaders(batchRequest.getHeaders());
|
.setHeaders(batchRequest.getHeaders());
|
||||||
|
|||||||
+11
-4
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.infrastructure.core.jersey;
|
package org.apache.fineract.infrastructure.core.jersey;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
import com.fasterxml.jackson.databind.MapperFeature;
|
import com.fasterxml.jackson.databind.MapperFeature;
|
||||||
@@ -28,6 +29,7 @@ import java.util.List;
|
|||||||
import org.apache.fineract.infrastructure.core.jersey.converter.JsonConverter;
|
import org.apache.fineract.infrastructure.core.jersey.converter.JsonConverter;
|
||||||
import org.apache.fineract.infrastructure.core.jersey.serializer.JacksonDeserializerAdapter;
|
import org.apache.fineract.infrastructure.core.jersey.serializer.JacksonDeserializerAdapter;
|
||||||
import org.apache.fineract.infrastructure.core.jersey.serializer.JacksonSerializerAdapter;
|
import org.apache.fineract.infrastructure.core.jersey.serializer.JacksonSerializerAdapter;
|
||||||
|
import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JacksonLocalDateArrayModule;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||||
@@ -43,9 +45,14 @@ public class JerseyJacksonConverterConfig {
|
|||||||
mergedSerializers.addAll(jsonConverters.stream().map(JacksonSerializerAdapter::new).toList());
|
mergedSerializers.addAll(jsonConverters.stream().map(JacksonSerializerAdapter::new).toList());
|
||||||
List<JsonDeserializer<?>> mergedDeserializers = new ArrayList<>(deserializers);
|
List<JsonDeserializer<?>> mergedDeserializers = new ArrayList<>(deserializers);
|
||||||
mergedDeserializers.addAll(jsonConverters.stream().map(JacksonDeserializerAdapter::new).toList());
|
mergedDeserializers.addAll(jsonConverters.stream().map(JacksonDeserializerAdapter::new).toList());
|
||||||
return new Jackson2ObjectMapperBuilder().serializers(mergedSerializers.toArray(new JsonSerializer[0]))
|
ObjectMapper objectMapper = new Jackson2ObjectMapperBuilder() //
|
||||||
.serializationInclusion(JsonInclude.Include.NON_NULL).deserializers(mergedDeserializers.toArray(new JsonDeserializer[0]))
|
.serializers(mergedSerializers.toArray(new JsonSerializer[0])) //
|
||||||
.featuresToDisable(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
.serializationInclusion(JsonInclude.Include.NON_NULL) //
|
||||||
.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS).build();
|
.deserializers(mergedDeserializers.toArray(new JsonDeserializer[0])) //
|
||||||
|
.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) //
|
||||||
|
.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) //
|
||||||
|
.build(); //
|
||||||
|
objectMapper.registerModule(new JacksonLocalDateArrayModule());
|
||||||
|
return objectMapper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -24,9 +24,8 @@ import com.fasterxml.jackson.core.JsonToken;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
// This is registered in the JacksonLocalDateBeanSerializerModifier
|
||||||
public class LocalDateJsonConverter implements JsonConverter<LocalDate> {
|
public class LocalDateJsonConverter implements JsonConverter<LocalDate> {
|
||||||
|
|
||||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;
|
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* 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.core.jersey.serializer;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.BeanDescription;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||||
|
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
|
||||||
|
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
|
||||||
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.fineract.infrastructure.core.jersey.converter.LocalDateJsonConverter;
|
||||||
|
import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JacksonLocalDateArraySerializer;
|
||||||
|
import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JsonLocalDateArrayFormat;
|
||||||
|
|
||||||
|
@SuppressFBWarnings({ "SE_BAD_FIELD", "SE_BAD_FIELD_STORE" })
|
||||||
|
public class JacksonLocalDateBeanSerializerModifier extends BeanSerializerModifier {
|
||||||
|
|
||||||
|
private final JsonSerializer<?> localDateSerializer = new JacksonSerializerAdapter<>(new LocalDateJsonConverter());
|
||||||
|
private final JsonSerializer<?> localDateArraySerializer = new JacksonLocalDateArraySerializer();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
|
||||||
|
List<BeanPropertyWriter> beanProperties) {
|
||||||
|
if (beanDesc.getBeanClass().isAnnotationPresent(JsonLocalDateArrayFormat.class)) {
|
||||||
|
assignLocalDateSerializer(beanProperties, (JsonSerializer<Object>) localDateArraySerializer);
|
||||||
|
} else {
|
||||||
|
assignLocalDateSerializer(beanProperties, (JsonSerializer<Object>) localDateSerializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return beanProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assignLocalDateSerializer(List<BeanPropertyWriter> beanProperties, JsonSerializer<Object> serializer) {
|
||||||
|
for (BeanPropertyWriter writer : beanProperties) {
|
||||||
|
if (LocalDate.class.equals(writer.getPropertyType())) {
|
||||||
|
writer.assignSerializer(serializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -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.infrastructure.core.jersey.serializer.legacy;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
import org.apache.fineract.infrastructure.core.jersey.serializer.JacksonLocalDateBeanSerializerModifier;
|
||||||
|
|
||||||
|
public class JacksonLocalDateArrayModule extends SimpleModule {
|
||||||
|
|
||||||
|
public JacksonLocalDateArrayModule() {
|
||||||
|
setSerializerModifier(new JacksonLocalDateBeanSerializerModifier());
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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.core.jersey.serializer.legacy;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public class JacksonLocalDateArraySerializer extends JsonSerializer<LocalDate> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
|
||||||
|
if (value != null) {
|
||||||
|
gen.writeStartArray();
|
||||||
|
gen.writeNumber(value.getYear());
|
||||||
|
gen.writeNumber(value.getMonthValue());
|
||||||
|
gen.writeNumber(value.getDayOfMonth());
|
||||||
|
gen.writeEndArray();
|
||||||
|
} else {
|
||||||
|
gen.writeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-32
@@ -43,7 +43,6 @@ import jakarta.ws.rs.core.UriInfo;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -187,14 +186,10 @@ public class SavingsAccountsApiResource {
|
|||||||
@Path("{accountId}")
|
@Path("{accountId}")
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
@Operation(summary = "Retrieve a savings application/account", description = "Retrieves a savings application/account\n\n"
|
public SavingsAccountData retrieveOne(@PathParam("accountId") final Long accountId,
|
||||||
+ "Example Requests :\n" + "\n" + "savingsaccounts/1\n" + "\n" + "\n" + "savingsaccounts/1?associations=all")
|
@DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
|
||||||
@ApiResponses({
|
@DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus,
|
||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.GetSavingsAccountsAccountIdResponse.class))) })
|
@QueryParam("associations") final String associations, @Context final UriInfo uriInfo) {
|
||||||
public String retrieveOne(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId,
|
|
||||||
@DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
|
|
||||||
@DefaultValue("all") @QueryParam("chargeStatus") @Parameter(description = "chargeStatus") final String chargeStatus,
|
|
||||||
@Context final UriInfo uriInfo) {
|
|
||||||
|
|
||||||
return retrieveSavingAccount(accountId, null, staffInSelectedOfficeOnly, chargeStatus, uriInfo);
|
return retrieveSavingAccount(accountId, null, staffInSelectedOfficeOnly, chargeStatus, uriInfo);
|
||||||
}
|
}
|
||||||
@@ -203,15 +198,10 @@ public class SavingsAccountsApiResource {
|
|||||||
@Path("/external-id/{externalId}")
|
@Path("/external-id/{externalId}")
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
@Operation(summary = "Retrieve a savings application/account by external id", description = "Retrieves a savings application/account by external id\n\n"
|
public SavingsAccountData retrieveOne(@PathParam("externalId") final String externalId,
|
||||||
+ "Example Requests :\n" + "\n" + "savingsaccounts/external-id/ExternalId1\n" + "\n" + "\n"
|
@DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
|
||||||
+ "savingsaccounts/external-id/ExternalId1?associations=all")
|
@DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus,
|
||||||
@ApiResponses({
|
@QueryParam("associations") final String associations, @Context final UriInfo uriInfo) {
|
||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.GetSavingsAccountsAccountIdResponse.class))) })
|
|
||||||
public String retrieveOne(@PathParam("externalId") @Parameter(description = "externalId") final String externalId,
|
|
||||||
@DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
|
|
||||||
@DefaultValue("all") @QueryParam("chargeStatus") @Parameter(description = "chargeStatus") final String chargeStatus,
|
|
||||||
@Context final UriInfo uriInfo) {
|
|
||||||
|
|
||||||
return retrieveSavingAccount(null, externalId, staffInSelectedOfficeOnly, chargeStatus, uriInfo);
|
return retrieveSavingAccount(null, externalId, staffInSelectedOfficeOnly, chargeStatus, uriInfo);
|
||||||
}
|
}
|
||||||
@@ -484,8 +474,8 @@ public class SavingsAccountsApiResource {
|
|||||||
return toApiJsonSerializer.serialize(importDocumentId);
|
return toApiJsonSerializer.serialize(importDocumentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String retrieveSavingAccount(Long accountId, String externalId, boolean staffInSelectedOfficeOnly, String chargeStatus,
|
private SavingsAccountData retrieveSavingAccount(Long accountId, String externalId, boolean staffInSelectedOfficeOnly,
|
||||||
UriInfo uriInfo) {
|
String chargeStatus, UriInfo uriInfo) {
|
||||||
context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
|
context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
|
||||||
|
|
||||||
if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) {
|
if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) {
|
||||||
@@ -496,14 +486,7 @@ public class SavingsAccountsApiResource {
|
|||||||
accountId = getResolvedAccountId(accountId, accountExternalId);
|
accountId = getResolvedAccountId(accountId, accountExternalId);
|
||||||
final SavingsAccountData savingsAccount = savingsAccountReadPlatformService.retrieveOne(accountId);
|
final SavingsAccountData savingsAccount = savingsAccountReadPlatformService.retrieveOne(accountId);
|
||||||
|
|
||||||
final Set<String> mandatoryResponseParameters = new HashSet<>();
|
return populateTemplateAndAssociations(accountId, savingsAccount, staffInSelectedOfficeOnly, chargeStatus, uriInfo);
|
||||||
final SavingsAccountData savingsAccountTemplate = populateTemplateAndAssociations(accountId, savingsAccount,
|
|
||||||
staffInSelectedOfficeOnly, chargeStatus, uriInfo, mandatoryResponseParameters);
|
|
||||||
|
|
||||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
|
|
||||||
mandatoryResponseParameters);
|
|
||||||
return toApiJsonSerializer.serialize(settings, savingsAccountTemplate,
|
|
||||||
SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String updateSavingAccount(Long accountId, String externalId, String apiRequestBodyAsJson, String commandParam) {
|
private String updateSavingAccount(Long accountId, String externalId, String apiRequestBodyAsJson, String commandParam) {
|
||||||
@@ -629,8 +612,7 @@ public class SavingsAccountsApiResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SavingsAccountData populateTemplateAndAssociations(final Long accountId, final SavingsAccountData savingsAccount,
|
private SavingsAccountData populateTemplateAndAssociations(final Long accountId, final SavingsAccountData savingsAccount,
|
||||||
final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo,
|
final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo) {
|
||||||
final Set<String> mandatoryResponseParameters) {
|
|
||||||
|
|
||||||
Collection<SavingsAccountTransactionData> transactions = null;
|
Collection<SavingsAccountTransactionData> transactions = null;
|
||||||
Collection<SavingsAccountChargeData> charges = null;
|
Collection<SavingsAccountChargeData> charges = null;
|
||||||
@@ -643,7 +625,6 @@ public class SavingsAccountsApiResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (associationParameters.contains(SavingsApiConstants.transactions)) {
|
if (associationParameters.contains(SavingsApiConstants.transactions)) {
|
||||||
mandatoryResponseParameters.add(SavingsApiConstants.transactions);
|
|
||||||
final Collection<SavingsAccountTransactionData> currentTransactions = savingsAccountReadPlatformService
|
final Collection<SavingsAccountTransactionData> currentTransactions = savingsAccountReadPlatformService
|
||||||
.retrieveAllTransactions(accountId, DepositAccountType.SAVINGS_DEPOSIT);
|
.retrieveAllTransactions(accountId, DepositAccountType.SAVINGS_DEPOSIT);
|
||||||
if (!CollectionUtils.isEmpty(currentTransactions)) {
|
if (!CollectionUtils.isEmpty(currentTransactions)) {
|
||||||
@@ -652,7 +633,6 @@ public class SavingsAccountsApiResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (associationParameters.contains(SavingsApiConstants.charges)) {
|
if (associationParameters.contains(SavingsApiConstants.charges)) {
|
||||||
mandatoryResponseParameters.add(SavingsApiConstants.charges);
|
|
||||||
final Collection<SavingsAccountChargeData> currentCharges = savingsAccountChargeReadPlatformService
|
final Collection<SavingsAccountChargeData> currentCharges = savingsAccountChargeReadPlatformService
|
||||||
.retrieveSavingsAccountCharges(accountId, chargeStatus);
|
.retrieveSavingsAccountCharges(accountId, chargeStatus);
|
||||||
if (!CollectionUtils.isEmpty(currentCharges)) {
|
if (!CollectionUtils.isEmpty(currentCharges)) {
|
||||||
|
|||||||
+3
-3
@@ -56,13 +56,13 @@ public class PostInterestForSavingTasklet implements Tasklet {
|
|||||||
|
|
||||||
private final SavingsAccountReadPlatformService savingAccountReadPlatformService;
|
private final SavingsAccountReadPlatformService savingAccountReadPlatformService;
|
||||||
private final ConfigurationDomainService configurationDomainService;
|
private final ConfigurationDomainService configurationDomainService;
|
||||||
private final Queue<List<SavingsAccountData>> queue = new ArrayDeque<>();
|
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
@Qualifier(TaskExecutorConstant.CONFIGURABLE_TASK_EXECUTOR_BEAN_NAME)
|
@Qualifier(TaskExecutorConstant.CONFIGURABLE_TASK_EXECUTOR_BEAN_NAME)
|
||||||
private final ThreadPoolTaskExecutor taskExecutor;
|
private final ThreadPoolTaskExecutor taskExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
final Queue<List<SavingsAccountData>> queue = new ArrayDeque<>();
|
||||||
final int threadPoolSize = Integer.parseInt((String) chunkContext.getStepContext().getJobParameters().get("thread-pool-size"));
|
final int threadPoolSize = Integer.parseInt((String) chunkContext.getStepContext().getJobParameters().get("thread-pool-size"));
|
||||||
taskExecutor.setCorePoolSize(threadPoolSize);
|
taskExecutor.setCorePoolSize(threadPoolSize);
|
||||||
taskExecutor.setMaxPoolSize(threadPoolSize);
|
taskExecutor.setMaxPoolSize(threadPoolSize);
|
||||||
@@ -89,7 +89,7 @@ public class PostInterestForSavingTasklet implements Tasklet {
|
|||||||
log.debug("Starting Interest posting - total records - {}", totalFilteredRecords);
|
log.debug("Starting Interest posting - total records - {}", totalFilteredRecords);
|
||||||
List<SavingsAccountData> queueElement = queue.element();
|
List<SavingsAccountData> queueElement = queue.element();
|
||||||
maxSavingsIdInList = queueElement.get(queueElement.size() - 1).getId();
|
maxSavingsIdInList = queueElement.get(queueElement.size() - 1).getId();
|
||||||
postInterest(queue.remove(), threadPoolSize, backdatedTxnsAllowedTill, pageSize, maxSavingsIdInList);
|
postInterest(queue.remove(), threadPoolSize, backdatedTxnsAllowedTill, pageSize, maxSavingsIdInList, queue);
|
||||||
} while (!CollectionUtils.isEmpty(queue));
|
} while (!CollectionUtils.isEmpty(queue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ public class PostInterestForSavingTasklet implements Tasklet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void postInterest(List<SavingsAccountData> savingsAccounts, int threadPoolSize, final boolean backdatedTxnsAllowedTill,
|
private void postInterest(List<SavingsAccountData> savingsAccounts, int threadPoolSize, final boolean backdatedTxnsAllowedTill,
|
||||||
final int pageSize, Long maxSavingsIdInList) {
|
final int pageSize, Long maxSavingsIdInList, Queue<List<SavingsAccountData>> queue) {
|
||||||
List<Callable<Void>> posters = new ArrayList<>();
|
List<Callable<Void>> posters = new ArrayList<>();
|
||||||
int fromIndex = 0;
|
int fromIndex = 0;
|
||||||
int size = savingsAccounts.size();
|
int size = savingsAccounts.size();
|
||||||
|
|||||||
+12
-1
@@ -40,11 +40,16 @@ import jakarta.ws.rs.core.MediaType;
|
|||||||
import jakarta.ws.rs.core.UriInfo;
|
import jakarta.ws.rs.core.UriInfo;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
|
||||||
|
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.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
|
import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
|
||||||
import org.apache.fineract.portfolio.savings.api.SavingsAccountChargesApiResource;
|
import org.apache.fineract.portfolio.savings.api.SavingsAccountChargesApiResource;
|
||||||
import org.apache.fineract.portfolio.savings.api.SavingsAccountTransactionsApiResource;
|
import org.apache.fineract.portfolio.savings.api.SavingsAccountTransactionsApiResource;
|
||||||
import org.apache.fineract.portfolio.savings.api.SavingsAccountsApiResource;
|
import org.apache.fineract.portfolio.savings.api.SavingsAccountsApiResource;
|
||||||
|
import org.apache.fineract.portfolio.savings.api.SavingsApiSetConstants;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
|
||||||
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
|
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
|
||||||
import org.apache.fineract.portfolio.self.client.service.AppuserClientMapperReadService;
|
import org.apache.fineract.portfolio.self.client.service.AppuserClientMapperReadService;
|
||||||
import org.apache.fineract.portfolio.self.savings.data.SelfSavingsAccountConstants;
|
import org.apache.fineract.portfolio.self.savings.data.SelfSavingsAccountConstants;
|
||||||
@@ -66,6 +71,8 @@ public class SelfSavingsApiResource {
|
|||||||
private final AppuserSavingsMapperReadService appuserSavingsMapperReadService;
|
private final AppuserSavingsMapperReadService appuserSavingsMapperReadService;
|
||||||
private final SelfSavingsDataValidator dataValidator;
|
private final SelfSavingsDataValidator dataValidator;
|
||||||
private final AppuserClientMapperReadService appUserClientMapperReadService;
|
private final AppuserClientMapperReadService appUserClientMapperReadService;
|
||||||
|
private final DefaultToApiJsonSerializer<SavingsAccountData> toApiJsonSerializer;
|
||||||
|
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("{accountId}")
|
@Path("{accountId}")
|
||||||
@@ -77,6 +84,7 @@ public class SelfSavingsApiResource {
|
|||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SelfSavingsApiResourceSwagger.GetSelfSavingsAccountsResponse.class))) })
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SelfSavingsApiResourceSwagger.GetSelfSavingsAccountsResponse.class))) })
|
||||||
public String retrieveSavings(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId,
|
public String retrieveSavings(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId,
|
||||||
@DefaultValue("all") @QueryParam("chargeStatus") @Parameter(description = "chargeStatus") final String chargeStatus,
|
@DefaultValue("all") @QueryParam("chargeStatus") @Parameter(description = "chargeStatus") final String chargeStatus,
|
||||||
|
@QueryParam("associations") @Parameter(description = "associations") final String associations,
|
||||||
@Context final UriInfo uriInfo) {
|
@Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
this.dataValidator.validateRetrieveSavings(uriInfo);
|
this.dataValidator.validateRetrieveSavings(uriInfo);
|
||||||
@@ -84,7 +92,10 @@ public class SelfSavingsApiResource {
|
|||||||
validateAppuserSavingsAccountMapping(accountId);
|
validateAppuserSavingsAccountMapping(accountId);
|
||||||
|
|
||||||
final boolean staffInSelectedOfficeOnly = false;
|
final boolean staffInSelectedOfficeOnly = false;
|
||||||
return this.savingsAccountsApiResource.retrieveOne(accountId, staffInSelectedOfficeOnly, chargeStatus, uriInfo);
|
SavingsAccountData result = this.savingsAccountsApiResource.retrieveOne(accountId, staffInSelectedOfficeOnly, chargeStatus,
|
||||||
|
associations, uriInfo);
|
||||||
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
|
return toApiJsonSerializer.serialize(settings, result, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
|||||||
+11
-11
@@ -47,17 +47,17 @@ public class SavingsApiSetConstants extends SavingsApiConstants {
|
|||||||
* These parameters will match the class level parameters of {@link SavingsAccountData}. Where possible, we try to
|
* These parameters will match the class level parameters of {@link SavingsAccountData}. Where possible, we try to
|
||||||
* get response parameters to match those of request parameters.
|
* get response parameters to match those of request parameters.
|
||||||
*/
|
*/
|
||||||
protected static final Set<String> SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
|
public static final Set<String> SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName, accountNoParamName,
|
||||||
accountNoParamName, externalIdParamName, statusParamName, activatedOnDateParamName, staffIdParamName, clientIdParamName,
|
externalIdParamName, statusParamName, activatedOnDateParamName, staffIdParamName, clientIdParamName, "clientName",
|
||||||
"clientName", groupIdParamName, "groupName", "savingsProductId", "savingsProductName", "currency",
|
groupIdParamName, "groupName", "savingsProductId", "savingsProductName", "currency", nominalAnnualInterestRateParamName,
|
||||||
nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestCalculationTypeParamName,
|
interestCompoundingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
|
||||||
interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName,
|
minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
|
||||||
lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
|
withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, feeAmountParamName,
|
||||||
withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, "summary", "transactions", "productOptions",
|
feeOnMonthDayParamName, "summary", "transactions", "productOptions", "interestCompoundingPeriodTypeOptions",
|
||||||
"interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions",
|
"interestPostingPeriodTypeOptions", "interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions",
|
||||||
"interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee",
|
"lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee", "annualFee", onHoldFundsParamName,
|
||||||
"annualFee", onHoldFundsParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName,
|
nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, datatables, savingsAmountOnHold,
|
||||||
datatables, savingsAmountOnHold, accountMappingForPaymentParamName, interestPostedTillDate));
|
accountMappingForPaymentParamName, interestPostedTillDate));
|
||||||
|
|
||||||
protected static final Set<String> SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
|
protected static final Set<String> SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
|
||||||
Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName,
|
Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName,
|
||||||
|
|||||||
+2
-1
@@ -43,6 +43,7 @@ import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData;
|
|||||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.transaction.annotation.Isolation;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -61,7 +62,7 @@ public class SavingsSchedularInterestPoster {
|
|||||||
private Collection<SavingsAccountData> savingAccounts;
|
private Collection<SavingsAccountData> savingAccounts;
|
||||||
private boolean backdatedTxnsAllowedTill;
|
private boolean backdatedTxnsAllowedTill;
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(isolation = Isolation.READ_UNCOMMITTED, rollbackFor = Exception.class)
|
||||||
public void postInterest() throws JobExecutionException {
|
public void postInterest() throws JobExecutionException {
|
||||||
if (!savingAccounts.isEmpty()) {
|
if (!savingAccounts.isEmpty()) {
|
||||||
List<Throwable> errors = new ArrayList<>();
|
List<Throwable> errors = new ArrayList<>();
|
||||||
|
|||||||
+7
-8
@@ -18,18 +18,19 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.integrationtests;
|
package org.apache.fineract.integrationtests;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import org.apache.fineract.client.models.DeleteSavingsAccountsAccountIdResponse;
|
import org.apache.fineract.client.models.DeleteSavingsAccountsAccountIdResponse;
|
||||||
import org.apache.fineract.client.models.GetSavingsAccountsAccountIdResponse;
|
|
||||||
import org.apache.fineract.client.models.PostSavingsAccountsAccountIdRequest;
|
import org.apache.fineract.client.models.PostSavingsAccountsAccountIdRequest;
|
||||||
import org.apache.fineract.client.models.PostSavingsAccountsAccountIdResponse;
|
import org.apache.fineract.client.models.PostSavingsAccountsAccountIdResponse;
|
||||||
import org.apache.fineract.client.models.PostSavingsAccountsRequest;
|
import org.apache.fineract.client.models.PostSavingsAccountsRequest;
|
||||||
import org.apache.fineract.client.models.PostSavingsAccountsResponse;
|
import org.apache.fineract.client.models.PostSavingsAccountsResponse;
|
||||||
import org.apache.fineract.client.models.PutSavingsAccountsAccountIdRequest;
|
import org.apache.fineract.client.models.PutSavingsAccountsAccountIdRequest;
|
||||||
import org.apache.fineract.client.models.PutSavingsAccountsAccountIdResponse;
|
import org.apache.fineract.client.models.PutSavingsAccountsAccountIdResponse;
|
||||||
|
import org.apache.fineract.client.models.SavingsAccountData;
|
||||||
import org.apache.fineract.client.util.Calls;
|
import org.apache.fineract.client.util.Calls;
|
||||||
import org.apache.fineract.integrationtests.client.IntegrationTest;
|
import org.apache.fineract.integrationtests.client.IntegrationTest;
|
||||||
import org.junit.jupiter.api.Order;
|
import org.junit.jupiter.api.Order;
|
||||||
@@ -100,13 +101,12 @@ public class SavingsAccountsExternalIdTest extends IntegrationTest {
|
|||||||
request.dateFormat(dateFormat);
|
request.dateFormat(dateFormat);
|
||||||
request.setLocale(locale);
|
request.setLocale(locale);
|
||||||
request.setActivatedOnDate(formattedDate);
|
request.setActivatedOnDate(formattedDate);
|
||||||
Response<GetSavingsAccountsAccountIdResponse> response = okR(
|
Response<SavingsAccountData> response = okR(fineractClient().savingsAccounts.retrieveOne26(EXTERNAL_ID, false, null, "all"));
|
||||||
fineractClient().savingsAccounts.retrieveOne26(EXTERNAL_ID, false, "all"));
|
|
||||||
|
|
||||||
assertThat(response.isSuccessful()).isTrue();
|
assertThat(response.isSuccessful()).isTrue();
|
||||||
assertThat(response.body()).isNotNull();
|
assertThat(response.body()).isNotNull();
|
||||||
assertThat(response.body().getStatus().getCode()).isEqualTo("savingsAccountStatusType.approved");
|
assertThat(response.body().getStatus().getCode()).isEqualTo("savingsAccountStatusType.approved");
|
||||||
assertThat(response.body().getNominalAnnualInterestRate()).isEqualTo(5.999);
|
assertThat(response.body().getNominalAnnualInterestRate()).isEqualByComparingTo(BigDecimal.valueOf(5.999));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -129,8 +129,7 @@ public class SavingsAccountsExternalIdTest extends IntegrationTest {
|
|||||||
request.dateFormat(dateFormat);
|
request.dateFormat(dateFormat);
|
||||||
request.setLocale(locale);
|
request.setLocale(locale);
|
||||||
request.setActivatedOnDate(formattedDate);
|
request.setActivatedOnDate(formattedDate);
|
||||||
Response<GetSavingsAccountsAccountIdResponse> response = okR(
|
Response<SavingsAccountData> response = okR(fineractClient().savingsAccounts.retrieveOne26(EXTERNAL_ID, false, null, "all"));
|
||||||
fineractClient().savingsAccounts.retrieveOne26(EXTERNAL_ID, false, "all"));
|
|
||||||
|
|
||||||
assertThat(response.isSuccessful()).isTrue();
|
assertThat(response.isSuccessful()).isTrue();
|
||||||
assertThat(response.body()).isNotNull();
|
assertThat(response.body()).isNotNull();
|
||||||
@@ -159,8 +158,8 @@ public class SavingsAccountsExternalIdTest extends IntegrationTest {
|
|||||||
request.dateFormat(dateFormat);
|
request.dateFormat(dateFormat);
|
||||||
request.setLocale(locale);
|
request.setLocale(locale);
|
||||||
request.setActivatedOnDate(formattedDate);
|
request.setActivatedOnDate(formattedDate);
|
||||||
Response<GetSavingsAccountsAccountIdResponse> response = Calls
|
Response<SavingsAccountData> response = Calls
|
||||||
.executeU(fineractClient().savingsAccounts.retrieveOne26(EXTERNAL_ID, false, "all"));
|
.executeU(fineractClient().savingsAccounts.retrieveOne26(EXTERNAL_ID, false, null, "all"));
|
||||||
|
|
||||||
assertThat(response.raw().code()).isEqualTo(404);
|
assertThat(response.raw().code()).isEqualTo(404);
|
||||||
}
|
}
|
||||||
|
|||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* 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.integrationtests.savings;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import org.apache.fineract.client.models.PostSavingsProductsRequest;
|
||||||
|
import org.apache.fineract.client.models.PostSavingsProductsResponse;
|
||||||
|
import org.apache.fineract.client.models.SavingsAccountData;
|
||||||
|
import org.apache.fineract.client.models.SavingsAccountTransactionData;
|
||||||
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.apache.fineract.integrationtests.savings.base.BaseSavingsIntegrationTest;
|
||||||
|
import org.junit.jupiter.api.Order;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@Order(2)
|
||||||
|
public class SavingsInterestPostingTest extends BaseSavingsIntegrationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSavingsInterestPosting_Works_ForMultipleAccounts() {
|
||||||
|
List<AccountWithDepositAmount> accounts = new ArrayList<>();
|
||||||
|
|
||||||
|
runAt("01 January 2023", () -> {
|
||||||
|
// Create Client
|
||||||
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
|
||||||
|
// Create product
|
||||||
|
PostSavingsProductsRequest productsRequest = dailyInterestPostingProduct();
|
||||||
|
PostSavingsProductsResponse product = createProduct(productsRequest);
|
||||||
|
Integer productId = product.getResourceId();
|
||||||
|
|
||||||
|
// Create accounts
|
||||||
|
IntStream.range(0, 200)//
|
||||||
|
.parallel().mapToObj(i -> applySavingsRequest(clientId, productId.longValue(), "01 January 2023"))//
|
||||||
|
.map(this::applySavingsAccount) //
|
||||||
|
.mapToLong(savingsAccount -> savingsAccount.getResourceId().longValue()) //
|
||||||
|
.forEach(savingsId -> {
|
||||||
|
approveSavingsAccount(savingsId, "01 January 2023");
|
||||||
|
activateSavingsAccount(savingsId, "01 January 2023");
|
||||||
|
accounts.add(new AccountWithDepositAmount(savingsId));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
runFromToInclusive("02 January 2023", "10 January 2023", (date) -> {
|
||||||
|
SecureRandom rnd = new SecureRandom();
|
||||||
|
accounts.parallelStream().forEach(account -> {
|
||||||
|
long lAmount = rnd.nextLong(10_000_000L);
|
||||||
|
BigDecimal amount = BigDecimal.valueOf(lAmount);
|
||||||
|
deposit(account.savingsId, date, amount);
|
||||||
|
account.addDeposit(amount);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
runAt("11 January 2023", () -> {
|
||||||
|
String jobName = "Post Interest For Savings";
|
||||||
|
schedulerJobHelper.executeAndAwaitJob(jobName);
|
||||||
|
|
||||||
|
accounts.parallelStream().forEach(account -> {
|
||||||
|
SavingsAccountData accountData = getSavingsAccount(account.savingsId);
|
||||||
|
BigDecimal accountBalance = accountData.getSummary().getAccountBalance();
|
||||||
|
BigDecimal accountBalanceByTransactions = accountData.getTransactions().stream()
|
||||||
|
.map(SavingsAccountTransactionData::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
assertThat(accountBalance)
|
||||||
|
.withFailMessage("Account balance (%s) and calculated balance (%s) from transactions do not match", accountBalance,
|
||||||
|
accountBalanceByTransactions)
|
||||||
|
.isEqualByComparingTo(accountBalanceByTransactions);
|
||||||
|
BigDecimal summedDepositAmount = accountData.getTransactions().stream().filter(tx -> tx.getTransactionType().getDeposit())
|
||||||
|
.map(SavingsAccountTransactionData::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
assertThat(summedDepositAmount).withFailMessage(
|
||||||
|
"Calculated balance (%s) from deposit transactions and submitted deposit balance (%s) do not match", accountBalance,
|
||||||
|
summedDepositAmount).isEqualByComparingTo(account.depositedAmount);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static class AccountWithDepositAmount {
|
||||||
|
|
||||||
|
public Long savingsId;
|
||||||
|
public BigDecimal depositedAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
AccountWithDepositAmount(Long savingsId) {
|
||||||
|
this.savingsId = savingsId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDeposit(BigDecimal amount) {
|
||||||
|
depositedAmount = depositedAmount.add(amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+249
@@ -0,0 +1,249 @@
|
|||||||
|
/**
|
||||||
|
* 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.integrationtests.savings.base;
|
||||||
|
|
||||||
|
import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE;
|
||||||
|
|
||||||
|
import io.restassured.builder.RequestSpecBuilder;
|
||||||
|
import io.restassured.builder.ResponseSpecBuilder;
|
||||||
|
import io.restassured.http.ContentType;
|
||||||
|
import io.restassured.specification.RequestSpecification;
|
||||||
|
import io.restassured.specification.ResponseSpecification;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.ToString;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.client.models.BusinessDateRequest;
|
||||||
|
import org.apache.fineract.client.models.PostSavingsAccountTransactionsRequest;
|
||||||
|
import org.apache.fineract.client.models.PostSavingsAccountTransactionsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostSavingsAccountsAccountIdRequest;
|
||||||
|
import org.apache.fineract.client.models.PostSavingsAccountsAccountIdResponse;
|
||||||
|
import org.apache.fineract.client.models.PostSavingsAccountsRequest;
|
||||||
|
import org.apache.fineract.client.models.PostSavingsAccountsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostSavingsProductsRequest;
|
||||||
|
import org.apache.fineract.client.models.PostSavingsProductsResponse;
|
||||||
|
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
|
||||||
|
import org.apache.fineract.client.models.SavingsAccountData;
|
||||||
|
import org.apache.fineract.client.models.SavingsAccountTransactionData;
|
||||||
|
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
|
||||||
|
import org.apache.fineract.integrationtests.client.IntegrationTest;
|
||||||
|
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.system.CodeHelper;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class BaseSavingsIntegrationTest extends IntegrationTest {
|
||||||
|
|
||||||
|
protected static final String DATETIME_PATTERN = "dd MMMM yyyy";
|
||||||
|
|
||||||
|
static {
|
||||||
|
Utils.initializeRESTAssured();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final ResponseSpecification responseSpec = createResponseSpecification(Matchers.is(200));
|
||||||
|
private final String fullAdminAuthKey = getFullAdminAuthKey();
|
||||||
|
protected final RequestSpecification requestSpec = createRequestSpecification(fullAdminAuthKey);
|
||||||
|
|
||||||
|
protected BusinessDateHelper businessDateHelper = new BusinessDateHelper();
|
||||||
|
protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
|
||||||
|
protected GlobalConfigurationHelper globalConfigurationHelper = new GlobalConfigurationHelper();
|
||||||
|
protected final CodeHelper codeHelper = new CodeHelper();
|
||||||
|
protected JournalEntryHelper journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec);
|
||||||
|
protected ClientHelper clientHelper = new ClientHelper(requestSpec, responseSpec);
|
||||||
|
protected SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec);
|
||||||
|
|
||||||
|
protected void runFromToInclusive(String fromDate, String toDate, Consumer<String> runnable) {
|
||||||
|
DateTimeFormatter format = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
|
||||||
|
LocalDate startDate = LocalDate.parse(fromDate, format);
|
||||||
|
LocalDate endDate = LocalDate.parse(toDate, format);
|
||||||
|
|
||||||
|
LocalDate currentDate = startDate;
|
||||||
|
while (currentDate.isBefore(endDate) || currentDate.isEqual(endDate)) {
|
||||||
|
runAt(format.format(currentDate), runnable);
|
||||||
|
currentDate = currentDate.plusDays(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void runAt(String date, Runnable runnable) {
|
||||||
|
runAt(date, (d) -> runnable.run());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void runAt(String date, Consumer<String> runnable) {
|
||||||
|
try {
|
||||||
|
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||||
|
new PutGlobalConfigurationsRequest().enabled(true));
|
||||||
|
businessDateHelper.updateBusinessDate(
|
||||||
|
new BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en"));
|
||||||
|
runnable.accept(date);
|
||||||
|
} finally {
|
||||||
|
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||||
|
new PutGlobalConfigurationsRequest().enabled(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PostSavingsProductsRequest dailyInterestPostingProduct() {
|
||||||
|
return new PostSavingsProductsRequest().locale("en").name(Utils.uniqueRandomStringGenerator("DAILY_INTEREST", 6))//
|
||||||
|
.shortName(Utils.uniqueRandomStringGenerator("", 4))//
|
||||||
|
.description("Daily interest posting product") //
|
||||||
|
.nominalAnnualInterestRate(10.0) //
|
||||||
|
.digitsAfterDecimal(0) //
|
||||||
|
.currencyCode("EUR") //
|
||||||
|
.accountingRule(1) // none
|
||||||
|
.interestCalculationDaysInYearType(DaysInYearType.DAYS_365).interestCompoundingPeriodType(InterestPeriodType.DAILY)
|
||||||
|
.interestCalculationType(InterestCalculationType.AVERAGE_DAILY_BALANCE) //
|
||||||
|
.interestPostingPeriodType(InterestPeriodType.DAILY);//
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PostSavingsProductsResponse createProduct(PostSavingsProductsRequest productsRequest) {
|
||||||
|
return ok(fineractClient().savingsProducts.create13(productsRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PostSavingsAccountsRequest applySavingsRequest(Long clientId, Long productId, String submittedDate) {
|
||||||
|
return new PostSavingsAccountsRequest().clientId(clientId.intValue()).productId(productId.intValue()).dateFormat(DATETIME_PATTERN)
|
||||||
|
.locale("en").submittedOnDate(submittedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PostSavingsAccountsResponse applySavingsAccount(PostSavingsAccountsRequest request) {
|
||||||
|
return ok(fineractClient().savingsAccounts.submitApplication2(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PostSavingsAccountsAccountIdResponse approveSavingsAccount(Long savingsId, String date) {
|
||||||
|
PostSavingsAccountsAccountIdRequest request = new PostSavingsAccountsAccountIdRequest().dateFormat(DATETIME_PATTERN).locale("en")
|
||||||
|
.approvedOnDate(date);
|
||||||
|
return ok(fineractClient().savingsAccounts.handleCommands6(savingsId, request, "approve"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PostSavingsAccountsAccountIdResponse activateSavingsAccount(Long savingsId, String date) {
|
||||||
|
PostSavingsAccountsAccountIdRequest request = new PostSavingsAccountsAccountIdRequest().dateFormat(DATETIME_PATTERN).locale("en")
|
||||||
|
.activatedOnDate(date);
|
||||||
|
return ok(fineractClient().savingsAccounts.handleCommands6(savingsId, request, "activate"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PostSavingsAccountTransactionsResponse deposit(Long savingsId, String date, BigDecimal amount) {
|
||||||
|
PostSavingsAccountTransactionsRequest request = new PostSavingsAccountTransactionsRequest() //
|
||||||
|
.dateFormat(DATETIME_PATTERN) //
|
||||||
|
.locale("en") //
|
||||||
|
.paymentTypeId(1).transactionAmount(amount) //
|
||||||
|
.transactionDate(date); //
|
||||||
|
return ok(fineractClient().savingsTransactions.transaction2(savingsId, request, "deposit"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SavingsAccountData getSavingsAccount(Long savingsId) {
|
||||||
|
return ok(fineractClient().savingsAccounts.retrieveOne25(savingsId, false, null, "transactions"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<SavingsAccountTransactionData> getTransactions(Long savingsId) {
|
||||||
|
return ok(fineractClient().savingsAccounts.retrieveOne25(savingsId, false, null, "transactions")).getTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void verifyNoTransactions(Long savingsId) {
|
||||||
|
verifyTransactions(savingsId, (Transaction[]) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void verifyTransactions(Long savingsId, Transaction... transactions) {
|
||||||
|
SavingsAccountData savingsDetails = ok(fineractClient().savingsAccounts.retrieveOne25(savingsId, false, null, "transactions"));
|
||||||
|
if (transactions == null || transactions.length == 0) {
|
||||||
|
Assertions.assertTrue(savingsDetails.getTransactions().isEmpty(), "No transaction is expected on savings account " + savingsId);
|
||||||
|
} else {
|
||||||
|
Assertions.assertEquals(transactions.length, savingsDetails.getTransactions().size());
|
||||||
|
Arrays.stream(transactions).forEach(tr -> {
|
||||||
|
Optional<SavingsAccountTransactionData> optTx = savingsDetails.getTransactions().stream()
|
||||||
|
.filter(item -> Objects.requireNonNull(item.getAmount()).compareTo(BigDecimal.valueOf(tr.amount)) == 0 //
|
||||||
|
&& Objects.equals(item.getTransactionType().getValue(), tr.type) //
|
||||||
|
&& Objects.equals(item.getDate(), LocalDate.parse(tr.date, dateTimeFormatter)))
|
||||||
|
.findFirst();
|
||||||
|
Assertions.assertTrue(optTx.isPresent(), "Required transaction not found: " + tr + " on savings account " + savingsId);
|
||||||
|
|
||||||
|
SavingsAccountTransactionData tx = optTx.get();
|
||||||
|
|
||||||
|
if (tr.reversed != null) {
|
||||||
|
Assertions.assertEquals(tr.reversed, tx.getReversed(),
|
||||||
|
"Transaction is not reversed: " + tr + " on savings account " + savingsId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestSpecification createRequestSpecification(String authKey) {
|
||||||
|
RequestSpecification requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||||
|
requestSpec.header("Authorization", "Basic " + authKey);
|
||||||
|
requestSpec.header("Fineract-Platform-TenantId", "default");
|
||||||
|
return requestSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ResponseSpecification createResponseSpecification(Matcher<Integer> statusCodeMatcher) {
|
||||||
|
return new ResponseSpecBuilder().expectStatusCode(statusCodeMatcher).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFullAdminAuthKey() {
|
||||||
|
return Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ToString
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class Transaction {
|
||||||
|
|
||||||
|
Double amount;
|
||||||
|
String type;
|
||||||
|
String date;
|
||||||
|
Boolean reversed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InterestPeriodType {
|
||||||
|
|
||||||
|
public static final int DAILY = 1;
|
||||||
|
public static final int MONTHLY = 4;
|
||||||
|
public static final int QUATERLY = 5;
|
||||||
|
public static final int BIANNUAL = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InterestCalculationType {
|
||||||
|
|
||||||
|
public static final int DAILY_BALANCE = 1;
|
||||||
|
public static final int AVERAGE_DAILY_BALANCE = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InterestRecalculationCompoundingMethod {
|
||||||
|
|
||||||
|
public static final Integer NONE = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DaysInYearType {
|
||||||
|
|
||||||
|
public static final Integer INVALID = 0;
|
||||||
|
public static final Integer ACTUAL = 1;
|
||||||
|
public static final Integer DAYS_360 = 360;
|
||||||
|
public static final Integer DAYS_364 = 364;
|
||||||
|
public static final Integer DAYS_365 = 365;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user