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();
+        });
+    }
+
+}

Reply via email to