FINERACT-2247: New command processing - Currency Management (org.apache.fineract.organisation.monetary)

This commit is contained in:
viktorpavlenko
2025-06-28 10:38:32 +02:00
committed by Aleksandar Vidakovic
parent 0629bb2d48
commit 4c3da5b06a
42 changed files with 404 additions and 538 deletions
@@ -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<CurrencyUpdateResponse> response = commandPipeline.send(command);
return response.get();
}
}
@@ -16,35 +16,13 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.fineract.organisation.monetary.data; package org.apache.fineract.organisation.monetary.command;
import java.math.BigDecimal; import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.fineract.command.core.Command;
import org.apache.fineract.organisation.monetary.data.CurrencyUpdateRequest;
/** @Data
* Immutable data object representing currency. @EqualsAndHashCode(callSuper = true)
*/ public class CurrencyUpdateCommand extends Command<CurrencyUpdateRequest> {}
public class MoneyData {
private final String code;
private final BigDecimal amount;
private final int decimalPlaces;
public MoneyData(final String code, final BigDecimal amount, final int decimalPlaces) {
this.code = code;
this.amount = amount;
this.decimalPlaces = decimalPlaces;
}
public String getCode() {
return this.code;
}
public BigDecimal getAmount() {
return this.amount;
}
public int getDecimalPlaces() {
return this.decimalPlaces;
}
}
@@ -16,14 +16,25 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
public record CurrencyRequest(List<String> currencies) implements Serializable { @Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CurrencyConfigurationData implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private List<CurrencyData> selectedCurrencyOptions;
private List<CurrencyData> currencyOptions;
} }
@@ -18,26 +18,29 @@
*/ */
package org.apache.fineract.organisation.monetary.data; package org.apache.fineract.organisation.monetary.data;
import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import lombok.EqualsAndHashCode; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Builder;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; import lombok.Data;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import lombok.NoArgsConstructor;
/** @Builder
* Immutable data object representing currency. @Data
*/ @NoArgsConstructor
@Getter @AllArgsConstructor
@EqualsAndHashCode
public class CurrencyData implements Serializable { public class CurrencyData implements Serializable {
private final String code; @Serial
private final String name; private static final long serialVersionUID = 1L;
private final int decimalPlaces;
private final Integer inMultiplesOf; private String code;
private final String displaySymbol; private String name;
private final String nameCode; private int decimalPlaces;
private final String displayLabel; private Integer inMultiplesOf;
private String displaySymbol;
private String nameCode;
private String displayLabel;
public static CurrencyData blank() { public static CurrencyData blank() {
return new CurrencyData("", "", 0, 0, "", ""); return new CurrencyData("", "", 0, 0, "", "");
@@ -90,11 +93,4 @@ public class CurrencyData implements Serializable {
return builder.toString(); return builder.toString();
} }
@org.mapstruct.Mapper(config = MapstructMapperConfig.class)
public interface Mapper {
default CurrencyData map(MonetaryCurrency source) {
return source.toData();
}
}
} }
@@ -18,25 +18,26 @@
*/ */
package org.apache.fineract.organisation.monetary.data; package org.apache.fineract.organisation.monetary.data;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection; import java.util.List;
import lombok.Getter; import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor; import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/** @Builder
* Immutable data object for application currency. @Data
*/ @NoArgsConstructor
@AllArgsConstructor
@Getter public class CurrencyUpdateRequest implements Serializable {
@RequiredArgsConstructor
public class ApplicationCurrencyConfigurationData implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@SuppressWarnings("unused") @NotNull(message = "{org.apache.fineract.organisation.monetary.currencies.not-null}")
private final Collection<CurrencyData> selectedCurrencyOptions; @NotEmpty(message = "{org.apache.fineract.organisation.monetary.currencies.not-empty}")
@SuppressWarnings("unused") private List<String> currencies;
private final Collection<CurrencyData> currencyOptions;
} }
@@ -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<String> currencies;
@Deprecated(forRemoval = true)
@JsonProperty("changes")
public Map<String, Object> getChanges() {
// TODO: remove this one day... we should never use hashmaps in such trivial cases!!!
return Map.of("currencies", currencies);
}
}
@@ -51,7 +51,7 @@ public class ApplicationCurrencyRepositoryWrapper {
} }
final ApplicationCurrency applicationCurrency = ApplicationCurrency.from(defaultApplicationCurrency, final ApplicationCurrency applicationCurrency = ApplicationCurrency.from(defaultApplicationCurrency,
currency.getDigitsAfterDecimal(), currency.getCurrencyInMultiplesOf()); currency.getDigitsAfterDecimal(), currency.getInMultiplesOf());
return applicationCurrency; return applicationCurrency;
} }
@@ -20,8 +20,10 @@ package org.apache.fineract.organisation.monetary.domain;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Embeddable; import jakarta.persistence.Embeddable;
import lombok.Getter;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
@Getter
@Embeddable @Embeddable
public class MonetaryCurrency { public class MonetaryCurrency {
@@ -74,17 +76,4 @@ public class MonetaryCurrency {
} }
return currencyData; return currencyData;
} }
public String getCode() {
return this.code;
}
public int getDigitsAfterDecimal() {
return this.digitsAfterDecimal;
}
public Integer getCurrencyInMultiplesOf() {
return this.inMultiplesOf;
}
} }
@@ -18,30 +18,26 @@
*/ */
package org.apache.fineract.organisation.monetary.handler; package org.apache.fineract.organisation.monetary.handler;
import org.apache.fineract.commands.annotation.CommandType; import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.handler.NewCommandSourceHandler; import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.command.core.Command;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; 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.apache.fineract.organisation.monetary.service.CurrencyWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Slf4j
@CommandType(entity = "CURRENCY", action = "UPDATE") @Component
public class UpdateCurrencyCommandHandler implements NewCommandSourceHandler { @RequiredArgsConstructor
public class CurrencyUpdateCommandHandler implements CommandHandler<CurrencyUpdateRequest, CurrencyUpdateResponse> {
private final CurrencyWritePlatformService writePlatformService; private final CurrencyWritePlatformService writePlatformService;
@Autowired
public UpdateCurrencyCommandHandler(final CurrencyWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CurrencyUpdateResponse handle(final Command<CurrencyUpdateRequest> command) {
return writePlatformService.updateAllowedCurrencies(command.getPayload());
return this.writePlatformService.updateAllowedCurrencies(command);
} }
} }
@@ -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);
}
@@ -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<String> 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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
final List<ApiParameterError> 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<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
}
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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.JsonDeserializationContext;
import com.google.gson.JsonDeserializer; import com.google.gson.JsonDeserializer;
@@ -29,6 +29,7 @@ import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
@AllArgsConstructor @AllArgsConstructor
@Deprecated(forRemoval = true)
public class MoneyDeserializer implements JsonDeserializer<Money> { public class MoneyDeserializer implements JsonDeserializer<Money> {
private final MathContext mc; private final MathContext mc;
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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.JsonElement;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
@@ -25,6 +25,7 @@ import com.google.gson.JsonSerializer;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
@Deprecated(forRemoval = true)
public class MoneySerializer implements JsonSerializer<Money> { public class MoneySerializer implements JsonSerializer<Money> {
@Override @Override
@@ -23,7 +23,6 @@ import java.sql.SQLException;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport; 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.apache.fineract.organisation.monetary.data.CurrencyData;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
@@ -31,15 +30,11 @@ import org.springframework.jdbc.core.RowMapper;
@RequiredArgsConstructor @RequiredArgsConstructor
public class CurrencyReadPlatformServiceImpl implements CurrencyReadPlatformService { public class CurrencyReadPlatformServiceImpl implements CurrencyReadPlatformService {
private final PlatformSecurityContext context;
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private final CurrencyMapper currencyRowMapper = new CurrencyMapper(); private final CurrencyRowMapper currencyRowMapper = new CurrencyRowMapper();
@Override @Override
public List<CurrencyData> retrieveAllowedCurrencies() { public List<CurrencyData> retrieveAllowedCurrencies() {
this.context.authenticatedUser();
final String sql = "select " + this.currencyRowMapper.schema() + " from m_organisation_currency c order by c.name"; final String sql = "select " + this.currencyRowMapper.schema() + " from m_organisation_currency c order by c.name";
return this.jdbcTemplate.query(sql, this.currencyRowMapper); // NOSONAR return this.jdbcTemplate.query(sql, this.currencyRowMapper); // NOSONAR
@@ -47,7 +42,6 @@ public class CurrencyReadPlatformServiceImpl implements CurrencyReadPlatformServ
@Override @Override
public List<CurrencyData> retrieveAllPlatformCurrencies() { public List<CurrencyData> retrieveAllPlatformCurrencies() {
final String sql = "select " + this.currencyRowMapper.schema() + " from m_currency c order by c.name"; final String sql = "select " + this.currencyRowMapper.schema() + " from m_currency c order by c.name";
return this.jdbcTemplate.query(sql, this.currencyRowMapper); // NOSONAR return this.jdbcTemplate.query(sql, this.currencyRowMapper); // NOSONAR
@@ -55,13 +49,12 @@ public class CurrencyReadPlatformServiceImpl implements CurrencyReadPlatformServ
@Override @Override
public CurrencyData retrieveCurrency(final String code) { public CurrencyData retrieveCurrency(final String code) {
final String sql = "select " + this.currencyRowMapper.schema() + " from m_currency c where c.code = ? order by c.name"; 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 return this.jdbcTemplate.queryForObject(sql, this.currencyRowMapper, new Object[] { code }); // NOSONAR
} }
private static final class CurrencyMapper implements RowMapper<CurrencyData> { private static final class CurrencyRowMapper implements RowMapper<CurrencyData> {
@Override @Override
public CurrencyData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { public CurrencyData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
@@ -18,11 +18,10 @@
*/ */
package org.apache.fineract.organisation.monetary.service; package org.apache.fineract.organisation.monetary.service;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.organisation.monetary.data.CurrencyUpdateRequest;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.organisation.monetary.data.CurrencyUpdateResponse;
public interface CurrencyWritePlatformService { public interface CurrencyWritePlatformService {
CommandProcessingResult updateAllowedCurrencies(JsonCommand command); CurrencyUpdateResponse updateAllowedCurrencies(CurrencyUpdateRequest request);
} }
@@ -18,10 +18,10 @@
*/ */
package org.apache.fineract.organisation.monetary.service; 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 { public interface OrganisationCurrencyReadPlatformService {
ApplicationCurrencyConfigurationData retrieveCurrencyConfiguration(); CurrencyConfigurationData retrieveCurrencyConfiguration();
} }
@@ -18,10 +18,8 @@
*/ */
package org.apache.fineract.organisation.monetary.service; package org.apache.fineract.organisation.monetary.service;
import java.util.Collection;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.organisation.monetary.data.ApplicationCurrencyConfigurationData; import org.apache.fineract.organisation.monetary.data.CurrencyConfigurationData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
@RequiredArgsConstructor @RequiredArgsConstructor
public class OrganisationCurrencyReadPlatformServiceImpl implements OrganisationCurrencyReadPlatformService { public class OrganisationCurrencyReadPlatformServiceImpl implements OrganisationCurrencyReadPlatformService {
@@ -29,14 +27,15 @@ public class OrganisationCurrencyReadPlatformServiceImpl implements Organisation
private final CurrencyReadPlatformService currencyReadPlatformService; private final CurrencyReadPlatformService currencyReadPlatformService;
@Override @Override
public ApplicationCurrencyConfigurationData retrieveCurrencyConfiguration() { public CurrencyConfigurationData retrieveCurrencyConfiguration() {
final Collection<CurrencyData> selectedCurrencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies(); final var selectedCurrencyOptions = currencyReadPlatformService.retrieveAllowedCurrencies();
final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllPlatformCurrencies(); final var currencyOptions = currencyReadPlatformService.retrieveAllPlatformCurrencies();
// remove selected currency options // remove selected currency options
currencyOptions.removeAll(selectedCurrencyOptions); currencyOptions.removeAll(selectedCurrencyOptions);
return new ApplicationCurrencyConfigurationData(selectedCurrencyOptions, currencyOptions); return CurrencyConfigurationData.builder().selectedCurrencyOptions(selectedCurrencyOptions).currencyOptions(currencyOptions)
.build();
} }
} }
@@ -21,15 +21,13 @@ package org.apache.fineract.test.initializer.global;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.client.models.CurrencyRequest; import org.apache.fineract.client.models.CurrencyUpdateRequest;
import org.apache.fineract.client.models.PutCurrenciesResponse;
import org.apache.fineract.client.services.CurrencyApi; import org.apache.fineract.client.services.CurrencyApi;
import org.apache.fineract.test.support.TestContext; import org.apache.fineract.test.support.TestContext;
import org.apache.fineract.test.support.TestContextKey; import org.apache.fineract.test.support.TestContextKey;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import retrofit2.Response;
@RequiredArgsConstructor @RequiredArgsConstructor
@Component @Component
@@ -42,9 +40,8 @@ public class CurrencyGlobalInitializerStep implements FineractGlobalInitializerS
@Override @Override
public void initialize() throws Exception { public void initialize() throws Exception {
CurrencyRequest currencyRequest = new CurrencyRequest(); var request = new CurrencyUpdateRequest();
Response<PutCurrenciesResponse> putCurrenciesResponse = currencyApi.updateCurrencies(currencyRequest.currencies(CURRENCIES)) var response = currencyApi.updateCurrencies(request.currencies(CURRENCIES)).execute();
.execute(); TestContext.INSTANCE.set(TestContextKey.PUT_CURRENCIES_RESPONSE, response);
TestContext.INSTANCE.set(TestContextKey.PUT_CURRENCIES_RESPONSE, putCurrenciesResponse);
} }
} }
@@ -25,6 +25,7 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.investor.config.InvestorModuleIsEnabledCondition; import org.apache.fineract.investor.config.InvestorModuleIsEnabledCondition;
import org.apache.fineract.organisation.monetary.domain.Money; 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.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
@@ -41,6 +42,7 @@ public class ExternalAssetOwnerTransferOutstandingInterestCalculation {
private final LoanSummaryProviderDelegate loanSummaryDataProvider; private final LoanSummaryProviderDelegate loanSummaryDataProvider;
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final LoanReadPlatformService loanReadPlatformService; private final LoanReadPlatformService loanReadPlatformService;
private final CurrencyMapper currencyMapper;
private LoanSummaryDataProvider fetchLoanSummaryDataProvider(Loan loan) { private LoanSummaryDataProvider fetchLoanSummaryDataProvider(Loan loan) {
return this.loanSummaryDataProvider.resolveLoanSummaryDataProvider(loan.getTransactionProcessingStrategyCode()); return this.loanSummaryDataProvider.resolveLoanSummaryDataProvider(loan.getTransactionProcessingStrategyCode());
@@ -58,7 +60,7 @@ public class ExternalAssetOwnerTransferOutstandingInterestCalculation {
.map(i -> i.getInterestOutstanding(loan.getCurrency())).reduce(Money.zero(loan.getCurrency()), MathUtil::plus); .map(i -> i.getInterestOutstanding(loan.getCurrency())).reduce(Money.zero(loan.getCurrency()), MathUtil::plus);
BigDecimal notDuePayableAmount = fetchLoanSummaryDataProvider(loan) BigDecimal notDuePayableAmount = fetchLoanSummaryDataProvider(loan)
.computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(loan, data.getRepaymentSchedule().getPeriods(), .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); yield MathUtil.add(duePayableAmount.getAmount(), notDuePayableAmount);
} }
@@ -64,7 +64,7 @@ public class InvestorBusinessEventSerializer extends AbstractBusinessEventWithCu
private static CurrencyDataV1 getCurrencyFromEvent(InvestorBusinessEvent event) { private static CurrencyDataV1 getCurrencyFromEvent(InvestorBusinessEvent event) {
MonetaryCurrency loanCurrency = event.getLoan().getCurrency(); MonetaryCurrency loanCurrency = event.getLoan().getCurrency();
CurrencyDataV1 currency = CurrencyDataV1.newBuilder().setCode(loanCurrency.getCode()) CurrencyDataV1 currency = CurrencyDataV1.newBuilder().setCode(loanCurrency.getCode())
.setDecimalPlaces(loanCurrency.getDigitsAfterDecimal()).setInMultiplesOf(loanCurrency.getCurrencyInMultiplesOf()).build(); .setDecimalPlaces(loanCurrency.getDigitsAfterDecimal()).setInMultiplesOf(loanCurrency.getInMultiplesOf()).build();
return currency; return currency;
} }
@@ -41,6 +41,7 @@ import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; 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.data.AdjustedDateDetailsDTO;
import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType; import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
@@ -69,6 +70,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanScheduleGenerator { public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanScheduleGenerator {
private final LoanTransactionService loanTransactionService; private final LoanTransactionService loanTransactionService;
private final CurrencyMapper currencyMapper;
@Override @Override
public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
@@ -2508,7 +2510,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap,
uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest,
newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate,
currency.toData(), applyInterestRecalculation, mc); currencyMapper.map(currency), applyInterestRecalculation, mc);
retainedInstallments.addAll(newRepaymentScheduleInstallments); retainedInstallments.addAll(newRepaymentScheduleInstallments);
loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations); loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations);
loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getSummary().getTotalInterestCharged())); loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getSummary().getTotalInterestCharged()));
@@ -27,6 +27,7 @@ import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.Money; 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.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService; import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
@@ -62,9 +63,9 @@ public class CumulativeDecliningBalanceInterestLoanScheduleGenerator extends Abs
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator; private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
public CumulativeDecliningBalanceInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator, public CumulativeDecliningBalanceInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator,
final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator, final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator, final LoanTransactionService loanTransactionService,
final LoanTransactionService loanTransactionService) { final CurrencyMapper currencyMapper) {
super(loanTransactionService); super(loanTransactionService, currencyMapper);
this.scheduledDateGenerator = scheduledDateGenerator; this.scheduledDateGenerator = scheduledDateGenerator;
this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator; this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator;
} }
@@ -25,6 +25,7 @@ import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import org.apache.fineract.organisation.monetary.domain.Money; 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.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService; import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -36,9 +37,9 @@ public class CumulativeFlatInterestLoanScheduleGenerator extends AbstractCumulat
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator; private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
public CumulativeFlatInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator, public CumulativeFlatInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator,
final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator, final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator, final LoanTransactionService loanTransactionService,
final LoanTransactionService loanTransactionService) { final CurrencyMapper currencyMapper) {
super(loanTransactionService); super(loanTransactionService, currencyMapper);
this.scheduledDateGenerator = scheduledDateGenerator; this.scheduledDateGenerator = scheduledDateGenerator;
this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator; this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator;
} }
@@ -19,12 +19,14 @@
package org.apache.fineract.portfolio.loanaccount.mapper; package org.apache.fineract.portfolio.loanaccount.mapper;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; 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.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; 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 { public interface LoanTransactionMapper {
@Mapping(target = "numberOfRepayments", ignore = true) @Mapping(target = "numberOfRepayments", ignore = true)
@@ -46,6 +48,6 @@ public interface LoanTransactionMapper {
@Mapping(target = "netDisbursalAmount", source = "loan.netDisbursalAmount") @Mapping(target = "netDisbursalAmount", source = "loan.netDisbursalAmount")
@Mapping(target = "transactionType", expression = "java(org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.transactionType(loanTransaction.getTypeOf()))") @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 = "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); LoanTransactionData mapLoanTransaction(LoanTransaction loanTransaction);
} }
@@ -31,8 +31,8 @@ import org.apache.fineract.infrastructure.core.serialization.gson.JsonExcludeAnn
import org.apache.fineract.infrastructure.core.serialization.gson.LocalDateAdapter; import org.apache.fineract.infrastructure.core.serialization.gson.LocalDateAdapter;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.serialization.gson.MoneyDeserializer; import org.apache.fineract.organisation.monetary.serialization.MoneyDeserializer;
import org.apache.fineract.organisation.monetary.serialization.gson.MoneySerializer; 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.InterestPeriod;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod; import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod;
@@ -22,7 +22,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; 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.note.data.NoteData;
import org.apache.fineract.portfolio.paymenttype.api.PaymentTypeApiResourceSwagger.GetPaymentTypesResponse; import org.apache.fineract.portfolio.paymenttype.api.PaymentTypeApiResourceSwagger.GetPaymentTypesResponse;
@@ -69,6 +68,26 @@ final class JournalEntriesApiResourceSwagger {
public Long officeId; 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 { static final class EnumOptionType {
private EnumOptionType() {} private EnumOptionType() {}
@@ -147,6 +147,11 @@ public class SecurityConfig {
.hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "READ_CACHE") .hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "READ_CACHE")
.requestMatchers(antMatcher(HttpMethod.PUT, "/api/*/caches")) .requestMatchers(antMatcher(HttpMethod.PUT, "/api/*/caches"))
.hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_WRITE", "UPDATE_CACHE") .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(HttpMethod.POST, "/api/*/twofactor/validate")).fullyAuthenticated() //
.requestMatchers(antMatcher("/api/*/twofactor")).fullyAuthenticated() // .requestMatchers(antMatcher("/api/*/twofactor")).fullyAuthenticated() //
@@ -61,7 +61,7 @@ public class LoanRepaymentBusinessEventSerializer extends AbstractBusinessEventW
String externalId = loan.getExternalId().getValue(); String externalId = loan.getExternalId().getValue();
MonetaryCurrency loanCurrency = loan.getCurrency(); MonetaryCurrency loanCurrency = loan.getCurrency();
CurrencyDataV1 currency = CurrencyDataV1.newBuilder().setCode(loanCurrency.getCode()) 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); RepaymentDueDataV1 repaymentDue = getRepaymentDueData(repaymentInstallment, loanCurrency);
@@ -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<ApplicationCurrencyConfigurationData> 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);
}
}
@@ -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;
}
}
@@ -20,21 +20,16 @@ package org.apache.fineract.organisation.monetary.service;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.organisation.monetary.data.CurrencyUpdateRequest;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.organisation.monetary.data.CurrencyUpdateResponse;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.apache.fineract.organisation.monetary.domain.OrganisationCurrency; import org.apache.fineract.organisation.monetary.domain.OrganisationCurrency;
import org.apache.fineract.organisation.monetary.domain.OrganisationCurrencyRepository; import org.apache.fineract.organisation.monetary.domain.OrganisationCurrencyRepository;
import org.apache.fineract.organisation.monetary.exception.CurrencyInUseException; 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.charge.service.ChargeReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService; import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService;
@@ -43,30 +38,22 @@ import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor @RequiredArgsConstructor
public class CurrencyWritePlatformServiceJpaRepositoryImpl implements CurrencyWritePlatformService { public class CurrencyWritePlatformServiceJpaRepositoryImpl implements CurrencyWritePlatformService {
private final PlatformSecurityContext context;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final OrganisationCurrencyRepository organisationCurrencyRepository; private final OrganisationCurrencyRepository organisationCurrencyRepository;
private final CurrencyCommandFromApiJsonDeserializer fromApiJsonDeserializer;
private final LoanProductReadPlatformService loanProductService; private final LoanProductReadPlatformService loanProductService;
private final SavingsProductReadPlatformService savingsProductService; private final SavingsProductReadPlatformService savingsProductService;
private final ChargeReadPlatformService chargeService; private final ChargeReadPlatformService chargeService;
@Transactional @Transactional
@Override @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<String, Object> changes = new LinkedHashMap<>();
final List<String> allowedCurrencyCodes = new ArrayList<>(); final List<String> allowedCurrencyCodes = new ArrayList<>();
final Set<OrganisationCurrency> allowedCurrencies = new HashSet<>(); final Set<OrganisationCurrency> allowedCurrencies = new HashSet<>();
for (final String currencyCode : currencies) { for (final String currencyCode : currencies) {
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currencyCode); final ApplicationCurrency currency = applicationCurrencyRepository.findOneWithNotFoundDetection(currencyCode);
final OrganisationCurrency allowedCurrency = currency.toOrganisationCurrency(); final OrganisationCurrency allowedCurrency = currency.toOrganisationCurrency();
@@ -74,9 +61,9 @@ public class CurrencyWritePlatformServiceJpaRepositoryImpl implements CurrencyWr
allowedCurrencies.add(allowedCurrency); allowedCurrencies.add(allowedCurrency);
} }
for (OrganisationCurrency priorCurrency : this.organisationCurrencyRepository.findAll()) { for (OrganisationCurrency priorCurrency : organisationCurrencyRepository.findAll()) {
if (!allowedCurrencyCodes.contains(priorCurrency.getCode())) { 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() if (!loanProductService.retrieveAllLoanProductsForCurrency(priorCurrency.getCode()).isEmpty()
|| !savingsProductService.retrieveAllForCurrency(priorCurrency.getCode()).isEmpty() || !savingsProductService.retrieveAllForCurrency(priorCurrency.getCode()).isEmpty()
|| !chargeService.retrieveAllChargesForCurrency(priorCurrency.getCode()).isEmpty()) { || !chargeService.retrieveAllChargesForCurrency(priorCurrency.getCode()).isEmpty()) {
@@ -85,14 +72,9 @@ public class CurrencyWritePlatformServiceJpaRepositoryImpl implements CurrencyWr
} }
} }
changes.put("currencies", allowedCurrencyCodes.toArray(new String[allowedCurrencyCodes.size()])); organisationCurrencyRepository.deleteAll();
organisationCurrencyRepository.saveAll(allowedCurrencies);
this.organisationCurrencyRepository.deleteAll(); return CurrencyUpdateResponse.builder().currencies(allowedCurrencyCodes).build();
this.organisationCurrencyRepository.saveAll(allowedCurrencies);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.with(changes) //
.build();
} }
} }
@@ -18,10 +18,8 @@
*/ */
package org.apache.fineract.organisation.monetary.starter; 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.ApplicationCurrencyRepositoryWrapper;
import org.apache.fineract.organisation.monetary.domain.OrganisationCurrencyRepository; 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.CurrencyReadPlatformService;
import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformServiceImpl; import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformServiceImpl;
import org.apache.fineract.organisation.monetary.service.CurrencyWritePlatformService; import org.apache.fineract.organisation.monetary.service.CurrencyWritePlatformService;
@@ -41,19 +39,17 @@ public class OrganisationMonetaryConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(CurrencyReadPlatformService.class) @ConditionalOnMissingBean(CurrencyReadPlatformService.class)
public CurrencyReadPlatformService currencyReadPlatformService(PlatformSecurityContext context, JdbcTemplate jdbcTemplate) { public CurrencyReadPlatformService currencyReadPlatformService(JdbcTemplate jdbcTemplate) {
return new CurrencyReadPlatformServiceImpl(context, jdbcTemplate); return new CurrencyReadPlatformServiceImpl(jdbcTemplate);
} }
@Bean @Bean
@ConditionalOnMissingBean(CurrencyWritePlatformService.class) @ConditionalOnMissingBean(CurrencyWritePlatformService.class)
public CurrencyWritePlatformService currencyWritePlatformService(PlatformSecurityContext context, public CurrencyWritePlatformService currencyWritePlatformService(ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, OrganisationCurrencyRepository organisationCurrencyRepository, LoanProductReadPlatformService loanProductService,
OrganisationCurrencyRepository organisationCurrencyRepository, CurrencyCommandFromApiJsonDeserializer fromApiJsonDeserializer, SavingsProductReadPlatformService savingsProductService, ChargeReadPlatformService chargeService) {
LoanProductReadPlatformService loanProductService, SavingsProductReadPlatformService savingsProductService, return new CurrencyWritePlatformServiceJpaRepositoryImpl(applicationCurrencyRepository, organisationCurrencyRepository,
ChargeReadPlatformService chargeService) { loanProductService, savingsProductService, chargeService);
return new CurrencyWritePlatformServiceJpaRepositoryImpl(context, applicationCurrencyRepository, organisationCurrencyRepository,
fromApiJsonDeserializer, loanProductService, savingsProductService, chargeService);
} }
@Bean @Bean
@@ -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<MoneyData> totalLoanPortfolio;
private final Collection<MoneyData> totalSavings;
public GroupSummary(final Long totalActiveClients, final Long totalChildGroups, final Collection<MoneyData> totalLoanPortfolio,
final Collection<MoneyData> 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<MoneyData> getTotalLoanPortfolio() {
return this.totalLoanPortfolio;
}
public Collection<MoneyData> getTotalSavings() {
return this.totalSavings;
}
}
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.data;
import lombok.Data; import lombok.Data;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
import org.apache.fineract.organisation.monetary.data.CurrencyData; 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.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
@@ -50,7 +51,7 @@ public class LoanPointInTimeData {
private Long loanProductId; private Long loanProductId;
private String loanProductName; 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, LoanPrincipalData.Mapper.class, LoanInterestData.Mapper.class, LoanFeeData.Mapper.class, LoanPenaltyData.Mapper.class,
LoanTotalAmountData.Mapper.class }) LoanTotalAmountData.Mapper.class })
public interface Mapper { public interface Mapper {
@@ -568,7 +568,7 @@ public class LoanAssemblerImpl implements LoanAssembler {
changes.put(LoanApiConstants.productIdParameterName, newValue); changes.put(LoanApiConstants.productIdParameterName, newValue);
loan.updateLoanProduct(loanProduct); loan.updateLoanProduct(loanProduct);
final MonetaryCurrency currency = new MonetaryCurrency(loanProduct.getCurrency().getCode(), final MonetaryCurrency currency = new MonetaryCurrency(loanProduct.getCurrency().getCode(),
loanProduct.getCurrency().getDigitsAfterDecimal(), loanProduct.getCurrency().getCurrencyInMultiplesOf()); loanProduct.getCurrency().getDigitsAfterDecimal(), loanProduct.getCurrency().getInMultiplesOf());
loan.getLoanRepaymentScheduleDetail().setCurrency(currency); loan.getLoanRepaymentScheduleDetail().setCurrency(currency);
if (!changes.containsKey(LoanApiConstants.interestRateFrequencyTypeParameterName)) { if (!changes.containsKey(LoanApiConstants.interestRateFrequencyTypeParameterName)) {
@@ -55,7 +55,7 @@ public class LoanProductRelatedDetailUpdateUtil {
String currencyCode = loanRepaymentScheduleDetail.getCurrency().getCode(); String currencyCode = loanRepaymentScheduleDetail.getCurrency().getCode();
Integer digitsAfterDecimal = loanRepaymentScheduleDetail.getCurrency().getDigitsAfterDecimal(); Integer digitsAfterDecimal = loanRepaymentScheduleDetail.getCurrency().getDigitsAfterDecimal();
Integer inMultiplesOf = loanRepaymentScheduleDetail.getCurrency().getCurrencyInMultiplesOf(); Integer inMultiplesOf = loanRepaymentScheduleDetail.getCurrency().getInMultiplesOf();
final String digitsAfterDecimalParamName = "digitsAfterDecimal"; final String digitsAfterDecimalParamName = "digitsAfterDecimal";
if (command.isChangeInIntegerParameterNamed(digitsAfterDecimalParamName, digitsAfterDecimal)) { if (command.isChangeInIntegerParameterNamed(digitsAfterDecimalParamName, digitsAfterDecimal)) {
@@ -173,8 +173,8 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
when(loanAccountData.getAccountNo()).thenReturn("0001"); when(loanAccountData.getAccountNo()).thenReturn("0001");
when(loanAccountData.getExternalId()).thenReturn(ExternalIdFactory.produce("externalId")); when(loanAccountData.getExternalId()).thenReturn(ExternalIdFactory.produce("externalId"));
when(loanAccountData.getDelinquencyRange()).thenReturn(new DelinquencyRangeData(1L, "classification", 1, 10)); when(loanAccountData.getDelinquencyRange()).thenReturn(new DelinquencyRangeData(1L, "classification", 1, 10));
when(loanAccountData.getCurrency()).thenAnswer(a -> new CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(), when(loanAccountData.getCurrency()).thenAnswer(
loanCurrency.getCurrencyInMultiplesOf())); a -> new CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(), loanCurrency.getInMultiplesOf()));
when(loanForProcessing.getCurrency()).thenReturn(loanCurrency); when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
when(loanForProcessing.isEnableInstallmentLevelDelinquency()).thenReturn(false); when(loanForProcessing.isEnableInstallmentLevelDelinquency()).thenReturn(false);
when(delinquentData.getDelinquentDate()).thenReturn(delinquentDate); when(delinquentData.getDelinquentDate()).thenReturn(delinquentDate);
@@ -238,8 +238,8 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
when(loanAccountData.getAccountNo()).thenReturn("0001"); when(loanAccountData.getAccountNo()).thenReturn("0001");
when(loanAccountData.getExternalId()).thenReturn(ExternalIdFactory.produce("externalId")); when(loanAccountData.getExternalId()).thenReturn(ExternalIdFactory.produce("externalId"));
when(loanAccountData.getDelinquencyRange()).thenReturn(new DelinquencyRangeData(1L, "classification", 1, 10)); when(loanAccountData.getDelinquencyRange()).thenReturn(new DelinquencyRangeData(1L, "classification", 1, 10));
when(loanAccountData.getCurrency()).thenAnswer(a -> new CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(), when(loanAccountData.getCurrency()).thenAnswer(
loanCurrency.getCurrencyInMultiplesOf())); a -> new CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(), loanCurrency.getInMultiplesOf()));
when(loanForProcessing.getCurrency()).thenReturn(loanCurrency); when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
when(loanForProcessing.isEnableInstallmentLevelDelinquency()).thenReturn(true); when(loanForProcessing.isEnableInstallmentLevelDelinquency()).thenReturn(true);
when(delinquentData.getDelinquentDate()).thenReturn(delinquentDate); when(delinquentData.getDelinquentDate()).thenReturn(delinquentDate);
@@ -402,7 +402,7 @@ public class SavingsProduct extends AbstractPersistableCustom<Long> {
actualChanges.put(digitsAfterDecimalParamName, newValue); actualChanges.put(digitsAfterDecimalParamName, newValue);
actualChanges.put(localeParamName, localeAsInput); actualChanges.put(localeParamName, localeAsInput);
digitsAfterDecimal = newValue; 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(); String currencyCode = this.currency.getCode();
@@ -410,11 +410,10 @@ public class SavingsProduct extends AbstractPersistableCustom<Long> {
final String newValue = command.stringValueOfParameterNamed(currencyCodeParamName); final String newValue = command.stringValueOfParameterNamed(currencyCodeParamName);
actualChanges.put(currencyCodeParamName, newValue); actualChanges.put(currencyCodeParamName, newValue);
currencyCode = newValue; currencyCode = newValue;
this.currency = new MonetaryCurrency(currencyCode, this.currency.getDigitsAfterDecimal(), this.currency = new MonetaryCurrency(currencyCode, this.currency.getDigitsAfterDecimal(), this.currency.getInMultiplesOf());
this.currency.getCurrencyInMultiplesOf());
} }
Integer inMultiplesOf = this.currency.getCurrencyInMultiplesOf(); Integer inMultiplesOf = this.currency.getInMultiplesOf();
if (command.isChangeInIntegerParameterNamed(inMultiplesOfParamName, inMultiplesOf)) { if (command.isChangeInIntegerParameterNamed(inMultiplesOfParamName, inMultiplesOf)) {
final Integer newValue = command.integerValueOfParameterNamed(inMultiplesOfParamName); final Integer newValue = command.integerValueOfParameterNamed(inMultiplesOfParamName);
actualChanges.put(inMultiplesOfParamName, newValue); actualChanges.put(inMultiplesOfParamName, newValue);
@@ -36,3 +36,8 @@ org.apache.fineract.externalevent.configurations.not-null=The parameter 'externa
# Cache # Cache
org.apache.fineract.cache.cache-type.not-null=The parameter 'cacheType' is mandatory. 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.
@@ -36,3 +36,8 @@ org.apache.fineract.externalevent.configurations.not-null=The parameter 'externa
# Cache # Cache
org.apache.fineract.cache.cache-type.not-null=The parameter 'cacheType' is mandatory. 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.
@@ -18,17 +18,20 @@
*/ */
package org.apache.fineract.integrationtests; 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.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder; import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification; import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification; import io.restassured.specification.ResponseSpecification;
import java.util.ArrayList; import java.util.List;
import java.util.Collections; import java.util.Objects;
import org.apache.fineract.integrationtests.common.CurrenciesHelper; import org.apache.fineract.integrationtests.common.CurrenciesHelper;
import org.apache.fineract.integrationtests.common.CurrencyDomain; import org.apache.fineract.integrationtests.common.CurrencyDomain;
import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.Utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -52,43 +55,32 @@ public class CurrenciesTest {
CurrencyDomain currency = CurrenciesHelper.getCurrencybyCode(requestSpec, responseSpec, "USD"); CurrencyDomain currency = CurrenciesHelper.getCurrencybyCode(requestSpec, responseSpec, "USD");
CurrencyDomain usd = CurrencyDomain.create("USD", "US Dollar", 2, "$", "currency.USD", "US Dollar ($)").build(); CurrencyDomain usd = CurrencyDomain.create("USD", "US Dollar", 2, "$", "currency.USD", "US Dollar ($)").build();
Assertions.assertTrue(currency.getDecimalPlaces() >= 0); assertNotNull(currency);
Assertions.assertNotNull(currency.getName()); assertTrue(currency.getDecimalPlaces() >= 0);
Assertions.assertNotNull(currency.getDisplaySymbol()); assertNotNull(currency.getName());
Assertions.assertNotNull(currency.getDisplayLabel()); assertNotNull(currency.getDisplaySymbol());
Assertions.assertNotNull(currency.getNameCode()); assertNotNull(currency.getDisplayLabel());
assertNotNull(currency.getNameCode());
Assertions.assertEquals(usd, currency); assertEquals(usd, currency);
} }
@Test @Test
public void testUpdateCurrencySelection() { public void testUpdateCurrencySelection() {
var currenciestoUpdate = List.of("KES", "BND", "LBP", "GHC", "USD", "INR");
// Test updation var currenciesOutput = CurrenciesHelper.updateSelectedCurrencies(this.requestSpec, this.responseSpec, currenciestoUpdate);
ArrayList<String> currenciestoUpdate = new ArrayList<String>();
currenciestoUpdate.add("KES");
currenciestoUpdate.add("BND");
currenciestoUpdate.add("LBP");
currenciestoUpdate.add("GHC");
currenciestoUpdate.add("USD");
currenciestoUpdate.add("INR");
ArrayList<String> currenciesOutput = CurrenciesHelper.updateSelectedCurrencies(this.requestSpec, this.responseSpec, assertNotNull(currenciesOutput);
currenciestoUpdate); assertEquals(currenciestoUpdate, currenciesOutput, "Verifying returned currencies match after update");
Assertions.assertNotNull(currenciesOutput);
Assertions.assertEquals(currenciestoUpdate, currenciesOutput, "Verifying Do Outputed Currencies Match after Updation"); var currenciesBeforeUpdate = currenciestoUpdate.stream()
.map(currency -> CurrenciesHelper.getCurrencybyCode(requestSpec, responseSpec, currency)).filter(Objects::nonNull).sorted()
.toList();
// Test that output matches updation var currenciesAfterUpdate = CurrenciesHelper.getSelectedCurrencies(requestSpec, responseSpec);
ArrayList<CurrencyDomain> currenciesBeforeUpdate = new ArrayList<CurrencyDomain>();
for (String e : currenciestoUpdate) {
currenciesBeforeUpdate.add(CurrenciesHelper.getCurrencybyCode(requestSpec, responseSpec, e));
}
Collections.sort(currenciesBeforeUpdate);
ArrayList<CurrencyDomain> currenciesAfterUpdate = CurrenciesHelper.getSelectedCurrencies(requestSpec, responseSpec); assertNotNull(currenciesAfterUpdate);
Assertions.assertNotNull(currenciesAfterUpdate); assertEquals(currenciesBeforeUpdate, currenciesAfterUpdate, "Verifying selected currencies match after update");
Assertions.assertEquals(currenciesBeforeUpdate, currenciesAfterUpdate, "Verifying Do Selected Currencies Match after Updation");
} }
} }
@@ -24,6 +24,8 @@ import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification; import io.restassured.specification.ResponseSpecification;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -41,15 +43,13 @@ public final class CurrenciesHelper {
// Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
// org.apache.fineract.client.models.PostLoansLoanIdRequest) // org.apache.fineract.client.models.PostLoansLoanIdRequest)
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public static ArrayList<CurrencyDomain> getAllCurrencies(final RequestSpecification requestSpec, public static List<CurrencyDomain> getAllCurrencies(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
final ResponseSpecification responseSpec) {
final String GET_ALL_CURRENCIES_URL = CURRENCIES_URL + "?" + Utils.TENANT_IDENTIFIER;
LOG.info("------------------------ RETRIEVING ALL CURRENCIES -------------------------"); LOG.info("------------------------ RETRIEVING ALL CURRENCIES -------------------------");
final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_ALL_CURRENCIES_URL, ""); HashMap response = Utils.performServerGet(requestSpec, responseSpec, CURRENCIES_URL + "?" + Utils.TENANT_IDENTIFIER, "");
ArrayList<HashMap> selectedCurrencyOptions = (ArrayList<HashMap>) response.get("selectedCurrencyOptions"); var selectedCurrencyOptions = (ArrayList<HashMap>) response.get("selectedCurrencyOptions");
ArrayList<HashMap> currencyOptions = (ArrayList<HashMap>) response.get("currencyOptions"); var currencyOptions = (ArrayList<HashMap>) response.get("currencyOptions");
currencyOptions.addAll(selectedCurrencyOptions); currencyOptions.addAll(selectedCurrencyOptions);
final String jsonData = new Gson().toJson(new ArrayList<HashMap>(selectedCurrencyOptions)); var jsonData = new Gson().toJson(selectedCurrencyOptions);
return new Gson().fromJson(jsonData, new TypeToken<ArrayList<CurrencyDomain>>() {}.getType()); return new Gson().fromJson(jsonData, new TypeToken<ArrayList<CurrencyDomain>>() {}.getType());
} }
@@ -57,12 +57,12 @@ public final class CurrenciesHelper {
// Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
// org.apache.fineract.client.models.PostLoansLoanIdRequest) // org.apache.fineract.client.models.PostLoansLoanIdRequest)
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public static ArrayList<CurrencyDomain> getSelectedCurrencies(final RequestSpecification requestSpec, public static List<CurrencyDomain> getSelectedCurrencies(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec) { final ResponseSpecification responseSpec) {
final String GET_ALL_SELECTED_CURRENCIES_URL = CURRENCIES_URL + "?fields=selectedCurrencyOptions" + "&" + Utils.TENANT_IDENTIFIER;
LOG.info("------------------------ RETRIEVING ALL SELECTED CURRENCIES -------------------------"); LOG.info("------------------------ RETRIEVING ALL SELECTED CURRENCIES -------------------------");
final HashMap response = Utils.performServerGet(requestSpec, responseSpec, GET_ALL_SELECTED_CURRENCIES_URL, ""); HashMap response = Utils.performServerGet(requestSpec, responseSpec,
final String jsonData = new Gson().toJson(response.get("selectedCurrencyOptions")); CURRENCIES_URL + "?fields=selectedCurrencyOptions" + "&" + Utils.TENANT_IDENTIFIER, "");
var jsonData = new Gson().toJson(response.get("selectedCurrencyOptions"));
return new Gson().fromJson(jsonData, new TypeToken<ArrayList<CurrencyDomain>>() {}.getType()); return new Gson().fromJson(jsonData, new TypeToken<ArrayList<CurrencyDomain>>() {}.getType());
} }
@@ -72,10 +72,10 @@ public final class CurrenciesHelper {
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public static CurrencyDomain getCurrencybyCode(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, public static CurrencyDomain getCurrencybyCode(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String code) { final String code) {
ArrayList<CurrencyDomain> currenciesList = getAllCurrencies(requestSpec, responseSpec); var currencies = getAllCurrencies(requestSpec, responseSpec);
for (CurrencyDomain e : currenciesList) { for (var currency : currencies) {
if (e.getCode().equals(code)) { if (currency.getCode().equals(code)) {
return e; return currency;
} }
} }
return null; return null;
@@ -85,22 +85,22 @@ public final class CurrenciesHelper {
// Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
// org.apache.fineract.client.models.PostLoansLoanIdRequest) // org.apache.fineract.client.models.PostLoansLoanIdRequest)
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public static ArrayList<String> updateSelectedCurrencies(final RequestSpecification requestSpec, public static List<String> updateSelectedCurrencies(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final ResponseSpecification responseSpec, final ArrayList<String> currencies) { final List<String> currencies) {
final String CURRENCIES_UPDATE_URL = CURRENCIES_URL + "?" + Utils.TENANT_IDENTIFIER; LOG.info(
LOG.info("---------------------------------UPDATE SELECTED CURRENCIES LIST---------------------------------------------"); "---------------------------------UPDATE SELECTED CURRENCIES LIST (deprecated)---------------------------------------------");
HashMap hash = Utils.performServerPut(requestSpec, responseSpec, CURRENCIES_UPDATE_URL, currenciesToJSON(currencies), "changes"); // TODO: this nested "changes" map makes no sense whatsover... in the future just use "currencies" (straight
return (ArrayList<String>) hash.get("currencies"); // forward, no nesting, no complexity)
Map changes = Utils.performServerPut(requestSpec, responseSpec, CURRENCIES_URL + "?" + Utils.TENANT_IDENTIFIER,
currenciesToJSON(currencies), "changes");
return (List<String>) changes.get("currencies");
} }
// TODO: Rewrite to use fineract-client instead! // TODO: Rewrite to use fineract-client instead!
// Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
// org.apache.fineract.client.models.PostLoansLoanIdRequest) // org.apache.fineract.client.models.PostLoansLoanIdRequest)
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
private static String currenciesToJSON(final ArrayList<String> currencies) { private static String currenciesToJSON(final List<String> currencies) {
HashMap map = new HashMap<>(); return new Gson().toJson(Map.of("currencies", currencies));
map.put("currencies", currencies);
LOG.info("map : {}", map);
return new Gson().toJson(map);
} }
} }