From b73551fbe2b1cc58ab1f80873f8b85b372fb1a9c Mon Sep 17 00:00:00 2001 From: Juan Pablo Alvarez Hernandez Date: Fri, 27 Jun 2025 16:26:54 -0600 Subject: [PATCH] FINERACT-2314: Geolocation tracking --- .../commands/domain/CommandSource.java | 5 + .../core/config/FineractProperties.java | 9 ++ .../core/filters/ClientIpHolder.java} | 31 ++----- .../core/filters/GeolocationHeaderFilter.java | 93 +++++++++++++++++++ .../monetary/api/CurrenciesApiResource.java | 80 ++++++++++++++++ .../command/CurrencyUpdateCommand.java | 28 ++++++ ...st.java => CurrencyConfigurationData.java} | 15 ++- .../monetary/data/CurrencyData.java | 42 ++++----- ...onData.java => CurrencyUpdateRequest.java} | 29 +++--- .../monetary/data/CurrencyUpdateResponse.java | 61 ++++++++++++ .../ApplicationCurrencyRepositoryWrapper.java | 2 +- .../monetary/domain/MonetaryCurrency.java | 15 +-- .../handler/CurrencyUpdateCommandHandler.java | 30 +++--- .../monetary/mapper/CurrencyMapper.java | 37 ++++++++ ...urrencyCommandFromApiJsonDeserializer.java | 79 ---------------- .../{gson => }/MoneyDeserializer.java | 3 +- .../{gson => }/MoneySerializer.java | 3 +- .../CurrencyReadPlatformServiceImpl.java | 11 +-- .../service/CurrencyWritePlatformService.java | 7 +- ...ganisationCurrencyReadPlatformService.java | 4 +- ...sationCurrencyReadPlatformServiceImpl.java | 13 ++- .../global/CurrencyGlobalInitializerStep.java | 11 +-- ...ransferOutstandingInterestCalculation.java | 4 +- .../InvestorBusinessEventSerializer.java | 2 +- ...stractCumulativeLoanScheduleGenerator.java | 4 +- ...gBalanceInterestLoanScheduleGenerator.java | 7 +- ...tiveFlatInterestLoanScheduleGenerator.java | 7 +- .../mapper/LoanTransactionMapper.java | 6 +- ...estScheduleModelParserServiceGsonImpl.java | 4 +- .../api/JournalEntriesApiResourceSwagger.java | 21 ++++- .../fineract/commands/data/AuditData.java | 1 + .../service/AuditReadPlatformServiceImpl.java | 13 ++- .../core/config/SecurityConfig.java | 13 ++- .../LoanRepaymentBusinessEventSerializer.java | 2 +- .../monetary/api/CurrenciesApiResource.java | 88 ------------------ .../api/CurrenciesApiResourceSwagger.java | 63 ------------- ...WritePlatformServiceJpaRepositoryImpl.java | 38 ++------ .../OrganisationMonetaryConfiguration.java | 18 ++-- .../portfolio/group/data/GroupSummary.java | 55 ----------- .../loanaccount/data/LoanPointInTimeData.java | 3 +- .../service/LoanAssemblerImpl.java | 2 +- .../LoanProductRelatedDetailUpdateUtil.java | 2 +- .../src/main/resources/application.properties | 1 + .../db/changelog/tenant/changelog-tenant.xml | 1 + .../tenant/parts/0186_add_column_ip.xml | 32 +++++++ ...ntDelinquencyRangeEventSerializerTest.java | 8 +- .../savings/domain/SavingsProduct.java | 7 +- .../fineract/validation/messages.properties | 5 + .../validation/messages_en.properties | 5 + .../integrationtests/CurrenciesTest.java | 54 +++++------ .../common/CurrenciesHelper.java | 52 +++++------ 51 files changed, 590 insertions(+), 536 deletions(-) rename fineract-core/src/main/java/org/apache/fineract/{organisation/monetary/data/MoneyData.java => infrastructure/core/filters/ClientIpHolder.java} (55%) create mode 100644 fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/GeolocationHeaderFilter.java create mode 100644 fineract-core/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResource.java create mode 100644 fineract-core/src/main/java/org/apache/fineract/organisation/monetary/command/CurrencyUpdateCommand.java rename fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/{request/CurrencyRequest.java => CurrencyConfigurationData.java} (70%) rename fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/{ApplicationCurrencyConfigurationData.java => CurrencyUpdateRequest.java} (63%) create mode 100644 fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyUpdateResponse.java rename fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/handler/UpdateCurrencyCommandHandler.java => fineract-core/src/main/java/org/apache/fineract/organisation/monetary/handler/CurrencyUpdateCommandHandler.java (56%) create mode 100644 fineract-core/src/main/java/org/apache/fineract/organisation/monetary/mapper/CurrencyMapper.java delete mode 100644 fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/CurrencyCommandFromApiJsonDeserializer.java rename fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/{gson => }/MoneyDeserializer.java (94%) rename fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/{gson => }/MoneySerializer.java (93%) delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResource.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResourceSwagger.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupSummary.java create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0186_add_column_ip.xml diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java index 574d2cd0902..2a8d157212e 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSource.java @@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.filters.ClientIpHolder; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.useradministration.domain.AppUser; @@ -137,6 +138,9 @@ public class CommandSource extends AbstractPersistableCustom { @Column(name = "loan_external_id", length = 100) private ExternalId loanExternalId; + @Column(name = "client_ip", nullable = true) + private String clientIp; + @Column(name = "is_sanitized", nullable = false) private boolean sanitized; @@ -162,6 +166,7 @@ public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final Js .transactionId(command.getTransactionId()) // .creditBureauId(command.getCreditBureauId()) // .organisationCreditBureauId(command.getOrganisationCreditBureauId()) // + .clientIp(ClientIpHolder.getClientIp()) // .loanExternalId(command.getLoanExternalId()).sanitized(sanitized).build(); // } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java index b308ced998c..1b52aa8d967 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -50,6 +50,8 @@ public class FineractProperties { private FineractCorrelationProperties correlation; + private FineractGeolocationProperties geolocation; + private FineractPartitionedJob partitionedJob; private FineractRemoteJobMessageHandlerProperties remoteJobMessageHandler; @@ -153,6 +155,13 @@ public static class FineractCorrelationProperties { private String headerName; } + @Getter + @Setter + public static class FineractGeolocationProperties { + + private boolean enabled; + } + @Getter @Setter public static class FineractPartitionedJob { diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/MoneyData.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/ClientIpHolder.java similarity index 55% rename from fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/MoneyData.java rename to fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/ClientIpHolder.java index 3bca6dcd6fc..245ec96bdb9 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/MoneyData.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/ClientIpHolder.java @@ -16,35 +16,22 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.organisation.monetary.data; -import java.math.BigDecimal; +package org.apache.fineract.infrastructure.core.filters; -/** - * Immutable data object representing currency. - */ -public class MoneyData { +public class ClientIpHolder { - private final String code; - private final BigDecimal amount; - private final int decimalPlaces; + private static final ThreadLocal clientIpHolder = new ThreadLocal<>(); - public MoneyData(final String code, final BigDecimal amount, final int decimalPlaces) { - this.code = code; - this.amount = amount; - this.decimalPlaces = decimalPlaces; + public static void setClientIp(String ip) { + clientIpHolder.set(ip); } - public String getCode() { - return this.code; + public static String getClientIp() { + return clientIpHolder.get(); } - public BigDecimal getAmount() { - return this.amount; + public static void clear() { + clientIpHolder.remove(); } - - public int getDecimalPlaces() { - return this.decimalPlaces; - } - } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/GeolocationHeaderFilter.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/GeolocationHeaderFilter.java new file mode 100644 index 00000000000..01d8064bd98 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/filters/GeolocationHeaderFilter.java @@ -0,0 +1,93 @@ +/** + * 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.filters; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.springframework.web.filter.OncePerRequestFilter; + +@RequiredArgsConstructor +@Slf4j +public class GeolocationHeaderFilter extends OncePerRequestFilter { + + private final FineractProperties fineractProperties; + + private static final String[] IP_HEADER_CANDIDATES = { "X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP", + "HTTP_X_FORWARDED_FOR", "HTTP_X_FORWARDED", "HTTP_X_CLUSTER_CLIENT_IP", "HTTP_CLIENT_IP", "HTTP_FORWARDED_FOR", + "HTTP_FORWARDED", "HTTP_VIA", "REMOTE_ADDR" }; + + public static String getClientIpAddress(HttpServletRequest request) { + for (String header : IP_HEADER_CANDIDATES) { + String ip = request.getHeader(header); + if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { + log.debug("SEND IP : {}", ip); + return ip; + } + } + log.debug("getRemoteAddr method : {}", request.getRemoteAddr()); + return request.getRemoteAddr(); + } + + @Override + protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) + throws IOException, ServletException { + FineractProperties.FineractGeolocationProperties geolocationProperties = fineractProperties.getGeolocation(); + if (geolocationProperties.isEnabled()) { + handleClientIp(request, response, filterChain, geolocationProperties); + } else { + filterChain.doFilter(request, response); + } + + } + + private void handleClientIp(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain, + FineractProperties.FineractGeolocationProperties geolocationProperties) throws IOException, ServletException { + try { + String clientIpAddress = getClientIpAddress(request); + if (StringUtils.isNotBlank(clientIpAddress)) { + log.info("Found Client IP in header : {}", clientIpAddress); + ClientIpHolder.setClientIp(clientIpAddress); + } + filterChain.doFilter(request, response); + } catch (Exception e) { + e.printStackTrace(); + } finally { + ClientIpHolder.clear(); + } + } + + @Override + protected boolean isAsyncDispatch(final HttpServletRequest request) { + return false; + } + + @Override + protected boolean shouldNotFilterErrorDispatch() { + return false; + } + +} diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResource.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResource.java new file mode 100644 index 00000000000..efea32844bb --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResource.java @@ -0,0 +1,80 @@ +/** + * 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.monetary.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import java.util.UUID; +import java.util.function.Supplier; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.command.core.CommandPipeline; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.organisation.monetary.command.CurrencyUpdateCommand; +import org.apache.fineract.organisation.monetary.data.CurrencyConfigurationData; +import org.apache.fineract.organisation.monetary.data.CurrencyUpdateRequest; +import org.apache.fineract.organisation.monetary.data.CurrencyUpdateResponse; +import org.apache.fineract.organisation.monetary.service.OrganisationCurrencyReadPlatformService; +import org.springframework.stereotype.Component; + +@Path("/v1/currencies") +@Component +@Tag(name = "Currency", description = "Application related configuration around viewing/updating the currencies permitted for use within the MFI.") +@RequiredArgsConstructor +public class CurrenciesApiResource { + + private final OrganisationCurrencyReadPlatformService readPlatformService; + private final CommandPipeline commandPipeline; + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve Currency Configuration", description = """ + Returns the list of currencies permitted for use AND the list of currencies not selected (but available for selection). + + Example Requests: + + currencies + currencies?fields=selectedCurrencyOptions + """) + public CurrencyConfigurationData retrieveCurrencies() { + return readPlatformService.retrieveCurrencyConfiguration(); + } + + @PUT + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Update Currency Configuration", description = "Updates the list of currencies permitted for use.") + public CurrencyUpdateResponse updateCurrencies(CurrencyUpdateRequest request) { + final var command = new CurrencyUpdateCommand(); + + command.setId(UUID.randomUUID()); + command.setCreatedAt(DateUtils.getAuditOffsetDateTime()); + command.setPayload(request); + + final Supplier response = commandPipeline.send(command); + + return response.get(); + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/command/CurrencyUpdateCommand.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/command/CurrencyUpdateCommand.java new file mode 100644 index 00000000000..b93083edd0c --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/command/CurrencyUpdateCommand.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.organisation.monetary.command; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.organisation.monetary.data.CurrencyUpdateRequest; + +@Data +@EqualsAndHashCode(callSuper = true) +public class CurrencyUpdateCommand extends Command {} diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/request/CurrencyRequest.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyConfigurationData.java similarity index 70% rename from fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/request/CurrencyRequest.java rename to fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyConfigurationData.java index 97f988f4d53..9ff45a5b305 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/request/CurrencyRequest.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyConfigurationData.java @@ -16,14 +16,25 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.organisation.monetary.data.request; +package org.apache.fineract.organisation.monetary.data; import java.io.Serial; import java.io.Serializable; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; -public record CurrencyRequest(List currencies) implements Serializable { +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CurrencyConfigurationData implements Serializable { @Serial private static final long serialVersionUID = 1L; + + private List selectedCurrencyOptions; + private List currencyOptions; } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyData.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyData.java index 102cf3d164b..f407bf2ae54 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyData.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyData.java @@ -18,26 +18,29 @@ */ package org.apache.fineract.organisation.monetary.data; +import java.io.Serial; import java.io.Serializable; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; -import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; -/** - * Immutable data object representing currency. - */ -@Getter -@EqualsAndHashCode +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor public class CurrencyData implements Serializable { - private final String code; - private final String name; - private final int decimalPlaces; - private final Integer inMultiplesOf; - private final String displaySymbol; - private final String nameCode; - private final String displayLabel; + @Serial + private static final long serialVersionUID = 1L; + + private String code; + private String name; + private int decimalPlaces; + private Integer inMultiplesOf; + private String displaySymbol; + private String nameCode; + private String displayLabel; public static CurrencyData blank() { return new CurrencyData("", "", 0, 0, "", ""); @@ -90,11 +93,4 @@ private String generateDisplayLabel() { return builder.toString(); } - @org.mapstruct.Mapper(config = MapstructMapperConfig.class) - public interface Mapper { - - default CurrencyData map(MonetaryCurrency source) { - return source.toData(); - } - } } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/ApplicationCurrencyConfigurationData.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyUpdateRequest.java similarity index 63% rename from fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/ApplicationCurrencyConfigurationData.java rename to fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyUpdateRequest.java index cb3aabc4c5f..1c9cf91d95f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/ApplicationCurrencyConfigurationData.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyUpdateRequest.java @@ -18,25 +18,26 @@ */ package org.apache.fineract.organisation.monetary.data; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; -import java.util.Collection; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; -/** - * Immutable data object for application currency. - */ - -@Getter -@RequiredArgsConstructor -public class ApplicationCurrencyConfigurationData implements Serializable { +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CurrencyUpdateRequest implements Serializable { @Serial private static final long serialVersionUID = 1L; - @SuppressWarnings("unused") - private final Collection selectedCurrencyOptions; - @SuppressWarnings("unused") - private final Collection currencyOptions; + @NotNull(message = "{org.apache.fineract.organisation.monetary.currencies.not-null}") + @NotEmpty(message = "{org.apache.fineract.organisation.monetary.currencies.not-empty}") + private List currencies; } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyUpdateResponse.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyUpdateResponse.java new file mode 100644 index 00000000000..ab914ebe5b6 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/data/CurrencyUpdateResponse.java @@ -0,0 +1,61 @@ +/** + * 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.monetary.data; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import java.io.Serial; +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CurrencyUpdateResponse implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(example = """ + [ + "KES", + "BND", + "LBP", + "GHC", + "USD", + "XOF", + "AED", + "AMD" + ] + """) + private List currencies; + + @Deprecated(forRemoval = true) + @JsonProperty("changes") + public Map getChanges() { + // TODO: remove this one day... we should never use hashmaps in such trivial cases!!! + return Map.of("currencies", currencies); + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrencyRepositoryWrapper.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrencyRepositoryWrapper.java index 43c4a276829..94303ad843c 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrencyRepositoryWrapper.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrencyRepositoryWrapper.java @@ -51,7 +51,7 @@ public ApplicationCurrency findOneWithNotFoundDetection(final MonetaryCurrency c } final ApplicationCurrency applicationCurrency = ApplicationCurrency.from(defaultApplicationCurrency, - currency.getDigitsAfterDecimal(), currency.getCurrencyInMultiplesOf()); + currency.getDigitsAfterDecimal(), currency.getInMultiplesOf()); return applicationCurrency; } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java index dd50f7bdeca..334bcf7140d 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java @@ -20,8 +20,10 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; +import lombok.Getter; import org.apache.fineract.organisation.monetary.data.CurrencyData; +@Getter @Embeddable public class MonetaryCurrency { @@ -74,17 +76,4 @@ public CurrencyData toData() { } return currencyData; } - - public String getCode() { - return this.code; - } - - public int getDigitsAfterDecimal() { - return this.digitsAfterDecimal; - } - - public Integer getCurrencyInMultiplesOf() { - return this.inMultiplesOf; - } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/handler/UpdateCurrencyCommandHandler.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/handler/CurrencyUpdateCommandHandler.java similarity index 56% rename from fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/handler/UpdateCurrencyCommandHandler.java rename to fineract-core/src/main/java/org/apache/fineract/organisation/monetary/handler/CurrencyUpdateCommandHandler.java index 3421a4eff37..2a64efa814e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/handler/UpdateCurrencyCommandHandler.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/handler/CurrencyUpdateCommandHandler.java @@ -18,30 +18,26 @@ */ package org.apache.fineract.organisation.monetary.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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.command.core.CommandHandler; +import org.apache.fineract.organisation.monetary.data.CurrencyUpdateRequest; +import org.apache.fineract.organisation.monetary.data.CurrencyUpdateResponse; import org.apache.fineract.organisation.monetary.service.CurrencyWritePlatformService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -@Service -@CommandType(entity = "CURRENCY", action = "UPDATE") -public class UpdateCurrencyCommandHandler implements NewCommandSourceHandler { +@Slf4j +@Component +@RequiredArgsConstructor +public class CurrencyUpdateCommandHandler implements CommandHandler { private final CurrencyWritePlatformService writePlatformService; - @Autowired - public UpdateCurrencyCommandHandler(final CurrencyWritePlatformService writePlatformService) { - this.writePlatformService = writePlatformService; - } - @Transactional @Override - public CommandProcessingResult processCommand(final JsonCommand command) { - - return this.writePlatformService.updateAllowedCurrencies(command); + public CurrencyUpdateResponse handle(final Command command) { + return writePlatformService.updateAllowedCurrencies(command.getPayload()); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/mapper/CurrencyMapper.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/mapper/CurrencyMapper.java new file mode 100644 index 00000000000..19ee2120445 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/mapper/CurrencyMapper.java @@ -0,0 +1,37 @@ +/** + * 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.monetary.mapper; + +import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.mapstruct.Mapping; + +@org.mapstruct.Mapper(config = MapstructMapperConfig.class) +public interface CurrencyMapper { + + @Mapping(target = "nameCode", ignore = true) + @Mapping(target = "name", ignore = true) + @Mapping(target = "displaySymbol", ignore = true) + @Mapping(target = "displayLabel", ignore = true) + @Mapping(source = "code", target = "code") + @Mapping(source = "digitsAfterDecimal", target = "decimalPlaces") + @Mapping(source = "inMultiplesOf", target = "inMultiplesOf") + CurrencyData map(MonetaryCurrency source); +} diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/CurrencyCommandFromApiJsonDeserializer.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/CurrencyCommandFromApiJsonDeserializer.java deleted file mode 100644 index 0e422b312bc..00000000000 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/CurrencyCommandFromApiJsonDeserializer.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * 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.monetary.serialization; - -import com.google.gson.JsonElement; -import com.google.gson.reflect.TypeToken; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.apache.commons.lang3.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; - -@Component -public final class CurrencyCommandFromApiJsonDeserializer { - - public static final String CURRENCIES = "currencies"; - /** - * The parameters supported for this command. - */ - private static final Set SUPPORTED_PARAMETERS = new HashSet<>(List.of(CURRENCIES)); - - private final FromJsonHelper fromApiJsonHelper; - - @Autowired - public CurrencyCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) { - this.fromApiJsonHelper = fromApiJsonHelper; - } - - public void validateForUpdate(final String json) { - - if (StringUtils.isBlank(json)) { - throw new InvalidJsonException(); - } - - final Type typeOfMap = new TypeToken>() {}.getType(); - this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS); - - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(CURRENCIES); - - final JsonElement element = this.fromApiJsonHelper.parse(json); - final String[] currencies = this.fromApiJsonHelper.extractArrayNamed(CURRENCIES, element); - baseDataValidator.reset().parameter(CURRENCIES).value(currencies).arrayNotEmpty(); - - throwExceptionIfValidationWarningsExist(dataValidationErrors); - } - - private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", - dataValidationErrors); - } - } -} diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/gson/MoneyDeserializer.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/MoneyDeserializer.java similarity index 94% rename from fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/gson/MoneyDeserializer.java rename to fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/MoneyDeserializer.java index 5214d239a49..ba7a06783dd 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/gson/MoneyDeserializer.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/MoneyDeserializer.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.organisation.monetary.serialization.gson; +package org.apache.fineract.organisation.monetary.serialization; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; @@ -29,6 +29,7 @@ import org.apache.fineract.organisation.monetary.domain.Money; @AllArgsConstructor +@Deprecated(forRemoval = true) public class MoneyDeserializer implements JsonDeserializer { private final MathContext mc; diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/gson/MoneySerializer.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/MoneySerializer.java similarity index 93% rename from fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/gson/MoneySerializer.java rename to fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/MoneySerializer.java index b5375857ac4..af3fd0b006c 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/gson/MoneySerializer.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/MoneySerializer.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.organisation.monetary.serialization.gson; +package org.apache.fineract.organisation.monetary.serialization; import com.google.gson.JsonElement; import com.google.gson.JsonPrimitive; @@ -25,6 +25,7 @@ import java.lang.reflect.Type; import org.apache.fineract.organisation.monetary.domain.Money; +@Deprecated(forRemoval = true) public class MoneySerializer implements JsonSerializer { @Override diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java index 51276e1c6ad..d0eb3079da9 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyReadPlatformServiceImpl.java @@ -23,7 +23,6 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; @@ -31,15 +30,11 @@ @RequiredArgsConstructor public class CurrencyReadPlatformServiceImpl implements CurrencyReadPlatformService { - private final PlatformSecurityContext context; private final JdbcTemplate jdbcTemplate; - private final CurrencyMapper currencyRowMapper = new CurrencyMapper(); + private final CurrencyRowMapper currencyRowMapper = new CurrencyRowMapper(); @Override public List retrieveAllowedCurrencies() { - - this.context.authenticatedUser(); - final String sql = "select " + this.currencyRowMapper.schema() + " from m_organisation_currency c order by c.name"; return this.jdbcTemplate.query(sql, this.currencyRowMapper); // NOSONAR @@ -47,7 +42,6 @@ public List retrieveAllowedCurrencies() { @Override public List retrieveAllPlatformCurrencies() { - final String sql = "select " + this.currencyRowMapper.schema() + " from m_currency c order by c.name"; return this.jdbcTemplate.query(sql, this.currencyRowMapper); // NOSONAR @@ -55,13 +49,12 @@ public List retrieveAllPlatformCurrencies() { @Override public CurrencyData retrieveCurrency(final String code) { - final String sql = "select " + this.currencyRowMapper.schema() + " from m_currency c where c.code = ? order by c.name"; return this.jdbcTemplate.queryForObject(sql, this.currencyRowMapper, new Object[] { code }); // NOSONAR } - private static final class CurrencyMapper implements RowMapper { + private static final class CurrencyRowMapper implements RowMapper { @Override public CurrencyData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformService.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformService.java index 282fb4175e1..b75bcecd99c 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformService.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformService.java @@ -18,11 +18,10 @@ */ package org.apache.fineract.organisation.monetary.service; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.organisation.monetary.data.CurrencyUpdateRequest; +import org.apache.fineract.organisation.monetary.data.CurrencyUpdateResponse; public interface CurrencyWritePlatformService { - CommandProcessingResult updateAllowedCurrencies(JsonCommand command); - + CurrencyUpdateResponse updateAllowedCurrencies(CurrencyUpdateRequest request); } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformService.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformService.java index 5816bf3c3e9..2006568b477 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformService.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformService.java @@ -18,10 +18,10 @@ */ package org.apache.fineract.organisation.monetary.service; -import org.apache.fineract.organisation.monetary.data.ApplicationCurrencyConfigurationData; +import org.apache.fineract.organisation.monetary.data.CurrencyConfigurationData; public interface OrganisationCurrencyReadPlatformService { - ApplicationCurrencyConfigurationData retrieveCurrencyConfiguration(); + CurrencyConfigurationData retrieveCurrencyConfiguration(); } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformServiceImpl.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformServiceImpl.java index 746f266af50..868f55377be 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformServiceImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/service/OrganisationCurrencyReadPlatformServiceImpl.java @@ -18,10 +18,8 @@ */ package org.apache.fineract.organisation.monetary.service; -import java.util.Collection; import lombok.RequiredArgsConstructor; -import org.apache.fineract.organisation.monetary.data.ApplicationCurrencyConfigurationData; -import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.monetary.data.CurrencyConfigurationData; @RequiredArgsConstructor public class OrganisationCurrencyReadPlatformServiceImpl implements OrganisationCurrencyReadPlatformService { @@ -29,14 +27,15 @@ public class OrganisationCurrencyReadPlatformServiceImpl implements Organisation private final CurrencyReadPlatformService currencyReadPlatformService; @Override - public ApplicationCurrencyConfigurationData retrieveCurrencyConfiguration() { + public CurrencyConfigurationData retrieveCurrencyConfiguration() { - final Collection selectedCurrencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies(); - final Collection currencyOptions = this.currencyReadPlatformService.retrieveAllPlatformCurrencies(); + final var selectedCurrencyOptions = currencyReadPlatformService.retrieveAllowedCurrencies(); + final var currencyOptions = currencyReadPlatformService.retrieveAllPlatformCurrencies(); // remove selected currency options currencyOptions.removeAll(selectedCurrencyOptions); - return new ApplicationCurrencyConfigurationData(selectedCurrencyOptions, currencyOptions); + return CurrencyConfigurationData.builder().selectedCurrencyOptions(selectedCurrencyOptions).currencyOptions(currencyOptions) + .build(); } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/CurrencyGlobalInitializerStep.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/CurrencyGlobalInitializerStep.java index 3dbbba0d11c..ad8a2d52bf1 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/CurrencyGlobalInitializerStep.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/CurrencyGlobalInitializerStep.java @@ -21,15 +21,13 @@ import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; -import org.apache.fineract.client.models.CurrencyRequest; -import org.apache.fineract.client.models.PutCurrenciesResponse; +import org.apache.fineract.client.models.CurrencyUpdateRequest; import org.apache.fineract.client.services.CurrencyApi; import org.apache.fineract.test.support.TestContext; import org.apache.fineract.test.support.TestContextKey; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import retrofit2.Response; @RequiredArgsConstructor @Component @@ -42,9 +40,8 @@ public class CurrencyGlobalInitializerStep implements FineractGlobalInitializerS @Override public void initialize() throws Exception { - CurrencyRequest currencyRequest = new CurrencyRequest(); - Response putCurrenciesResponse = currencyApi.updateCurrencies(currencyRequest.currencies(CURRENCIES)) - .execute(); - TestContext.INSTANCE.set(TestContextKey.PUT_CURRENCIES_RESPONSE, putCurrenciesResponse); + var request = new CurrencyUpdateRequest(); + var response = currencyApi.updateCurrencies(request.currencies(CURRENCIES)).execute(); + TestContext.INSTANCE.set(TestContextKey.PUT_CURRENCIES_RESPONSE, response); } } diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerTransferOutstandingInterestCalculation.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerTransferOutstandingInterestCalculation.java index 46422b88c89..eac4d0d9f27 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerTransferOutstandingInterestCalculation.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerTransferOutstandingInterestCalculation.java @@ -25,6 +25,7 @@ import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.investor.config.InvestorModuleIsEnabledCondition; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.monetary.mapper.CurrencyMapper; import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; @@ -41,6 +42,7 @@ public class ExternalAssetOwnerTransferOutstandingInterestCalculation { private final LoanSummaryProviderDelegate loanSummaryDataProvider; private final ConfigurationDomainService configurationDomainService; private final LoanReadPlatformService loanReadPlatformService; + private final CurrencyMapper currencyMapper; private LoanSummaryDataProvider fetchLoanSummaryDataProvider(Loan loan) { return this.loanSummaryDataProvider.resolveLoanSummaryDataProvider(loan.getTransactionProcessingStrategyCode()); @@ -58,7 +60,7 @@ public BigDecimal calculateOutstandingInterest(Loan loan) { .map(i -> i.getInterestOutstanding(loan.getCurrency())).reduce(Money.zero(loan.getCurrency()), MathUtil::plus); BigDecimal notDuePayableAmount = fetchLoanSummaryDataProvider(loan) .computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(loan, data.getRepaymentSchedule().getPeriods(), - DateUtils.getBusinessLocalDate(), loan.getCurrency().toData(), duePayableAmount.getAmount()); + DateUtils.getBusinessLocalDate(), currencyMapper.map(loan.getCurrency()), duePayableAmount.getAmount()); yield MathUtil.add(duePayableAmount.getAmount(), notDuePayableAmount); } diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/serialization/serializer/investor/InvestorBusinessEventSerializer.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/serialization/serializer/investor/InvestorBusinessEventSerializer.java index 2549cf84dd2..c9daae3529d 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/service/serialization/serializer/investor/InvestorBusinessEventSerializer.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/serialization/serializer/investor/InvestorBusinessEventSerializer.java @@ -64,7 +64,7 @@ public class InvestorBusinessEventSerializer extends AbstractBusinessEventWithCu private static CurrencyDataV1 getCurrencyFromEvent(InvestorBusinessEvent event) { MonetaryCurrency loanCurrency = event.getLoan().getCurrency(); CurrencyDataV1 currency = CurrencyDataV1.newBuilder().setCode(loanCurrency.getCode()) - .setDecimalPlaces(loanCurrency.getDigitsAfterDecimal()).setInMultiplesOf(loanCurrency.getCurrencyInMultiplesOf()).build(); + .setDecimalPlaces(loanCurrency.getDigitsAfterDecimal()).setInMultiplesOf(loanCurrency.getInMultiplesOf()).build(); return currency; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java index 7ebfb54db97..af07a66b44a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java @@ -41,6 +41,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.monetary.mapper.CurrencyMapper; import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO; import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; @@ -69,6 +70,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanScheduleGenerator { private final LoanTransactionService loanTransactionService; + private final CurrencyMapper currencyMapper; @Override public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, @@ -2508,7 +2510,7 @@ public LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final Lo totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, - currency.toData(), applyInterestRecalculation, mc); + currencyMapper.map(currency), applyInterestRecalculation, mc); retainedInstallments.addAll(newRepaymentScheduleInstallments); loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations); loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getSummary().getTotalInterestCharged())); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java index a3ce6e25bc9..6087a6bd8e7 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java @@ -27,6 +27,7 @@ import java.util.TreeMap; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.monetary.mapper.CurrencyMapper; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; @@ -62,9 +63,9 @@ public class CumulativeDecliningBalanceInterestLoanScheduleGenerator extends Abs private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator; public CumulativeDecliningBalanceInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator, - final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator, - final LoanTransactionService loanTransactionService) { - super(loanTransactionService); + final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator, final LoanTransactionService loanTransactionService, + final CurrencyMapper currencyMapper) { + super(loanTransactionService, currencyMapper); this.scheduledDateGenerator = scheduledDateGenerator; this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeFlatInterestLoanScheduleGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeFlatInterestLoanScheduleGenerator.java index ad58d1f2f55..4d1be6331bd 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeFlatInterestLoanScheduleGenerator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeFlatInterestLoanScheduleGenerator.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.TreeMap; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.monetary.mapper.CurrencyMapper; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService; import org.springframework.stereotype.Component; @@ -36,9 +37,9 @@ public class CumulativeFlatInterestLoanScheduleGenerator extends AbstractCumulat private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator; public CumulativeFlatInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator, - final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator, - final LoanTransactionService loanTransactionService) { - super(loanTransactionService); + final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator, final LoanTransactionService loanTransactionService, + final CurrencyMapper currencyMapper) { + super(loanTransactionService, currencyMapper); this.scheduledDateGenerator = scheduledDateGenerator; this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java index 101165eab9f..97733c12af6 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java @@ -19,12 +19,14 @@ package org.apache.fineract.portfolio.loanaccount.mapper; import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.organisation.monetary.mapper.CurrencyMapper; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@Mapper(config = MapstructMapperConfig.class, uses = { LoanTransactionRelationMapper.class, LoanChargePaidByMapper.class }) +@Mapper(config = MapstructMapperConfig.class, uses = { LoanTransactionRelationMapper.class, LoanChargePaidByMapper.class, + CurrencyMapper.class }) public interface LoanTransactionMapper { @Mapping(target = "numberOfRepayments", ignore = true) @@ -46,6 +48,6 @@ public interface LoanTransactionMapper { @Mapping(target = "netDisbursalAmount", source = "loan.netDisbursalAmount") @Mapping(target = "transactionType", expression = "java(org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.transactionType(loanTransaction.getTypeOf()))") @Mapping(target = "paymentDetailData", expression = "java(loanTransaction.getPaymentDetail() != null ? loanTransaction.getPaymentDetail().toData() : null)") - @Mapping(target = "currency", expression = "java(loanTransaction.getLoan().getCurrency().toData())") + @Mapping(target = "currency", source = "loan.currency") LoanTransactionData mapLoanTransaction(LoanTransaction loanTransaction); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestScheduleModelParserServiceGsonImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestScheduleModelParserServiceGsonImpl.java index 067d66b011d..1b7d41ff8ac 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestScheduleModelParserServiceGsonImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestScheduleModelParserServiceGsonImpl.java @@ -31,8 +31,8 @@ import org.apache.fineract.infrastructure.core.serialization.gson.LocalDateAdapter; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; -import org.apache.fineract.organisation.monetary.serialization.gson.MoneyDeserializer; -import org.apache.fineract.organisation.monetary.serialization.gson.MoneySerializer; +import org.apache.fineract.organisation.monetary.serialization.MoneyDeserializer; +import org.apache.fineract.organisation.monetary.serialization.MoneySerializer; import org.apache.fineract.portfolio.loanproduct.calc.data.InterestPeriod; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod; diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResourceSwagger.java index 7aa7816d976..cc0aa3b0c9a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResourceSwagger.java @@ -22,7 +22,6 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; -import org.apache.fineract.organisation.monetary.api.CurrenciesApiResourceSwagger.CurrencyItem; import org.apache.fineract.portfolio.note.data.NoteData; import org.apache.fineract.portfolio.paymenttype.api.PaymentTypeApiResourceSwagger.GetPaymentTypesResponse; @@ -69,6 +68,26 @@ private PostJournalEntriesTransactionIdResponse() { public Long officeId; } + public static final class CurrencyItem { + + private CurrencyItem() {} + + @Schema(example = "USD") + public String code; + @Schema(example = "US Dollar") + public String name; + @Schema(example = "2") + public Integer decimalPlaces; + @Schema(example = "100") + public Integer inMultiplesOf; + @Schema(example = "$") + public String displaySymbol; + @Schema(example = "currency.USD") + public String nameCode; + @Schema(example = "US Dollar ($)") + public String displayLabel; + } + static final class EnumOptionType { private EnumOptionType() {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/data/AuditData.java b/fineract-provider/src/main/java/org/apache/fineract/commands/data/AuditData.java index 7cb15801731..6a415676e82 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/data/AuditData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/data/AuditData.java @@ -56,4 +56,5 @@ public final class AuditData implements Serializable { private final Long clientId; private final Long loanId; private final String url; + private final String ip; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java index 2d18544e1ae..f03d5c61e3b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/AuditReadPlatformServiceImpl.java @@ -111,10 +111,12 @@ public String schema(final boolean includeJson, final String hierarchy) { + commandAsJsonString + ", " + " o.name as officeName, gl.level_name as groupLevelName, g.display_name as groupName, c.display_name as clientName, " + " l.account_no as loanAccountNo, s.account_no as savingsAccountNo " + " from m_portfolio_command_source aud " - + " left join m_appuser mk on mk.id = aud.maker_id" + " left join m_appuser ck on ck.id = aud.checker_id" - + " left join m_office o on o.id = aud.office_id" + " left join m_group g on g.id = aud.group_id" - + " left join m_group_level gl on gl.id = g.level_id" + " left join m_client c on c.id = aud.client_id" - + " left join m_loan l on l.id = aud.loan_id" + " left join m_savings_account s on s.id = aud.savings_account_id" + + " l.account_no as loanAccountNo, s.account_no as savingsAccountNo, aud.client_ip as ip " + + " from m_portfolio_command_source aud " + " left join m_appuser mk on mk.id = aud.maker_id" + + " left join m_appuser ck on ck.id = aud.checker_id" + " left join m_office o on o.id = aud.office_id" + + " left join m_group g on g.id = aud.group_id" + " left join m_group_level gl on gl.id = g.level_id" + + " left join m_client c on c.id = aud.client_id" + " left join m_loan l on l.id = aud.loan_id" + + " left join m_savings_account s on s.id = aud.savings_account_id" + " left join r_enum_value ev on ev.enum_name = 'status' and ev.enum_id = aud.status"; // data scoping: head office (hierarchy = ".") can see all audit @@ -158,13 +160,14 @@ public AuditData mapRow(final ResultSet rs, @SuppressWarnings("unused") final in final String clientName = rs.getString("clientName"); final String loanAccountNo = rs.getString("loanAccountNo"); final String savingsAccountNo = rs.getString("savingsAccountNo"); + final String ip = (rs.getString("ip") != null) ? rs.getString("ip") : "SN/IP"; ZonedDateTime madeOnDate = madeOnDateUTC != null ? madeOnDateUTC.toZonedDateTime() : madeOnDateTenant; ZonedDateTime checkedOnDate = checkedOnDateUTC != null ? checkedOnDateUTC.toZonedDateTime() : checkedOnDateTenant; return new AuditData(id, actionName, entityName, resourceId, subresourceId, maker, madeOnDate, checker, checkedOnDate, processingResult, commandAsJson, officeName, groupLevelName, groupName, clientName, loanAccountNo, savingsAccountNo, - clientId, loanId, resourceGetUrl); + clientId, loanId, resourceGetUrl, ip); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java index 9ae39f235bc..2d731cc4f9e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java @@ -33,6 +33,7 @@ import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; import org.apache.fineract.infrastructure.core.filters.CorrelationHeaderFilter; +import org.apache.fineract.infrastructure.core.filters.GeolocationHeaderFilter; import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreFilter; import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreHelper; import org.apache.fineract.infrastructure.core.filters.RequestResponseFilter; @@ -147,6 +148,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "READ_CACHE") .requestMatchers(antMatcher(HttpMethod.PUT, "/api/*/caches")) .hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_WRITE", "UPDATE_CACHE") + // currency + .requestMatchers(antMatcher(HttpMethod.GET, "/api/*/currencies")) + .hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "READ_CURRENCY") + .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/currencies")) + .hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_WRITE", "UPDATE_CURRENCY") // ... .requestMatchers(antMatcher(HttpMethod.POST, "/api/*/twofactor/validate")).fullyAuthenticated() // .requestMatchers(antMatcher("/api/*/twofactor")).fullyAuthenticated() // @@ -160,7 +166,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .addFilterBefore(tenantAwareBasicAuthenticationFilter(), SecurityContextHolderFilter.class) // .addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class) // .addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class) // - .addFilterAfter(fineractInstanceModeApiFilter(), CorrelationHeaderFilter.class); // + .addFilterAfter(fineractInstanceModeApiFilter(), CorrelationHeaderFilter.class) // + .addFilterAfter(geolocationHeaderFilter(), RequestResponseFilter.class); // if (!Objects.isNull(loanCOBFilterHelper)) { http.addFilterAfter(loanCOBApiFilter(), FineractInstanceModeApiFilter.class) // .addFilterAfter(idempotencyStoreFilter(), LoanCOBApiFilter.class); // @@ -222,6 +229,10 @@ public TenantAwareBasicAuthenticationFilter tenantAwareBasicAuthenticationFilter return filter; } + public GeolocationHeaderFilter geolocationHeaderFilter() { + return new GeolocationHeaderFilter(fineractProperties); + } + @Bean public BasicAuthenticationEntryPoint basicAuthenticationEntryPoint() { BasicAuthenticationEntryPoint basicAuthenticationEntryPoint = new BasicAuthenticationEntryPoint(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanRepaymentBusinessEventSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanRepaymentBusinessEventSerializer.java index 5050512d74d..ae8a14ea131 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanRepaymentBusinessEventSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanRepaymentBusinessEventSerializer.java @@ -61,7 +61,7 @@ public ByteBufferSerializable toAvroDTO(BusinessEvent rawEvent) { String externalId = loan.getExternalId().getValue(); MonetaryCurrency loanCurrency = loan.getCurrency(); CurrencyDataV1 currency = CurrencyDataV1.newBuilder().setCode(loanCurrency.getCode()) - .setDecimalPlaces(loanCurrency.getDigitsAfterDecimal()).setInMultiplesOf(loanCurrency.getCurrencyInMultiplesOf()).build(); + .setDecimalPlaces(loanCurrency.getDigitsAfterDecimal()).setInMultiplesOf(loanCurrency.getInMultiplesOf()).build(); RepaymentDueDataV1 repaymentDue = getRepaymentDueData(repaymentInstallment, loanCurrency); diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResource.java deleted file mode 100644 index fa02ea61748..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResource.java +++ /dev/null @@ -1,88 +0,0 @@ -/** - * 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.monetary.api; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import lombok.RequiredArgsConstructor; -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.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.organisation.monetary.data.ApplicationCurrencyConfigurationData; -import org.apache.fineract.organisation.monetary.data.request.CurrencyRequest; -import org.apache.fineract.organisation.monetary.service.OrganisationCurrencyReadPlatformService; -import org.springframework.stereotype.Component; - -@Path("/v1/currencies") -@Component -@Tag(name = "Currency", description = "Application related configuration around viewing/updating the currencies permitted for use within the MFI.") -@RequiredArgsConstructor -public class CurrenciesApiResource { - - private static final String RESOURCE_NAME_FOR_PERMISSIONS = "CURRENCY"; - - private final PlatformSecurityContext context; - private final OrganisationCurrencyReadPlatformService readPlatformService; - private final DefaultToApiJsonSerializer toApiJsonSerializer; - private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; - - @GET - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Currency Configuration", description = "Returns the list of currencies permitted for use AND the list of currencies not selected (but available for selection).\n" - + "\n" + "Example Requests:\n" + "\n" + "currencies\n" + "\n" + "\n" + "currencies?fields=selectedCurrencyOptions") - public ApplicationCurrencyConfigurationData retrieveCurrencies() { - - this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); - - return this.readPlatformService.retrieveCurrencyConfiguration(); - } - - @PUT - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update Currency Configuration", description = "Updates the list of currencies permitted for use.") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = CurrencyRequest.class))) - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CurrenciesApiResourceSwagger.PutCurrenciesResponse.class))) }) - public CommandProcessingResult updateCurrencies(@Parameter(hidden = true) CurrencyRequest currencyRequest) { - - final CommandWrapper commandRequest = new CommandWrapperBuilder() // - .updateCurrencies() // - .withJson(toApiJsonSerializer.serialize(currencyRequest)) // - .build(); - - return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResourceSwagger.java deleted file mode 100644 index 67ac7f7e649..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/api/CurrenciesApiResourceSwagger.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * 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.monetary.api; - -import io.swagger.v3.oas.annotations.media.Schema; - -/** - * Created by sanyam on 14/8/17. - */ -public final class CurrenciesApiResourceSwagger { - - private CurrenciesApiResourceSwagger() { - - } - - public static final class CurrencyItem { - - private CurrencyItem() {} - - @Schema(example = "USD") - public String code; - @Schema(example = "US Dollar") - public String name; - @Schema(example = "2") - public Integer decimalPlaces; - @Schema(example = "100") - public Integer inMultiplesOf; - @Schema(example = "$") - public String displaySymbol; - @Schema(example = "currency.USD") - public String nameCode; - @Schema(example = "US Dollar ($)") - public String displayLabel; - } - - @Schema(description = "PutCurrenciesResponse") - public static final class PutCurrenciesResponse { - - private PutCurrenciesResponse() { - - } - - @Schema(example = "[\"KES\",\n" + " \"BND\",\n" + " \"LBP\",\n" + " \"GHC\",\n" + " \"USD\",\n" - + " \"XOF\",\n" + " \"AED\",\n" + " \"AMD\"]") - public String[] currencies; - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformServiceJpaRepositoryImpl.java index c42ea2e90e3..20d7f300f12 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/service/CurrencyWritePlatformServiceJpaRepositoryImpl.java @@ -20,21 +20,16 @@ import java.util.ArrayList; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Set; import lombok.RequiredArgsConstructor; -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.security.service.PlatformSecurityContext; +import org.apache.fineract.organisation.monetary.data.CurrencyUpdateRequest; +import org.apache.fineract.organisation.monetary.data.CurrencyUpdateResponse; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.apache.fineract.organisation.monetary.domain.OrganisationCurrency; import org.apache.fineract.organisation.monetary.domain.OrganisationCurrencyRepository; import org.apache.fineract.organisation.monetary.exception.CurrencyInUseException; -import org.apache.fineract.organisation.monetary.serialization.CurrencyCommandFromApiJsonDeserializer; import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService; import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService; @@ -43,30 +38,22 @@ @RequiredArgsConstructor public class CurrencyWritePlatformServiceJpaRepositoryImpl implements CurrencyWritePlatformService { - private final PlatformSecurityContext context; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; private final OrganisationCurrencyRepository organisationCurrencyRepository; - private final CurrencyCommandFromApiJsonDeserializer fromApiJsonDeserializer; private final LoanProductReadPlatformService loanProductService; private final SavingsProductReadPlatformService savingsProductService; private final ChargeReadPlatformService chargeService; @Transactional @Override - public CommandProcessingResult updateAllowedCurrencies(final JsonCommand command) { + public CurrencyUpdateResponse updateAllowedCurrencies(final CurrencyUpdateRequest request) { + final var currencies = request.getCurrencies(); - this.context.authenticatedUser(); - - this.fromApiJsonDeserializer.validateForUpdate(command.json()); - - final String[] currencies = command.arrayValueOfParameterNamed("currencies"); - - final Map changes = new LinkedHashMap<>(); final List allowedCurrencyCodes = new ArrayList<>(); final Set allowedCurrencies = new HashSet<>(); for (final String currencyCode : currencies) { - final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currencyCode); + final ApplicationCurrency currency = applicationCurrencyRepository.findOneWithNotFoundDetection(currencyCode); final OrganisationCurrency allowedCurrency = currency.toOrganisationCurrency(); @@ -74,9 +61,9 @@ public CommandProcessingResult updateAllowedCurrencies(final JsonCommand command allowedCurrencies.add(allowedCurrency); } - for (OrganisationCurrency priorCurrency : this.organisationCurrencyRepository.findAll()) { + for (OrganisationCurrency priorCurrency : organisationCurrencyRepository.findAll()) { if (!allowedCurrencyCodes.contains(priorCurrency.getCode())) { - // Check if it's safe to remove this currency. + // check if it's safe to remove this currency. if (!loanProductService.retrieveAllLoanProductsForCurrency(priorCurrency.getCode()).isEmpty() || !savingsProductService.retrieveAllForCurrency(priorCurrency.getCode()).isEmpty() || !chargeService.retrieveAllChargesForCurrency(priorCurrency.getCode()).isEmpty()) { @@ -85,14 +72,9 @@ public CommandProcessingResult updateAllowedCurrencies(final JsonCommand command } } - changes.put("currencies", allowedCurrencyCodes.toArray(new String[allowedCurrencyCodes.size()])); - - this.organisationCurrencyRepository.deleteAll(); - this.organisationCurrencyRepository.saveAll(allowedCurrencies); + organisationCurrencyRepository.deleteAll(); + organisationCurrencyRepository.saveAll(allowedCurrencies); - return new CommandProcessingResultBuilder() // - .withCommandId(command.commandId()) // - .with(changes) // - .build(); + return CurrencyUpdateResponse.builder().currencies(allowedCurrencyCodes).build(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/starter/OrganisationMonetaryConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/starter/OrganisationMonetaryConfiguration.java index c35818e4744..0db76dd7b7a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/starter/OrganisationMonetaryConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/starter/OrganisationMonetaryConfiguration.java @@ -18,10 +18,8 @@ */ package org.apache.fineract.organisation.monetary.starter; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.apache.fineract.organisation.monetary.domain.OrganisationCurrencyRepository; -import org.apache.fineract.organisation.monetary.serialization.CurrencyCommandFromApiJsonDeserializer; import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService; import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformServiceImpl; import org.apache.fineract.organisation.monetary.service.CurrencyWritePlatformService; @@ -41,19 +39,17 @@ public class OrganisationMonetaryConfiguration { @Bean @ConditionalOnMissingBean(CurrencyReadPlatformService.class) - public CurrencyReadPlatformService currencyReadPlatformService(PlatformSecurityContext context, JdbcTemplate jdbcTemplate) { - return new CurrencyReadPlatformServiceImpl(context, jdbcTemplate); + public CurrencyReadPlatformService currencyReadPlatformService(JdbcTemplate jdbcTemplate) { + return new CurrencyReadPlatformServiceImpl(jdbcTemplate); } @Bean @ConditionalOnMissingBean(CurrencyWritePlatformService.class) - public CurrencyWritePlatformService currencyWritePlatformService(PlatformSecurityContext context, - ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, - OrganisationCurrencyRepository organisationCurrencyRepository, CurrencyCommandFromApiJsonDeserializer fromApiJsonDeserializer, - LoanProductReadPlatformService loanProductService, SavingsProductReadPlatformService savingsProductService, - ChargeReadPlatformService chargeService) { - return new CurrencyWritePlatformServiceJpaRepositoryImpl(context, applicationCurrencyRepository, organisationCurrencyRepository, - fromApiJsonDeserializer, loanProductService, savingsProductService, chargeService); + public CurrencyWritePlatformService currencyWritePlatformService(ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, + OrganisationCurrencyRepository organisationCurrencyRepository, LoanProductReadPlatformService loanProductService, + SavingsProductReadPlatformService savingsProductService, ChargeReadPlatformService chargeService) { + return new CurrencyWritePlatformServiceJpaRepositoryImpl(applicationCurrencyRepository, organisationCurrencyRepository, + loanProductService, savingsProductService, chargeService); } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupSummary.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupSummary.java deleted file mode 100644 index 7fc58a490ff..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/data/GroupSummary.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * 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.group.data; - -import java.util.Collection; -import org.apache.fineract.organisation.monetary.data.MoneyData; - -public class GroupSummary { - - private final Long totalActiveClients; - private final Long totalChildGroups; - private final Collection totalLoanPortfolio; - private final Collection totalSavings; - - public GroupSummary(final Long totalActiveClients, final Long totalChildGroups, final Collection totalLoanPortfolio, - final Collection totalSavings) { - this.totalActiveClients = totalActiveClients; - this.totalChildGroups = totalChildGroups; - this.totalLoanPortfolio = totalLoanPortfolio; - this.totalSavings = totalSavings; - } - - public Long getTotalActiveClients() { - return this.totalActiveClients; - } - - public Long getTotalChildGroups() { - return this.totalChildGroups; - } - - public Collection getTotalLoanPortfolio() { - return this.totalLoanPortfolio; - } - - public Collection getTotalSavings() { - return this.totalSavings; - } - -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanPointInTimeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanPointInTimeData.java index 685ef6c0031..08fd930dfe8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanPointInTimeData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanPointInTimeData.java @@ -21,6 +21,7 @@ import lombok.Data; import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.monetary.mapper.CurrencyMapper; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.mapstruct.Mapping; @@ -50,7 +51,7 @@ public class LoanPointInTimeData { private Long loanProductId; private String loanProductName; - @org.mapstruct.Mapper(config = MapstructMapperConfig.class, uses = { LoanStatusEnumData.Mapper.class, CurrencyData.Mapper.class, + @org.mapstruct.Mapper(config = MapstructMapperConfig.class, uses = { LoanStatusEnumData.Mapper.class, CurrencyMapper.class, LoanPrincipalData.Mapper.class, LoanInterestData.Mapper.class, LoanFeeData.Mapper.class, LoanPenaltyData.Mapper.class, LoanTotalAmountData.Mapper.class }) public interface Mapper { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java index 23d1f21e046..abcce34a1d2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java @@ -568,7 +568,7 @@ public Map updateFrom(JsonCommand command, Loan loan) { changes.put(LoanApiConstants.productIdParameterName, newValue); loan.updateLoanProduct(loanProduct); final MonetaryCurrency currency = new MonetaryCurrency(loanProduct.getCurrency().getCode(), - loanProduct.getCurrency().getDigitsAfterDecimal(), loanProduct.getCurrency().getCurrencyInMultiplesOf()); + loanProduct.getCurrency().getDigitsAfterDecimal(), loanProduct.getCurrency().getInMultiplesOf()); loan.getLoanRepaymentScheduleDetail().setCurrency(currency); if (!changes.containsKey(LoanApiConstants.interestRateFrequencyTypeParameterName)) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductRelatedDetailUpdateUtil.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductRelatedDetailUpdateUtil.java index 1d99f0d56db..bc0479b4b74 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductRelatedDetailUpdateUtil.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductRelatedDetailUpdateUtil.java @@ -55,7 +55,7 @@ public Map updateLoanRepaymentSchedule(final LoanProductRelatedD String currencyCode = loanRepaymentScheduleDetail.getCurrency().getCode(); Integer digitsAfterDecimal = loanRepaymentScheduleDetail.getCurrency().getDigitsAfterDecimal(); - Integer inMultiplesOf = loanRepaymentScheduleDetail.getCurrency().getCurrencyInMultiplesOf(); + Integer inMultiplesOf = loanRepaymentScheduleDetail.getCurrency().getInMultiplesOf(); final String digitsAfterDecimalParamName = "digitsAfterDecimal"; if (command.isChangeInIntegerParameterNamed(digitsAfterDecimalParamName, digitsAfterDecimal)) { diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index 33642742e4d..da018aef556 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -62,6 +62,7 @@ fineract.correlation.enabled=${FINERACT_LOGGING_HTTP_CORRELATION_ID_ENABLED:fals fineract.correlation.header-name=${FINERACT_LOGGING_HTTP_CORRELATION_ID_HEADER_NAME:X-Correlation-ID} fineract.job.stuck-retry-threshold=${FINERACT_JOB_STUCK_RETRY_THRESHOLD:5} +fineract.geolocation.enabled=${FINERACT_GEOLOCATION_ENABLED:true} fineract.job.loan-cob-enabled=${FINERACT_JOB_LOAN_COB_ENABLED:true} fineract.partitioned-job.partitioned-job-properties[0].job-name=LOAN_COB diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 42b41ea10f8..1aed61075a7 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -204,4 +204,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0186_add_column_ip.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0186_add_column_ip.xml new file mode 100644 index 00000000000..95e47c8200b --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0186_add_column_ip.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java index 7b6e646212f..a909e638a08 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java @@ -173,8 +173,8 @@ delinquencyReadPlatformService, new LoanChargeDataMapperImpl(null, null, null), when(loanAccountData.getAccountNo()).thenReturn("0001"); when(loanAccountData.getExternalId()).thenReturn(ExternalIdFactory.produce("externalId")); when(loanAccountData.getDelinquencyRange()).thenReturn(new DelinquencyRangeData(1L, "classification", 1, 10)); - when(loanAccountData.getCurrency()).thenAnswer(a -> new CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(), - loanCurrency.getCurrencyInMultiplesOf())); + when(loanAccountData.getCurrency()).thenAnswer( + a -> new CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(), loanCurrency.getInMultiplesOf())); when(loanForProcessing.getCurrency()).thenReturn(loanCurrency); when(loanForProcessing.isEnableInstallmentLevelDelinquency()).thenReturn(false); when(delinquentData.getDelinquentDate()).thenReturn(delinquentDate); @@ -238,8 +238,8 @@ delinquencyReadPlatformService, new LoanChargeDataMapperImpl(null, null, null), when(loanAccountData.getAccountNo()).thenReturn("0001"); when(loanAccountData.getExternalId()).thenReturn(ExternalIdFactory.produce("externalId")); when(loanAccountData.getDelinquencyRange()).thenReturn(new DelinquencyRangeData(1L, "classification", 1, 10)); - when(loanAccountData.getCurrency()).thenAnswer(a -> new CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(), - loanCurrency.getCurrencyInMultiplesOf())); + when(loanAccountData.getCurrency()).thenAnswer( + a -> new CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(), loanCurrency.getInMultiplesOf())); when(loanForProcessing.getCurrency()).thenReturn(loanCurrency); when(loanForProcessing.isEnableInstallmentLevelDelinquency()).thenReturn(true); when(delinquentData.getDelinquentDate()).thenReturn(delinquentDate); diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java index 917b377023a..f958a7b5224 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java @@ -402,7 +402,7 @@ public Map update(final JsonCommand command) { actualChanges.put(digitsAfterDecimalParamName, newValue); actualChanges.put(localeParamName, localeAsInput); digitsAfterDecimal = newValue; - this.currency = new MonetaryCurrency(this.currency.getCode(), digitsAfterDecimal, this.currency.getCurrencyInMultiplesOf()); + this.currency = new MonetaryCurrency(this.currency.getCode(), digitsAfterDecimal, this.currency.getInMultiplesOf()); } String currencyCode = this.currency.getCode(); @@ -410,11 +410,10 @@ public Map update(final JsonCommand command) { final String newValue = command.stringValueOfParameterNamed(currencyCodeParamName); actualChanges.put(currencyCodeParamName, newValue); currencyCode = newValue; - this.currency = new MonetaryCurrency(currencyCode, this.currency.getDigitsAfterDecimal(), - this.currency.getCurrencyInMultiplesOf()); + this.currency = new MonetaryCurrency(currencyCode, this.currency.getDigitsAfterDecimal(), this.currency.getInMultiplesOf()); } - Integer inMultiplesOf = this.currency.getCurrencyInMultiplesOf(); + Integer inMultiplesOf = this.currency.getInMultiplesOf(); if (command.isChangeInIntegerParameterNamed(inMultiplesOfParamName, inMultiplesOf)) { final Integer newValue = command.integerValueOfParameterNamed(inMultiplesOfParamName); actualChanges.put(inMultiplesOfParamName, newValue); diff --git a/fineract-validation/src/main/resources/fineract/validation/messages.properties b/fineract-validation/src/main/resources/fineract/validation/messages.properties index 743eabc4e07..a4e53db4da5 100644 --- a/fineract-validation/src/main/resources/fineract/validation/messages.properties +++ b/fineract-validation/src/main/resources/fineract/validation/messages.properties @@ -36,3 +36,8 @@ org.apache.fineract.externalevent.configurations.not-null=The parameter 'externa # Cache org.apache.fineract.cache.cache-type.not-null=The parameter 'cacheType' is mandatory. + +# Currency + +org.apache.fineract.organisation.monetary.currencies.not-null=The parameter 'currencies' is mandatory. +org.apache.fineract.organisation.monetary.currencies.not-empty=The parameter 'currencies' cannot be empty. diff --git a/fineract-validation/src/main/resources/fineract/validation/messages_en.properties b/fineract-validation/src/main/resources/fineract/validation/messages_en.properties index 743eabc4e07..a4e53db4da5 100644 --- a/fineract-validation/src/main/resources/fineract/validation/messages_en.properties +++ b/fineract-validation/src/main/resources/fineract/validation/messages_en.properties @@ -36,3 +36,8 @@ org.apache.fineract.externalevent.configurations.not-null=The parameter 'externa # Cache org.apache.fineract.cache.cache-type.not-null=The parameter 'cacheType' is mandatory. + +# Currency + +org.apache.fineract.organisation.monetary.currencies.not-null=The parameter 'currencies' is mandatory. +org.apache.fineract.organisation.monetary.currencies.not-empty=The parameter 'currencies' cannot be empty. diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/CurrenciesTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/CurrenciesTest.java index 0125dfb5c09..a66b60d2417 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/CurrenciesTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/CurrenciesTest.java @@ -18,17 +18,20 @@ */ package org.apache.fineract.integrationtests; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + 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.util.ArrayList; -import java.util.Collections; +import java.util.List; +import java.util.Objects; import org.apache.fineract.integrationtests.common.CurrenciesHelper; import org.apache.fineract.integrationtests.common.CurrencyDomain; import org.apache.fineract.integrationtests.common.Utils; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -52,43 +55,32 @@ public void testCurrencyElements() { CurrencyDomain currency = CurrenciesHelper.getCurrencybyCode(requestSpec, responseSpec, "USD"); CurrencyDomain usd = CurrencyDomain.create("USD", "US Dollar", 2, "$", "currency.USD", "US Dollar ($)").build(); - Assertions.assertTrue(currency.getDecimalPlaces() >= 0); - Assertions.assertNotNull(currency.getName()); - Assertions.assertNotNull(currency.getDisplaySymbol()); - Assertions.assertNotNull(currency.getDisplayLabel()); - Assertions.assertNotNull(currency.getNameCode()); + assertNotNull(currency); + assertTrue(currency.getDecimalPlaces() >= 0); + assertNotNull(currency.getName()); + assertNotNull(currency.getDisplaySymbol()); + assertNotNull(currency.getDisplayLabel()); + assertNotNull(currency.getNameCode()); - Assertions.assertEquals(usd, currency); + assertEquals(usd, currency); } @Test public void testUpdateCurrencySelection() { + var currenciestoUpdate = List.of("KES", "BND", "LBP", "GHC", "USD", "INR"); - // Test updation - ArrayList currenciestoUpdate = new ArrayList(); - currenciestoUpdate.add("KES"); - currenciestoUpdate.add("BND"); - currenciestoUpdate.add("LBP"); - currenciestoUpdate.add("GHC"); - currenciestoUpdate.add("USD"); - currenciestoUpdate.add("INR"); - - ArrayList currenciesOutput = CurrenciesHelper.updateSelectedCurrencies(this.requestSpec, this.responseSpec, - currenciestoUpdate); - Assertions.assertNotNull(currenciesOutput); + var currenciesOutput = CurrenciesHelper.updateSelectedCurrencies(this.requestSpec, this.responseSpec, currenciestoUpdate); - Assertions.assertEquals(currenciestoUpdate, currenciesOutput, "Verifying Do Outputed Currencies Match after Updation"); + assertNotNull(currenciesOutput); + assertEquals(currenciestoUpdate, currenciesOutput, "Verifying returned currencies match after update"); - // Test that output matches updation - ArrayList currenciesBeforeUpdate = new ArrayList(); - for (String e : currenciestoUpdate) { - currenciesBeforeUpdate.add(CurrenciesHelper.getCurrencybyCode(requestSpec, responseSpec, e)); - } - Collections.sort(currenciesBeforeUpdate); + var currenciesBeforeUpdate = currenciestoUpdate.stream() + .map(currency -> CurrenciesHelper.getCurrencybyCode(requestSpec, responseSpec, currency)).filter(Objects::nonNull).sorted() + .toList(); - ArrayList currenciesAfterUpdate = CurrenciesHelper.getSelectedCurrencies(requestSpec, responseSpec); - Assertions.assertNotNull(currenciesAfterUpdate); + var currenciesAfterUpdate = CurrenciesHelper.getSelectedCurrencies(requestSpec, responseSpec); - Assertions.assertEquals(currenciesBeforeUpdate, currenciesAfterUpdate, "Verifying Do Selected Currencies Match after Updation"); + assertNotNull(currenciesAfterUpdate); + assertEquals(currenciesBeforeUpdate, currenciesAfterUpdate, "Verifying selected currencies match after update"); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CurrenciesHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CurrenciesHelper.java index e7556a71c2c..528bf59966f 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CurrenciesHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CurrenciesHelper.java @@ -24,6 +24,8 @@ import io.restassured.specification.ResponseSpecification; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,15 +43,13 @@ private CurrenciesHelper() { // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest) @Deprecated(forRemoval = true) - public static ArrayList getAllCurrencies(final RequestSpecification requestSpec, - final ResponseSpecification responseSpec) { - final String GET_ALL_CURRENCIES_URL = CURRENCIES_URL + "?" + Utils.TENANT_IDENTIFIER; + public static List getAllCurrencies(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { LOG.info("------------------------ RETRIEVING ALL CURRENCIES -------------------------"); - final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_ALL_CURRENCIES_URL, ""); - ArrayList selectedCurrencyOptions = (ArrayList) response.get("selectedCurrencyOptions"); - ArrayList currencyOptions = (ArrayList) response.get("currencyOptions"); + HashMap response = Utils.performServerGet(requestSpec, responseSpec, CURRENCIES_URL + "?" + Utils.TENANT_IDENTIFIER, ""); + var selectedCurrencyOptions = (ArrayList) response.get("selectedCurrencyOptions"); + var currencyOptions = (ArrayList) response.get("currencyOptions"); currencyOptions.addAll(selectedCurrencyOptions); - final String jsonData = new Gson().toJson(new ArrayList(selectedCurrencyOptions)); + var jsonData = new Gson().toJson(selectedCurrencyOptions); return new Gson().fromJson(jsonData, new TypeToken>() {}.getType()); } @@ -57,12 +57,12 @@ public static ArrayList getAllCurrencies(final RequestSpecificat // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest) @Deprecated(forRemoval = true) - public static ArrayList getSelectedCurrencies(final RequestSpecification requestSpec, + public static List getSelectedCurrencies(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { - final String GET_ALL_SELECTED_CURRENCIES_URL = CURRENCIES_URL + "?fields=selectedCurrencyOptions" + "&" + Utils.TENANT_IDENTIFIER; LOG.info("------------------------ RETRIEVING ALL SELECTED CURRENCIES -------------------------"); - final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_ALL_SELECTED_CURRENCIES_URL, ""); - final String jsonData = new Gson().toJson(response.get("selectedCurrencyOptions")); + HashMap response = Utils.performServerGet(requestSpec, responseSpec, + CURRENCIES_URL + "?fields=selectedCurrencyOptions" + "&" + Utils.TENANT_IDENTIFIER, ""); + var jsonData = new Gson().toJson(response.get("selectedCurrencyOptions")); return new Gson().fromJson(jsonData, new TypeToken>() {}.getType()); } @@ -72,10 +72,10 @@ public static ArrayList getSelectedCurrencies(final RequestSpeci @Deprecated(forRemoval = true) public static CurrencyDomain getCurrencybyCode(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String code) { - ArrayList currenciesList = getAllCurrencies(requestSpec, responseSpec); - for (CurrencyDomain e : currenciesList) { - if (e.getCode().equals(code)) { - return e; + var currencies = getAllCurrencies(requestSpec, responseSpec); + for (var currency : currencies) { + if (currency.getCode().equals(code)) { + return currency; } } return null; @@ -85,22 +85,22 @@ public static CurrencyDomain getCurrencybyCode(final RequestSpecification reques // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest) @Deprecated(forRemoval = true) - public static ArrayList updateSelectedCurrencies(final RequestSpecification requestSpec, - final ResponseSpecification responseSpec, final ArrayList currencies) { - final String CURRENCIES_UPDATE_URL = CURRENCIES_URL + "?" + Utils.TENANT_IDENTIFIER; - LOG.info("---------------------------------UPDATE SELECTED CURRENCIES LIST---------------------------------------------"); - HashMap hash = Utils.performServerPut(requestSpec, responseSpec, CURRENCIES_UPDATE_URL, currenciesToJSON(currencies), "changes"); - return (ArrayList) hash.get("currencies"); + public static List updateSelectedCurrencies(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + final List currencies) { + LOG.info( + "---------------------------------UPDATE SELECTED CURRENCIES LIST (deprecated)---------------------------------------------"); + // TODO: this nested "changes" map makes no sense whatsover... in the future just use "currencies" (straight + // forward, no nesting, no complexity) + Map changes = Utils.performServerPut(requestSpec, responseSpec, CURRENCIES_URL + "?" + Utils.TENANT_IDENTIFIER, + currenciesToJSON(currencies), "changes"); + return (List) changes.get("currencies"); } // TODO: Rewrite to use fineract-client instead! // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest) @Deprecated(forRemoval = true) - private static String currenciesToJSON(final ArrayList currencies) { - HashMap map = new HashMap<>(); - map.put("currencies", currencies); - LOG.info("map : {}", map); - return new Gson().toJson(map); + private static String currenciesToJSON(final List currencies) { + return new Gson().toJson(Map.of("currencies", currencies)); } }