This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 7f9a812048 FINERACT-2220: Make ProgressiveLoanInterestScheduleModel
serializable and deserializable
7f9a812048 is described below
commit 7f9a812048f856849a721b195f580f2a570ce2ff
Author: Soma Sörös <[email protected]>
AuthorDate: Thu Apr 3 18:06:03 2025 +0200
FINERACT-2220: Make ProgressiveLoanInterestScheduleModel serializable and
deserializable
---
.../fineract/client/util/FineractClient.java | 3 +
.../core/serialization/gson/JsonExclude.java | 32 +++++
...sonExcludeAnnotationBasedExclusionStrategy.java | 40 +++++++
.../core/serialization/gson/LocalDateAdapter.java | 43 +++++++
.../serialization/gson/MoneyDeserializer.java | 43 +++++++
.../serialization/gson/MoneySerializer.java | 35 ++++++
.../loanaccount/domain/ProgressiveLoanModel.java | 55 +++++++++
.../repository/ProgressiveLoanModelRepository.java | 30 +++++
.../InterestScheduleModelRepositoryWrapper.java | 33 ++++++
...InterestScheduleModelRepositoryWrapperImpl.java | 68 +++++++++++
.../InterestScheduleModelServiceGsonContext.java | 66 +++++++++++
.../InternalProgressiveLoanApiResource.java | 129 +++++++++++++++++++++
.../InternalProgressiveLoanApiResourceSwagger.java | 126 ++++++++++++++++++++
.../service/ProgressiveLoanConfiguration.java | 36 ++++++
...siveLoanInterestScheduleModelParserService.java | 37 ++++++
...InterestScheduleModelParserServiceGsonImpl.java | 90 ++++++++++++++
.../loanproduct/calc/data/InterestPeriod.java | 14 +++
.../data/ProgressiveLoanInterestScheduleModel.java | 6 +
.../loanproduct/calc/data/RepaymentPeriod.java | 13 ++-
.../progressiveloan/module-changelog-master.xml | 1 +
.../parts/5001_create_progressive_loan_model.xml | 71 ++++++++++++
.../calc/ProgressiveEMICalculatorTest.java | 95 +++++++++++++++
.../resources/db/changelog/db.changelog-master.xml | 1 +
.../ProgressiveLoanModelIntegrationTest.java | 54 +++++++++
24 files changed, 1120 insertions(+), 1 deletion(-)
diff --git
a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java
b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java
index 2237d8ec81..624c73b44e 100644
---
a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java
+++
b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java
@@ -107,6 +107,7 @@ import org.apache.fineract.client.services.PaymentTypeApi;
import org.apache.fineract.client.services.PeriodicAccrualAccountingApi;
import org.apache.fineract.client.services.PermissionsApi;
import org.apache.fineract.client.services.PocketApi;
+import org.apache.fineract.client.services.ProgressiveLoanApi;
import org.apache.fineract.client.services.ProvisioningCategoryApi;
import org.apache.fineract.client.services.ProvisioningCriteriaApi;
import org.apache.fineract.client.services.ProvisioningEntriesApi;
@@ -294,6 +295,7 @@ public final class FineractClient {
public final UsersApi users;
public final WorkingDaysApi workingDays;
public final LoanInterestPauseApi loanInterestPauseApi;
+ public final ProgressiveLoanApi progressiveLoanApi;
public final ExternalAssetOwnersApi externalAssetOwners;
public final ExternalAssetOwnerLoanProductAttributesApi
externalAssetOwnerLoanProductAttributes;
@@ -421,6 +423,7 @@ public final class FineractClient {
users = retrofit.create(UsersApi.class);
workingDays = retrofit.create(WorkingDaysApi.class);
loanInterestPauseApi = retrofit.create(LoanInterestPauseApi.class);
+ progressiveLoanApi = retrofit.create(ProgressiveLoanApi.class);
}
public static Builder builder() {
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/gson/JsonExclude.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/gson/JsonExclude.java
new file mode 100644
index 0000000000..2a0b8c1bc3
--- /dev/null
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/gson/JsonExclude.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.serialization.gson;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Exclude field from json serialization when using Gson with custom
+ * {@link JsonExcludeAnnotationBasedExclusionStrategy}.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.FIELD })
+public @interface JsonExclude {}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/gson/JsonExcludeAnnotationBasedExclusionStrategy.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/gson/JsonExcludeAnnotationBasedExclusionStrategy.java
new file mode 100644
index 0000000000..c1c1981eb3
--- /dev/null
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/gson/JsonExcludeAnnotationBasedExclusionStrategy.java
@@ -0,0 +1,40 @@
+/**
+ * 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.serialization.gson;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+
+/**
+ * Exclusion strategy to handle {@link JsonExclude} annotations to exclude
fields from serialization and
+ * deserialization.
+ */
+public class JsonExcludeAnnotationBasedExclusionStrategy implements
ExclusionStrategy {
+
+ @Override
+ public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+ JsonExclude annotation =
fieldAttributes.getAnnotation(JsonExclude.class);
+ return annotation != null;
+ }
+
+ @Override
+ public boolean shouldSkipClass(Class<?> aClass) {
+ return false;
+ }
+}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/gson/LocalDateAdapter.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/gson/LocalDateAdapter.java
new file mode 100644
index 0000000000..5a06665ba3
--- /dev/null
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/gson/LocalDateAdapter.java
@@ -0,0 +1,43 @@
+/**
+ * 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.serialization.gson;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+public class LocalDateAdapter extends TypeAdapter<LocalDate> {
+
+ @Override
+ public void write(final JsonWriter jsonWriter, final LocalDate localDate)
throws IOException {
+
+ jsonWriter.value(localDate.format(DateTimeFormatter.ISO_LOCAL_DATE));
+
+ }
+
+ @Override
+ public LocalDate read(final JsonReader jsonReader) throws IOException {
+
+ return LocalDate.parse(jsonReader.nextString(),
DateTimeFormatter.ISO_LOCAL_DATE);
+
+ }
+}
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/gson/MoneyDeserializer.java
new file mode 100644
index 0000000000..5214d239a4
--- /dev/null
+++
b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/gson/MoneyDeserializer.java
@@ -0,0 +1,43 @@
+/**
+ * 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.gson;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import java.lang.reflect.Type;
+import java.math.MathContext;
+import lombok.AllArgsConstructor;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+
+@AllArgsConstructor
+public class MoneyDeserializer implements JsonDeserializer<Money> {
+
+ private final MathContext mc;
+ private final MonetaryCurrency currency;
+
+ @Override
+ public Money deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
+ throws JsonParseException {
+ return Money.of(currency, jsonElement.getAsBigDecimal(), 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/gson/MoneySerializer.java
new file mode 100644
index 0000000000..b5375857ac
--- /dev/null
+++
b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/serialization/gson/MoneySerializer.java
@@ -0,0 +1,35 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.organisation.monetary.serialization.gson;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import java.lang.reflect.Type;
+import org.apache.fineract.organisation.monetary.domain.Money;
+
+public class MoneySerializer implements JsonSerializer<Money> {
+
+ @Override
+ public JsonElement serialize(Money money, Type type,
JsonSerializationContext jsonSerializationContext) {
+ return new JsonPrimitive(money.getAmount());
+ }
+
+}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ProgressiveLoanModel.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ProgressiveLoanModel.java
new file mode 100644
index 0000000000..c8aec9b2fd
--- /dev/null
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ProgressiveLoanModel.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.domain;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.OneToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.Version;
+import java.time.LocalDate;
+import java.time.OffsetDateTime;
+import lombok.Getter;
+import lombok.Setter;
+import
org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+
+@Entity
+@Table(name = "m_loan_progressive_model")
+@Getter
+@Setter
+public class ProgressiveLoanModel extends AbstractPersistableCustom<Long> {
+
+ @Version
+ int version;
+
+ @OneToOne
+ @JoinColumn(name = "loan_id", nullable = false)
+ private Loan loan;
+
+ @Column(name = "json_model", columnDefinition = "text", nullable = false)
+ private String jsonModel;
+
+ @Column(name = "business_date", nullable = false)
+ private LocalDate businessDate;
+
+ @Column(name = "last_modified_on_utc", nullable = false)
+ private OffsetDateTime lastModifiedDate;
+
+}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/ProgressiveLoanModelRepository.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/ProgressiveLoanModelRepository.java
new file mode 100644
index 0000000000..b49beb9d2b
--- /dev/null
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/ProgressiveLoanModelRepository.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.repository;
+
+import java.util.Optional;
+import org.apache.fineract.portfolio.loanaccount.domain.ProgressiveLoanModel;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ProgressiveLoanModelRepository
+ extends JpaSpecificationExecutor<ProgressiveLoanModel>,
JpaRepository<ProgressiveLoanModel, Long> {
+
+ Optional<ProgressiveLoanModel> findOneByLoanId(Long loanId);
+}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapper.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapper.java
new file mode 100644
index 0000000000..76dfd8a0ca
--- /dev/null
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapper.java
@@ -0,0 +1,33 @@
+/**
+ * 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.loanaccount.service;
+
+import java.util.Optional;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import
org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
+import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
+
+public interface InterestScheduleModelRepositoryWrapper {
+
+ String writeInterestScheduleModel(Loan loan,
ProgressiveLoanInterestScheduleModel model);
+
+ Optional<ProgressiveLoanInterestScheduleModel>
readProgressiveLoanInterestScheduleModel(Long loanId,
+ LoanProductMinimumRepaymentScheduleRelatedDetail detail, Integer
installmentAmountInMultipliesOf);
+
+}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java
new file mode 100644
index 0000000000..7be230c5b0
--- /dev/null
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import jakarta.transaction.Transactional;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.ProgressiveLoanModel;
+import
org.apache.fineract.portfolio.loanaccount.repository.ProgressiveLoanModelRepository;
+import
org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
+import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class InterestScheduleModelRepositoryWrapperImpl implements
InterestScheduleModelRepositoryWrapper {
+
+ private final ProgressiveLoanModelRepository loanModelRepository;
+ private final ProgressiveLoanInterestScheduleModelParserService
progressiveLoanInterestScheduleModelParserService;
+
+ @Transactional
+ @Override
+ public String writeInterestScheduleModel(Loan loan,
ProgressiveLoanInterestScheduleModel model) {
+ if (model == null) {
+ return null;
+ }
+ String jsonModel =
progressiveLoanInterestScheduleModelParserService.toJson(model);
+ ProgressiveLoanModel progressiveLoanModel =
loanModelRepository.findOneByLoanId(loan.getId()).orElseGet(() -> {
+ ProgressiveLoanModel plm = new ProgressiveLoanModel();
+ plm.setLoan(loan);
+ return plm;
+ });
+
progressiveLoanModel.setBusinessDate(ThreadLocalContextUtil.getBusinessDate());
+
progressiveLoanModel.setLastModifiedDate(DateUtils.getAuditOffsetDateTime());
+ progressiveLoanModel.setJsonModel(jsonModel);
+ loanModelRepository.save(progressiveLoanModel);
+ return jsonModel;
+ }
+
+ @Override
+ public Optional<ProgressiveLoanInterestScheduleModel>
readProgressiveLoanInterestScheduleModel(final Long loanId,
+ final LoanProductMinimumRepaymentScheduleRelatedDetail detail,
final Integer installmentAmountInMultipliesOf) {
+ return loanModelRepository.findOneByLoanId(loanId) //
+ .map(ProgressiveLoanModel::getJsonModel) //
+ .map(jsonModel ->
progressiveLoanInterestScheduleModelParserService.fromJson(jsonModel, detail,
+ MoneyHelper.getMathContext(),
installmentAmountInMultipliesOf)); //
+ }
+}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelServiceGsonContext.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelServiceGsonContext.java
new file mode 100644
index 0000000000..39de64dfe1
--- /dev/null
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelServiceGsonContext.java
@@ -0,0 +1,66 @@
+/**
+ * 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.loanaccount.service;
+
+import java.lang.reflect.Type;
+import java.math.MathContext;
+import java.util.ArrayList;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+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;
+import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
+
+@Data
+@RequiredArgsConstructor
+public class InterestScheduleModelServiceGsonContext {
+
+ private final MonetaryCurrency currency;
+ private final MathContext mc;
+ private final LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail;
+ private RepaymentPeriod prev = null;
+ private final Integer installmentAmountInMultipliesOf;
+
+ public RepaymentPeriod createRepaymentPeriodInstance(Type type) {
+ if (type == RepaymentPeriod.class) {
+
+ setPrev(RepaymentPeriod.empty(getPrev(), getMc()));
+ return getPrev();
+ }
+ throw new IllegalArgumentException("Unsupported RepaymentPeriod type:
" + type);
+ }
+
+ public InterestPeriod createInterestPeriodInstance(Type type) {
+ if (type == InterestPeriod.class) {
+ return InterestPeriod.empty(getPrev(), getMc());
+ }
+ throw new IllegalArgumentException("Unsupported InterestPeriod type: "
+ type);
+ }
+
+ public ProgressiveLoanInterestScheduleModel
createProgressiveLoanInterestScheduleModelInstance(Type type) {
+ if (type == ProgressiveLoanInterestScheduleModel.class) {
+ setPrev(null);
+ return new ProgressiveLoanInterestScheduleModel(new ArrayList<>(),
getLoanProductRelatedDetail(), new ArrayList<>(),
+ installmentAmountInMultipliesOf, getMc());
+ }
+ throw new IllegalArgumentException("Unsupported
ProgressiveLoanInterestScheduleModel type: " + type);
+ }
+}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java
new file mode 100644
index 0000000000..8e7febf158
--- /dev/null
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java
@@ -0,0 +1,129 @@
+/**
+ * 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.loanaccount.service;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+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.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Objects;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import
org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
+import
org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@RequiredArgsConstructor
+@Profile(FineractProfiles.TEST)
+@Component
+@Path("v1/internal/loan/progressive")
+@Tag(name = "Progressive Loan", description = "internal loan testing API. This
API should be disabled in production!!!")
+public class InternalProgressiveLoanApiResource implements InitializingBean {
+
+ private final LoanRepositoryWrapper loanRepository;
+ private final AdvancedPaymentScheduleTransactionProcessor
advancedPaymentScheduleTransactionProcessor;
+ private final ProgressiveLoanInterestScheduleModelParserService
progressiveLoanInterestScheduleModelParserService;
+ private final InterestScheduleModelRepositoryWrapper writePlatformService;
+
+ @Override
+ @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
+ public void afterPropertiesSet() throws Exception {
+
log.warn("------------------------------------------------------------");
+ log.warn("
");
+ log.warn("DO NOT USE THIS IN PRODUCTION!");
+ log.warn("Internal client services mode is enabled");
+ log.warn("DO NOT USE THIS IN PRODUCTION!");
+ log.warn("
");
+
log.warn("------------------------------------------------------------");
+ }
+
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Path("{loanId}/model")
+ @Operation(summary = "Fetch ProgressiveLoanInterestScheduleModel",
description = "DO NOT USE THIS IN PRODUCTION!")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content =
@Content(schema = @Schema(implementation =
InternalProgressiveLoanApiResourceSwagger.ProgressiveLoanInterestScheduleModel.class)))
})
+ public String fetchModel(@PathParam("loanId") @Parameter(description =
"loanId") long loanId) {
+ Loan loan = loanRepository.findOneWithNotFoundDetection(loanId);
+ if (!loan.isProgressiveSchedule()) {
+ throw new IllegalArgumentException("The loan is not progressive.");
+ }
+
+ return writePlatformService
+ .readProgressiveLoanInterestScheduleModel(loanId,
loan.getLoanRepaymentScheduleDetail(),
+
loan.getLoanProduct().getInstallmentAmountInMultiplesOf())
+
.map(progressiveLoanInterestScheduleModelParserService::toJson).orElse(null);
+ }
+
+ private ProgressiveLoanInterestScheduleModel
reprocessTransactionsAndGetModel(Loan loan) {
+ List<LoanTransaction> transactionsToReprocess =
loan.retrieveListOfTransactionsForReprocessing();
+ LocalDate businessDate = ThreadLocalContextUtil.getBusinessDate();
+ Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel>
changedTransactionDetailProgressiveLoanInterestScheduleModelPair =
advancedPaymentScheduleTransactionProcessor
+
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate,
transactionsToReprocess, loan.getCurrency(),
+ loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges());
+ ProgressiveLoanInterestScheduleModel model =
changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getRight();
+ final List<Long> replayedTransactions =
changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft()
+ .getTransactionChanges().stream().filter(change ->
change.getOldTransaction() != null)
+ .map(change ->
change.getNewTransaction().getId()).filter(Objects::nonNull).toList();
+
+ if (!replayedTransactions.isEmpty()) {
+ log.warn("Reprocessed transactions show differences: There are
unsaved changes of the following transactions: {}",
+ replayedTransactions);
+ }
+ return model;
+ }
+
+ @POST
+ @Path("{loanId}/model")
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Update and Save
ProgressiveLoanInterestScheduleModel", description = "DO NOT USE THIS IN
PRODUCTION!")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content =
@Content(schema = @Schema(implementation =
InternalProgressiveLoanApiResourceSwagger.ProgressiveLoanInterestScheduleModel.class)))
})
+ public String updateModel(@PathParam("loanId") @Parameter(description =
"loanId") long loanId) {
+ Loan loan = loanRepository.findOneWithNotFoundDetection(loanId);
+ if (!loan.isProgressiveSchedule()) {
+ throw new IllegalArgumentException("The loan is not progressive.");
+ }
+ ProgressiveLoanInterestScheduleModel model =
reprocessTransactionsAndGetModel(loan);
+
+ return writePlatformService.writeInterestScheduleModel(loan, model);
+ }
+}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResourceSwagger.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResourceSwagger.java
new file mode 100644
index 0000000000..9c249db68b
--- /dev/null
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResourceSwagger.java
@@ -0,0 +1,126 @@
+/**
+ * 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.loanaccount.service;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+final class InternalProgressiveLoanApiResourceSwagger {
+
+ @Schema(description = "ProgressiveLoanInterestScheduleModel")
+ static final class ProgressiveLoanInterestScheduleModel {
+
+ private ProgressiveLoanInterestScheduleModel() {}
+
+ @Schema(example = "[]")
+ public List<RepaymentPeriod> repaymentPeriods;
+ @Schema(example = "[]")
+ public TreeSet<InterestRate> interestRates;
+ @Schema(example = "{}")
+ public Map<Integer, List<LoanTermVariationsData>> loanTermVariations;
+ @Schema(example = "1")
+ public Integer installmentAmountInMultiplesOf;
+ @Schema(example = "{}")
+ public Map<Integer, Boolean> modifiers;
+ }
+
+ @Schema(description = "Interest Period")
+ static final class InterestPeriod {
+
+ private InterestPeriod() {}
+
+ @Schema(example = "01/01/2024")
+ public String fromDate;
+ @Schema(example = "01/09/2024")
+ public String dueDate;
+ @Schema(example = "0.9636548454")
+ public BigDecimal rateFactor;
+ @Schema(example = "0.9456878987")
+ public BigDecimal rateFactorTillPeriodDueDate;
+ @Schema(example = "0.0")
+ public BigDecimal chargebackPrincipal;
+ @Schema(example = "0.0")
+ public BigDecimal chargebackInterest;
+ @Schema(example = "1000.0")
+ public BigDecimal disbursementAmount;
+ @Schema(example = "3.38")
+ public BigDecimal balanceCorrectionAmount;
+ @Schema(example = "865.71")
+ public BigDecimal outstandingLoanBalance;
+ @Schema(example = "false")
+ public boolean isPaused;
+ }
+
+ @Schema(description = "Repayment Period")
+ static final class RepaymentPeriod {
+
+ private RepaymentPeriod() {}
+
+ @Schema(example = "01/01/2024")
+ public String fromDate;
+ @Schema(example = "01/02/2024")
+ public String dueDate;
+ @Schema(example = "[]")
+ public List<InterestPeriod> interestPeriods;
+ @Schema(example = "127.04")
+ public BigDecimal emi;
+ @Schema(example = "127.04")
+ public BigDecimal originalEmi;
+ @Schema(example = "104.04")
+ public BigDecimal paidPrincipal;
+ @Schema(example = "23.00")
+ public BigDecimal paidInterest;
+ }
+
+ @Schema(description = "Interest Rate")
+ static final class InterestRate {
+
+ private InterestRate() {}
+
+ @Schema(example = "21/12/2024")
+ public String effectiveFrom;
+ @Schema(example = "7.963")
+ public BigDecimal interestRate;
+ }
+
+ @Schema(description = "Loan Term Variations Data")
+ static final class LoanTermVariationsData {
+
+ private LoanTermVariationsData() {}
+
+ @Schema(example = "12345")
+ public Long id;
+ @Schema(example = "null")
+ public EnumOptionData termType;
+ @Schema(example = "21/12/2024")
+ public String termVariationApplicableFrom;
+ @Schema(example = "1.20")
+ public BigDecimal decimalValue;
+ @Schema(example = "21/12/2024")
+ public String dateValue;
+ @Schema(example = "true")
+ public boolean isSpecificToInstallment;
+ @Schema(example = "true")
+ public Boolean isProcessed;
+ }
+}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanConfiguration.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanConfiguration.java
new file mode 100644
index 0000000000..08b25899e8
--- /dev/null
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanConfiguration.java
@@ -0,0 +1,36 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import
org.apache.fineract.portfolio.loanaccount.starter.AdvancedPaymentScheduleTransactionProcessorCondition;
+import
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
+public class ProgressiveLoanConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ProgressiveLoanInterestScheduleModelParserService
interestScheduleModelService() {
+ return new ProgressiveLoanInterestScheduleModelParserServiceGsonImpl();
+ }
+}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestScheduleModelParserService.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestScheduleModelParserService.java
new file mode 100644
index 0000000000..e148ae23ae
--- /dev/null
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestScheduleModelParserService.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.portfolio.loanaccount.service;
+
+import java.math.MathContext;
+import
org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
+import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
+
+public interface ProgressiveLoanInterestScheduleModelParserService {
+
+ /**
+ * Json stringify a ProgressiveLoanInterestScheduleModel model.
+ */
+ String toJson(ProgressiveLoanInterestScheduleModel model);
+
+ /**
+ * Restore a ProgressiveLoanInterestScheduleModel from a JSON string.
+ */
+ ProgressiveLoanInterestScheduleModel fromJson(String s,
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
+ MathContext mc, Integer installmentAmountInMultipliesOf);
+}
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
new file mode 100644
index 0000000000..067d66b011
--- /dev/null
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestScheduleModelParserServiceGsonImpl.java
@@ -0,0 +1,90 @@
+/**
+ * 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.loanaccount.service;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.InstanceCreator;
+import com.google.gson.ToNumberPolicy;
+import jakarta.validation.constraints.NotNull;
+import java.math.MathContext;
+import java.time.LocalDate;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import
org.apache.fineract.infrastructure.core.serialization.gson.JsonExcludeAnnotationBasedExclusionStrategy;
+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.portfolio.loanproduct.calc.data.InterestPeriod;
+import
org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
+import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod;
+import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
+
+@Slf4j
+@RequiredArgsConstructor
+public class ProgressiveLoanInterestScheduleModelParserServiceGsonImpl
implements ProgressiveLoanInterestScheduleModelParserService {
+
+ private final Gson gsonSerializer = createSerializer();
+
+ private Gson createSerializer() {
+ return new GsonBuilder().registerTypeAdapter(LocalDate.class, new
LocalDateAdapter().nullSafe())
+
.setNumberToNumberStrategy(ToNumberPolicy.BIG_DECIMAL).registerTypeAdapter(Money.class,
new MoneySerializer())
+ .addDeserializationExclusionStrategy(new
JsonExcludeAnnotationBasedExclusionStrategy())
+ .addSerializationExclusionStrategy(new
JsonExcludeAnnotationBasedExclusionStrategy()).create();
+ }
+
+ private Gson
createDeserializer(LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail, MathContext mc,
+ Integer installmentAmountInMultipliesOf) {
+ InterestScheduleModelServiceGsonContext ctx = new
InterestScheduleModelServiceGsonContext(
+ new
MonetaryCurrency(loanProductRelatedDetail.getCurrencyData()), mc,
loanProductRelatedDetail,
+ installmentAmountInMultipliesOf);
+ return new GsonBuilder().registerTypeAdapter(LocalDate.class, new
LocalDateAdapter().nullSafe())
+ .setNumberToNumberStrategy(ToNumberPolicy.BIG_DECIMAL)
+ .registerTypeAdapter(Money.class, new
MoneyDeserializer(ctx.getMc(), ctx.getCurrency()))
+ .registerTypeAdapter(InterestPeriod.class,
(InstanceCreator<InterestPeriod>) ctx::createInterestPeriodInstance)
+
.registerTypeAdapter(ProgressiveLoanInterestScheduleModel.class,
+
(InstanceCreator<ProgressiveLoanInterestScheduleModel>)
ctx::createProgressiveLoanInterestScheduleModelInstance)
+ .registerTypeAdapter(RepaymentPeriod.class,
(InstanceCreator<RepaymentPeriod>) ctx::createRepaymentPeriodInstance)
+ .addDeserializationExclusionStrategy(new
JsonExcludeAnnotationBasedExclusionStrategy())
+ .addSerializationExclusionStrategy(new
JsonExcludeAnnotationBasedExclusionStrategy()).create();
+ }
+
+ @Override
+ public String toJson(@NotNull ProgressiveLoanInterestScheduleModel model) {
+ return gsonSerializer.toJson(model);
+ }
+
+ @Override
+ public ProgressiveLoanInterestScheduleModel fromJson(String s,
+ @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail, @NotNull MathContext mc,
+ Integer installmentAmountInMultipliesOf) {
+ if (s == null) {
+ return null;
+ }
+ try {
+ Gson gson = createDeserializer(loanProductRelatedDetail, mc,
installmentAmountInMultipliesOf);
+ return gson.fromJson(s,
ProgressiveLoanInterestScheduleModel.class);
+ } catch (Exception e) {
+ log.warn("Failed to parse ProgressiveLoanInterestScheduleModel
json. Falling back to default value.", e);
+ return null;
+ }
+ }
+}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/InterestPeriod.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/InterestPeriod.java
index 0f461fe20b..71431d4576 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/InterestPeriod.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/InterestPeriod.java
@@ -29,6 +29,7 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
+import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.Money;
@@ -39,6 +40,7 @@ import org.apache.fineract.organisation.monetary.domain.Money;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class InterestPeriod implements Comparable<InterestPeriod> {
+ @JsonExclude
private final RepaymentPeriod repaymentPeriod;
@Setter
@NotNull
@@ -58,9 +60,21 @@ public class InterestPeriod implements
Comparable<InterestPeriod> {
private Money balanceCorrectionAmount;
private Money outstandingLoanBalance;
+ @JsonExclude
private final MathContext mc;
private final boolean isPaused;
+ public static InterestPeriod copy(@NotNull RepaymentPeriod
repaymentPeriod, @NotNull InterestPeriod interestPeriod, MathContext mc) {
+ return new InterestPeriod(repaymentPeriod,
interestPeriod.getFromDate(), interestPeriod.getDueDate(),
+ interestPeriod.getRateFactor(),
interestPeriod.getRateFactorTillPeriodDueDate(),
interestPeriod.getChargebackPrincipal(),
+ interestPeriod.getChargebackInterest(),
interestPeriod.getDisbursementAmount(),
interestPeriod.getBalanceCorrectionAmount(),
+ interestPeriod.getOutstandingLoanBalance(), mc,
interestPeriod.isPaused());
+ }
+
+ public static InterestPeriod empty(@NotNull RepaymentPeriod
repaymentPeriod, MathContext mc) {
+ return new InterestPeriod(repaymentPeriod, null, null, null, null,
null, null, null, null, null, mc, false);
+ }
+
public static InterestPeriod copy(@NotNull RepaymentPeriod
repaymentPeriod, @NotNull InterestPeriod interestPeriod) {
return new InterestPeriod(repaymentPeriod,
interestPeriod.getFromDate(), interestPeriod.getDueDate(),
interestPeriod.getRateFactor(),
interestPeriod.getRateFactorTillPeriodDueDate(),
interestPeriod.getChargebackPrincipal(),
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
index 3a74d32f23..58debfb913 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
@@ -39,8 +39,10 @@ import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.Accessors;
+import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.Money;
@@ -50,14 +52,18 @@ import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaym
@Data
@Accessors(fluent = true)
+@AllArgsConstructor
public class ProgressiveLoanInterestScheduleModel {
private final List<RepaymentPeriod> repaymentPeriods;
private final TreeSet<InterestRate> interestRates;
+ @JsonExclude
private final LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail;
private final Map<LoanTermVariationType, List<LoanTermVariationsData>>
loanTermVariations;
private final Integer installmentAmountInMultiplesOf;
+ @JsonExclude
private final MathContext mc;
+ @JsonExclude
private final Money zero;
private final Map<LoanInterestScheduleModelModifiers, Boolean> modifiers;
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
index 5390c17058..e4dd3025d9 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
@@ -31,6 +31,7 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
+import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.util.Memo;
@@ -39,6 +40,7 @@ import org.apache.fineract.portfolio.util.Memo;
@EqualsAndHashCode(exclude = { "previous" })
public final class RepaymentPeriod {
+ @JsonExclude
private final RepaymentPeriod previous;
@Getter
private final LocalDate fromDate;
@@ -59,11 +61,16 @@ public final class RepaymentPeriod {
@Getter
private Money paidInterest;
+ @JsonExclude
private final MathContext mc;
+ @JsonExclude
private Memo<BigDecimal> rateFactorPlus1Calculation;
+ @JsonExclude
private Memo<Money> calculatedDueInterestCalculation;
+ @JsonExclude
private Memo<Money> dueInterestCalculation;
+ @JsonExclude
private Memo<Money> outstandingBalanceCalculation;
private RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate,
LocalDate dueDate, List<InterestPeriod> interestPeriods,
@@ -79,6 +86,10 @@ public final class RepaymentPeriod {
this.mc = mc;
}
+ public static RepaymentPeriod empty(RepaymentPeriod previous, MathContext
mc) {
+ return new RepaymentPeriod(previous, null, null, new ArrayList<>(),
null, null, null, null, mc);
+ }
+
public static RepaymentPeriod create(RepaymentPeriod previous, LocalDate
fromDate, LocalDate dueDate, Money emi, MathContext mc) {
final Money zero = emi.zero();
final RepaymentPeriod newRepaymentPeriod = new
RepaymentPeriod(previous, fromDate, dueDate, new ArrayList<>(), emi, emi, zero,
zero,
@@ -94,7 +105,7 @@ public final class RepaymentPeriod {
repaymentPeriod.paidInterest, mc);
// There is always at least 1 interest period, by default with same
from-due date as repayment period
for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
-
newRepaymentPeriod.interestPeriods.add(InterestPeriod.copy(newRepaymentPeriod,
interestPeriod));
+
newRepaymentPeriod.interestPeriods.add(InterestPeriod.copy(newRepaymentPeriod,
interestPeriod, mc));
}
return newRepaymentPeriod;
}
diff --git
a/fineract-progressive-loan/src/main/resources/jpa/progressiveloan/db/changelog/tenant/module/progressiveloan/module-changelog-master.xml
b/fineract-progressive-loan/src/main/resources/db/changelog/tenant/module/progressiveloan/module-changelog-master.xml
similarity index 92%
rename from
fineract-progressive-loan/src/main/resources/jpa/progressiveloan/db/changelog/tenant/module/progressiveloan/module-changelog-master.xml
rename to
fineract-progressive-loan/src/main/resources/db/changelog/tenant/module/progressiveloan/module-changelog-master.xml
index baf21a9ba0..60b1a5e140 100644
---
a/fineract-progressive-loan/src/main/resources/jpa/progressiveloan/db/changelog/tenant/module/progressiveloan/module-changelog-master.xml
+++
b/fineract-progressive-loan/src/main/resources/db/changelog/tenant/module/progressiveloan/module-changelog-master.xml
@@ -23,4 +23,5 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<!-- Sequence is starting from 5000 to make it easier to move existing
liquibase changesets here -->
+ <include file="parts/5001_create_progressive_loan_model.xml"
relativeToChangelogFile="true"/>
</databaseChangeLog>
diff --git
a/fineract-progressive-loan/src/main/resources/db/changelog/tenant/module/progressiveloan/parts/5001_create_progressive_loan_model.xml
b/fineract-progressive-loan/src/main/resources/db/changelog/tenant/module/progressiveloan/parts/5001_create_progressive_loan_model.xml
new file mode 100644
index 0000000000..de328cf033
--- /dev/null
+++
b/fineract-progressive-loan/src/main/resources/db/changelog/tenant/module/progressiveloan/parts/5001_create_progressive_loan_model.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<databaseChangeLog
+ xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
+
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.31.xsd"
+ objectQuotingStrategy="QUOTE_ONLY_RESERVED_WORDS">
+ <changeSet id="1744116182-1" author="fineract">
+ <createTable tableName="m_loan_progressive_model">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"
primaryKeyName="pk_m_loan_progressive_model"/>
+ </column>
+ <column name="version" type="INT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="loan_id" type="BIGINT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="json_model" type="TEXT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="business_date" type="DATE">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
+ </changeSet>
+ <changeSet id="1744116182-2" author="fineract">
+ <addUniqueConstraint columnNames="loan_id"
constraintName="uc_m_loan_progressive_model_loan"
+ tableName="m_loan_progressive_model"/>
+ </changeSet>
+ <changeSet id="1744116182-3" author="fineract">
+ <addForeignKeyConstraint baseColumnNames="loan_id"
baseTableName="m_loan_progressive_model"
+
constraintName="FK_M_LOAN_PROGRESSIVE_MODEL_ON_LOAN" referencedColumnNames="id"
+ referencedTableName="m_loan"/>
+ </changeSet>
+ <changeSet author="fineract" id="1744116182-4-my" context="mysql">
+ <addColumn tableName="m_loan_progressive_model">
+ <column name="last_modified_on_utc" type="DATETIME(6)">
+ <constraints nullable="false"/>
+ </column>
+ </addColumn>
+ </changeSet>
+ <changeSet author="fineract" id="1744116182
+ -4-pg" context="postgresql">
+ <addColumn tableName="m_loan_progressive_model">
+ <column name="last_modified_on_utc" type="TIMESTAMP WITH TIME
ZONE">
+ <constraints nullable="false"/>
+ </column>
+ </addColumn>
+ </changeSet>
+</databaseChangeLog>
diff --git
a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java
b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java
index 33c1b341eb..5bb211460e 100644
---
a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java
+++
b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java
@@ -37,6 +37,7 @@ import
org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
+import
org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanInterestScheduleModelParserServiceGsonImpl;
import org.apache.fineract.portfolio.loanproduct.calc.data.InterestPeriod;
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
import
org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
@@ -70,6 +71,7 @@ class ProgressiveEMICalculatorTest {
private static List<LoanRepaymentScheduleInstallment> periods;
private final BigDecimal interestRate = BigDecimal.valueOf(0.094822);
+ private final ProgressiveLoanInterestScheduleModelParserServiceGsonImpl
interestScheduleModelService = new
ProgressiveLoanInterestScheduleModelParserServiceGsonImpl();
@BeforeAll
public static void init() {
@@ -3114,6 +3116,79 @@ class ProgressiveEMICalculatorTest {
checkPeriod(interestSchedule, 5, 0, 17.05, 0.005833333333,
0.0988749999945, 0.10, 16.95, 0.0);
}
+ @Test
+ public void test_interest_schedule_model_service_serialization() {
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
+ repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2,
1)),
+ repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3,
1)),
+ repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4,
1)),
+ repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5,
1)),
+ repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6,
1)),
+ repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7,
1)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(7.0);
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestScheduleExpected =
emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(100.0);
+ ProgressiveLoanInterestScheduleModel interestScheduleActual =
copyJson(interestScheduleExpected);
+ verifyAllPeriods(interestScheduleExpected, interestScheduleActual);
+ emiCalculator.addDisbursement(interestScheduleExpected,
LocalDate.of(2024, 1, 1), disbursedAmount);
+ emiCalculator.addDisbursement(interestScheduleActual,
LocalDate.of(2024, 1, 1), disbursedAmount);
+ verifyAllPeriods(interestScheduleExpected, interestScheduleActual);
+ interestScheduleActual = copyJson(interestScheduleExpected);
+ verifyAllPeriods(interestScheduleExpected, interestScheduleActual);
+
+ // repay 1st period
+ LocalDate txnDate = LocalDate.of(2024, 2, 1);
+ emiCalculator.payPrincipal(interestScheduleExpected, txnDate, txnDate,
toMoney(16.43));
+ emiCalculator.payInterest(interestScheduleExpected, txnDate, txnDate,
toMoney(0.58));
+ emiCalculator.payPrincipal(interestScheduleActual, txnDate, txnDate,
toMoney(16.43));
+ emiCalculator.payInterest(interestScheduleActual, txnDate, txnDate,
toMoney(0.58));
+
+ verifyAllPeriods(interestScheduleExpected, interestScheduleActual);
+ interestScheduleActual = copyJson(interestScheduleExpected);
+ verifyAllPeriods(interestScheduleExpected, interestScheduleActual);
+
+ // repay 2nd period
+ txnDate = LocalDate.of(2024, 3, 1);
+ emiCalculator.payPrincipal(interestScheduleExpected, txnDate, txnDate,
toMoney(16.52));
+ emiCalculator.payInterest(interestScheduleExpected, txnDate, txnDate,
toMoney(0.49));
+ emiCalculator.payPrincipal(interestScheduleActual, txnDate, txnDate,
toMoney(16.52));
+ emiCalculator.payInterest(interestScheduleActual, txnDate, txnDate,
toMoney(0.49));
+
+ verifyAllPeriods(interestScheduleExpected, interestScheduleActual);
+ interestScheduleActual = copyJson(interestScheduleExpected);
+ verifyAllPeriods(interestScheduleExpected, interestScheduleActual);
+
+ // chargeback
+ txnDate = LocalDate.of(2024, 3, 15);
+ emiCalculator.chargebackPrincipal(interestScheduleExpected, txnDate,
toMoney(16.52));
+ emiCalculator.chargebackInterest(interestScheduleExpected, txnDate,
toMoney(0.49));
+ emiCalculator.chargebackPrincipal(interestScheduleActual, txnDate,
toMoney(16.52));
+ emiCalculator.chargebackInterest(interestScheduleActual, txnDate,
toMoney(0.49));
+
+ verifyAllPeriods(interestScheduleExpected, interestScheduleActual);
+ interestScheduleActual = copyJson(interestScheduleExpected);
+ verifyAllPeriods(interestScheduleExpected, interestScheduleActual);
+
+ PeriodDueDetails dueAmountsExpected =
emiCalculator.getDueAmounts(interestScheduleExpected, LocalDate.of(2024, 4, 1),
txnDate);
+ PeriodDueDetails dueAmountsActual =
emiCalculator.getDueAmounts(interestScheduleActual, LocalDate.of(2024, 4, 1),
txnDate);
+
+
Assertions.assertEquals(toDouble(dueAmountsExpected.getDuePrincipal()),
toDouble(dueAmountsActual.getDuePrincipal()));
+ Assertions.assertEquals(toDouble(dueAmountsExpected.getDueInterest()),
toDouble(dueAmountsActual.getDueInterest()));
+
+ }
+
private static LoanScheduleModelRepaymentPeriod repayment(int
periodNumber, LocalDate fromDate, LocalDate dueDate) {
final Money zeroAmount = Money.zero(currency);
return LoanScheduleModelRepaymentPeriod.repayment(periodNumber,
fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount,
@@ -3202,4 +3277,24 @@ class ProgressiveEMICalculatorTest {
private static Money toMoney(final double value) {
return Money.of(currency, BigDecimal.valueOf(value));
}
+
+ private static void verifyAllPeriods(final
ProgressiveLoanInterestScheduleModel expectedModel,
+ final ProgressiveLoanInterestScheduleModel actualModel) {
+ for (int repInd = 0; repInd < expectedModel.repaymentPeriods().size();
repInd++) {
+ RepaymentPeriod repaymentPeriod =
expectedModel.repaymentPeriods().get(repInd);
+ for (int interestPeriodIndex = 0; interestPeriodIndex <
repaymentPeriod.getInterestPeriods().size(); interestPeriodIndex++) {
+ final InterestPeriod interestPeriod =
repaymentPeriod.getInterestPeriods().get(interestPeriodIndex);
+ checkPeriod(actualModel, repInd, interestPeriodIndex,
toDouble(repaymentPeriod.getEmi()),
+
toDouble(applyMathContext(interestPeriod.getRateFactor())),
toDouble(interestPeriod.getCalculatedDueInterest()),
+ toDouble(repaymentPeriod.getDueInterest()),
toDouble(repaymentPeriod.getDuePrincipal()),
+ toDouble(repaymentPeriod.getOutstandingLoanBalance()));
+ }
+ }
+ }
+
+ private ProgressiveLoanInterestScheduleModel
copyJson(ProgressiveLoanInterestScheduleModel toCopy) {
+ String json = interestScheduleModelService.toJson(toCopy);
+ return interestScheduleModelService.fromJson(json,
toCopy.loanProductRelatedDetail(), toCopy.mc(),
+ toCopy.installmentAmountInMultiplesOf());
+ }
}
diff --git
a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
index fd9f869f14..f44f38478e 100644
--- a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
+++ b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
@@ -35,6 +35,7 @@
<include
file="db/changelog/tenant/module/loan/module-changelog-master.xml"
context="tenant_db AND !initial_switch"/>
<include
file="db/changelog/tenant/module/investor/module-changelog-master.xml"
context="tenant_db AND !initial_switch"/>
<includeAll path="db/custom-changelog" errorIfMissingOrEmpty="false"
context="tenant_db AND !initial_switch AND custom_changelog"/>
+ <include
file="/db/changelog/tenant/module/progressiveloan/module-changelog-master.xml"
context="tenant_db AND !initial_switch"/>
<!-- Scripts to run after the modules were initialized -->
<include file="tenant/final-changelog-tenant.xml"
relativeToChangelogFile="true" context="tenant_db AND !initial_switch"/>
</databaseChangeLog>
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanModelIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanModelIntegrationTest.java
new file mode 100644
index 0000000000..3e6aa001ca
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanModelIntegrationTest.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests;
+
+import org.apache.fineract.client.models.ProgressiveLoanInterestScheduleModel;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.junit.jupiter.api.Test;
+
+public class ProgressiveLoanModelIntegrationTest extends
BaseLoanIntegrationTest {
+
+ private final Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+ private final Long loanProductId =
loanProductHelper.createLoanProduct(create4IProgressive().isInterestRecalculationEnabled(true))
+ .getResourceId();
+
+ @Test
+ public void testModelReturnsNullAndThenSaveThenNotNull() {
+ runAt("1 January 2024", () -> {
+ Long loanId = applyAndApproveProgressiveLoan(clientId,
loanProductId, "1 January 2024", 1000.0, 96.32, 6, null);
+ loanTransactionHelper.disburseLoan(loanId, "1 January 2024",
1000.0);
+
+ // Model not saved, fetching it. It should return null
+ ProgressiveLoanInterestScheduleModel
progressiveLoanInterestScheduleModelResponse1 = ok(
+ fineractClient().progressiveLoanApi.fetchModel(loanId));
+
+ assertThat(progressiveLoanInterestScheduleModelResponse1).isNull();
+
+ // Forcing Model recalculation and save to database. It should
return the actual model.
+ ProgressiveLoanInterestScheduleModel ok =
ok(fineractClient().progressiveLoanApi.updateModel(loanId));
+ assertThat(ok).isNotNull();
+
+ // Model saved in previous step. API should return the previous
model.
+ ProgressiveLoanInterestScheduleModel
progressiveLoanInterestScheduleModelResponse2 = ok(
+ fineractClient().progressiveLoanApi.fetchModel(loanId));
+
assertThat(progressiveLoanInterestScheduleModelResponse2).isNotNull();
+ });
+ }
+
+}