diff --git a/fineract-accounting/src/main/resources/jpa/static-weaving/module/fineract-accounting/persistence.xml b/fineract-accounting/src/main/resources/jpa/static-weaving/module/fineract-accounting/persistence.xml
index d1e6be5a80f..23ade62e2fa 100644
--- a/fineract-accounting/src/main/resources/jpa/static-weaving/module/fineract-accounting/persistence.xml
+++ b/fineract-accounting/src/main/resources/jpa/static-weaving/module/fineract-accounting/persistence.xml
@@ -48,6 +48,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-branch/src/main/resources/jpa/static-weaving/module/fineract-branch/persistence.xml b/fineract-branch/src/main/resources/jpa/static-weaving/module/fineract-branch/persistence.xml
index ee2b5fcfef2..18fae21beff 100644
--- a/fineract-branch/src/main/resources/jpa/static-weaving/module/fineract-branch/persistence.xml
+++ b/fineract-branch/src/main/resources/jpa/static-weaving/module/fineract-branch/persistence.xml
@@ -48,6 +48,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-charge/src/main/resources/jpa/static-weaving/module/fineract-charge/persistence.xml b/fineract-charge/src/main/resources/jpa/static-weaving/module/fineract-charge/persistence.xml
index 7482e322cdd..94c1a2f8dec 100644
--- a/fineract-charge/src/main/resources/jpa/static-weaving/module/fineract-charge/persistence.xml
+++ b/fineract-charge/src/main/resources/jpa/static-weaving/module/fineract-charge/persistence.xml
@@ -48,6 +48,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-cob/src/main/resources/jpa/static-weaving/module/fineract-cob/persistence.xml b/fineract-cob/src/main/resources/jpa/static-weaving/module/fineract-cob/persistence.xml
index 946e34b7079..d7fdec20372 100644
--- a/fineract-cob/src/main/resources/jpa/static-weaving/module/fineract-cob/persistence.xml
+++ b/fineract-cob/src/main/resources/jpa/static-weaving/module/fineract-cob/persistence.xml
@@ -47,6 +47,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucket.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucket.java
index 6bda1952377..ec44dfeabf0 100644
--- a/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucket.java
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucket.java
@@ -18,12 +18,15 @@
*/
package org.apache.fineract.portfolio.delinquency.domain;
+import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
+import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
+import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.persistence.Version;
@@ -48,6 +51,13 @@ public class DelinquencyBucket extends AbstractAuditableWithUTCDateTimeCustom ranges;
+ @OneToOne(mappedBy = "bucket", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+ private DelinquencyMinimumPaymentPeriodAndRule minimumPaymentPeriodAndRule;
+
+ @Enumerated
+ @Column(name = "bucket_type")
+ private DelinquencyBucketType bucketType;
+
@Version
private Long version;
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketType.java
new file mode 100644
index 00000000000..443537b2c53
--- /dev/null
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketType.java
@@ -0,0 +1,47 @@
+/**
+ * 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.delinquency.domain;
+
+import java.util.Arrays;
+import lombok.Getter;
+
+public enum DelinquencyBucketType {
+
+ REGULAR(1L, "bucketType.regular"), //
+ WORKING_CAPITAL(2L, "bucketType.workingCapital");
+
+ @Getter
+ private final Long value;
+
+ @Getter
+ private final String code;
+
+ DelinquencyBucketType(Long value, String code) {
+ this.value = value;
+ this.code = code;
+ }
+
+ public static DelinquencyBucketType fromLong(Long value) {
+ if (value == null) {
+ return null;
+ }
+ return Arrays.stream(DelinquencyBucketType.values()).filter(v -> v.getValue().equals(value)).findAny().orElse(REGULAR);
+ }
+}
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketTypeConverter.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketTypeConverter.java
new file mode 100644
index 00000000000..650955fd4d9
--- /dev/null
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketTypeConverter.java
@@ -0,0 +1,38 @@
+/**
+ * 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.delinquency.domain;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+import java.util.Optional;
+
+@Converter(autoApply = true)
+public class DelinquencyBucketTypeConverter implements AttributeConverter {
+
+ @Override
+ public Long convertToDatabaseColumn(DelinquencyBucketType delinquencyBucketType) {
+ return Optional.ofNullable(delinquencyBucketType).map(DelinquencyBucketType::getValue).orElse(null);
+ }
+
+ @Override
+ public DelinquencyBucketType convertToEntityAttribute(Long aLong) {
+ return DelinquencyBucketType.fromLong(aLong);
+ }
+}
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyFrequencyType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyFrequencyType.java
new file mode 100644
index 00000000000..67d42992bc3
--- /dev/null
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyFrequencyType.java
@@ -0,0 +1,50 @@
+/**
+ * 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.delinquency.domain;
+
+import java.util.Arrays;
+import lombok.Getter;
+
+public enum DelinquencyFrequencyType {
+
+ DAYS(0, "delinquencyFrequencyType.days"), //
+ WEEKS(1, "delinquencyFrequencyType.weeks"), //
+ MONTHS(2, "delinquencyFrequencyType.months"), //
+ YEARS(3, "delinquencyFrequencyType.years");
+
+ @Getter
+ private final Integer value;
+ @Getter
+ private final String code;
+
+ DelinquencyFrequencyType(final Integer value, final String code) {
+ this.value = value;
+ this.code = code;
+ }
+
+ public static DelinquencyFrequencyType fromInt(final Integer v) {
+ if (v == null) {
+ return null;
+ }
+ return Arrays.stream(values()).filter(e -> e.getValue().equals(v)).findAny()
+ .orElseThrow(() -> new IllegalArgumentException("Invalid value of DelinquencyFrequencyType: " + v));
+ }
+
+}
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyFrequencyTypeConverter.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyFrequencyTypeConverter.java
new file mode 100644
index 00000000000..960daf2844a
--- /dev/null
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyFrequencyTypeConverter.java
@@ -0,0 +1,39 @@
+/**
+ * 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.delinquency.domain;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+import java.util.Optional;
+
+@Converter(autoApply = true)
+public class DelinquencyFrequencyTypeConverter implements AttributeConverter {
+
+ @Override
+ public Integer convertToDatabaseColumn(DelinquencyFrequencyType delinquencyFrequencyType) {
+ return Optional.ofNullable(delinquencyFrequencyType).map(DelinquencyFrequencyType::getValue).orElse(null);
+ }
+
+ @Override
+ public DelinquencyFrequencyType convertToEntityAttribute(Integer intValue) {
+ return DelinquencyFrequencyType.fromInt(intValue);
+
+ }
+}
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPayment.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPayment.java
new file mode 100644
index 00000000000..788156856f5
--- /dev/null
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPayment.java
@@ -0,0 +1,45 @@
+/**
+ * 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.delinquency.domain;
+
+import java.util.Arrays;
+import lombok.Getter;
+
+public enum DelinquencyMinimumPayment {
+
+ PERCENTAGE(1L, "delinquencyMinimumPayment.percentage"), //
+ FLAT(2L, "delinquencyMinimumPayment.flat");
+
+ @Getter
+ private final Long value;
+ @Getter
+ private final String code;
+
+ DelinquencyMinimumPayment(Long value, String code) {
+ this.value = value;
+ this.code = code;
+ }
+
+ public static DelinquencyMinimumPayment fromLong(Long value) {
+ if (value == null) {
+ return null;
+ }
+ return Arrays.stream(DelinquencyMinimumPayment.values()).filter(v -> v.getValue().equals(value)).findAny().orElse(null);
+ }
+}
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentConverter.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentConverter.java
new file mode 100644
index 00000000000..2aa3ee89378
--- /dev/null
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentConverter.java
@@ -0,0 +1,38 @@
+/**
+ * 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.delinquency.domain;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+import java.util.Optional;
+
+@Converter(autoApply = true)
+public class DelinquencyMinimumPaymentConverter implements AttributeConverter {
+
+ @Override
+ public Long convertToDatabaseColumn(DelinquencyMinimumPayment delinquencyMinimumPayment) {
+ return Optional.ofNullable(delinquencyMinimumPayment).map(DelinquencyMinimumPayment::getValue).orElse(null);
+ }
+
+ @Override
+ public DelinquencyMinimumPayment convertToEntityAttribute(Long aLong) {
+ return DelinquencyMinimumPayment.fromLong(aLong);
+ }
+}
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentPeriodAndRule.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentPeriodAndRule.java
new file mode 100644
index 00000000000..ca1ee989cd7
--- /dev/null
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentPeriodAndRule.java
@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fineract.portfolio.delinquency.domain;
+
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.OneToOne;
+import jakarta.persistence.Table;
+import java.io.Serial;
+import java.math.BigDecimal;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_delinquency_payment_rule")
+public class DelinquencyMinimumPaymentPeriodAndRule extends AbstractAuditableWithUTCDateTimeCustom {
+
+ @Serial
+ private static final long serialVersionUID = -9204385885041120403L;
+
+ @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+ @JoinColumn(name = "bucket_id", nullable = false, unique = true)
+ private DelinquencyBucket bucket;
+
+ @Column(name = "frequency", nullable = false)
+ private Long frequency;
+
+ @Column(name = "frequency_type", nullable = false)
+ private DelinquencyFrequencyType frequencyType;
+
+ @Column(name = "minimum_payment", nullable = false)
+ private BigDecimal minimumPayment;
+
+ @Column(name = "minimum_payment_type", nullable = false)
+ private DelinquencyMinimumPayment minimumPaymentType;
+}
diff --git a/fineract-core/src/main/resources/jpa/static-weaving/module/fineract-core/persistence.xml b/fineract-core/src/main/resources/jpa/static-weaving/module/fineract-core/persistence.xml
index 4883cc15e81..dda569b6f16 100644
--- a/fineract-core/src/main/resources/jpa/static-weaving/module/fineract-core/persistence.xml
+++ b/fineract-core/src/main/resources/jpa/static-weaving/module/fineract-core/persistence.xml
@@ -48,6 +48,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalStepDef.java
index 1590115c84a..b687dad6e31 100644
--- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalStepDef.java
+++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalStepDef.java
@@ -25,6 +25,7 @@
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.math.BigDecimal;
+import java.util.List;
import java.util.Map;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
@@ -33,10 +34,15 @@
import org.apache.fineract.client.feign.services.WorkingCapitalLoanProductsApi;
import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
import org.apache.fineract.client.models.DeleteWorkingCapitalLoanProductsProductIdResponse;
+import org.apache.fineract.client.models.DelinquencyBucketData;
+import org.apache.fineract.client.models.DelinquencyBucketRequest;
+import org.apache.fineract.client.models.DelinquencyRangeData;
import org.apache.fineract.client.models.GetConfigurableAttributes;
import org.apache.fineract.client.models.GetPaymentAllocation;
import org.apache.fineract.client.models.GetWorkingCapitalLoanProductsProductIdResponse;
+import org.apache.fineract.client.models.MinimumPaymentPeriodAndRule;
import org.apache.fineract.client.models.PostAllowAttributeOverrides;
+import org.apache.fineract.client.models.PostDelinquencyBucketResponse;
import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsRequest;
import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsResponse;
import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdRequest;
@@ -805,4 +811,62 @@ public void checkWorkingCapitalLoanProductDeleteFailure(Long productId) {
assertThat(exception.getDeveloperMessage())
.contains(ErrorMessageHelper.workingCapitalLoanProductIdentifiedDoesNotExistFailure(String.valueOf(productId)));
}
+
+ @When("Admin Calls Delinquency Template")
+ public void adminCallsDelinquencyTemplate() {
+ DelinquencyBucketData template = ok(() -> fineractFeignClient.delinquencyRangeAndBucketsManagement().getTemplate3());
+ log.info("Template DelinquencyBucketData: {}", template);
+ }
+
+ @When("Admin creates WC Delinquency Bucket With Values")
+ public void adminCreatesWCDelinquencyBucketWithValues() {
+ DelinquencyBucketRequest delinquencyBucketRequest = new DelinquencyBucketRequest() //
+ .name("DB-" + System.currentTimeMillis()) //
+ .bucketType("2").ranges(List.of(1L)).minimumPaymentPeriodAndRule(new MinimumPaymentPeriodAndRule().frequency(1L)
+ .frequencyType(1).minimumPayment(BigDecimal.valueOf(1.23D)).minimumPaymentType(1L));
+ PostDelinquencyBucketResponse ok = ok(
+ () -> fineractFeignClient.delinquencyRangeAndBucketsManagement().createDelinquencyBucket(delinquencyBucketRequest));
+ assertThat(ok).isNotNull();
+ assertThat(ok.getResourceId()).isNotNull();
+ testContext().get().put("DELINQUENCY_BUCKET_ID", ok.getResourceId());
+ }
+
+ @Then("Get Delinquency Bucket has the following values")
+ public void getDelinquencyBucketHasTheFollowingValues() {
+ Long id = (Long) testContext().get().get("DELINQUENCY_BUCKET_ID");
+ DelinquencyBucketData delinquencyBucketData = ok(
+ () -> fineractFeignClient.delinquencyRangeAndBucketsManagement().getDelinquencyBucket(id));
+ log.info("Get DelinquencyBucketData: {}", delinquencyBucketData);
+ }
+
+ @Then("Get Delinquency Bucket With Template has the following values")
+ public void getDelinquencyBucketWithTemplateHasTheFollowingValues() {
+ Long id = (Long) testContext().get().get("DELINQUENCY_BUCKET_ID");
+ DelinquencyBucketData delinquencyBucketData = ok(() -> fineractFeignClient.delinquencyRangeAndBucketsManagement()
+ .getDelinquencyBucketUniversal(id, Map.of("template", "true")));
+ log.info("Get With Template DelinquencyBucketData: {}", delinquencyBucketData);
+ }
+
+ @When("Admin modifies WC Delinquency Bucket With Values")
+ public void adminModifiesWCDelinquencyBucketWithValues() {
+ Long id = (Long) testContext().get().get("DELINQUENCY_BUCKET_ID");
+ DelinquencyBucketData delinquencyBucketData = ok(
+ () -> fineractFeignClient.delinquencyRangeAndBucketsManagement().getDelinquencyBucket(id));
+ DelinquencyBucketRequest request = new DelinquencyBucketRequest() //
+ .ranges(delinquencyBucketData.getRanges().stream().map(DelinquencyRangeData::getId).toList())
+ // 1 Regular
+ // 2 Working Capital
+ .name(delinquencyBucketData.getName()).bucketType("2").minimumPaymentPeriodAndRule(new MinimumPaymentPeriodAndRule() //
+ .minimumPayment(BigDecimal.valueOf(7.89D)).minimumPaymentType(2L) //
+ .frequencyType(3) //
+ .frequency(4L) //
+ );
+ ok(() -> fineractFeignClient.delinquencyRangeAndBucketsManagement().updateDelinquencyBucket(id, request));
+ }
+
+ @When("Admin deletes WC Delinquency Bucket With Values")
+ public void adminDeletesWCDelinquencyBucketWithValues() {
+ Long id = (Long) testContext().get().get("DELINQUENCY_BUCKET_ID");
+ ok(() -> fineractFeignClient.delinquencyRangeAndBucketsManagement().deleteDelinquencyBucket(id));
+ }
}
diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDelinquencyConfiguration.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDelinquencyConfiguration.feature
new file mode 100644
index 00000000000..c6192122173
--- /dev/null
+++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDelinquencyConfiguration.feature
@@ -0,0 +1,12 @@
+@WCDelinquencyCFeature @WC
+Feature: Working Capital Delinquency Configuration
+
+ @TestRailId:CXXXX
+ Scenario: Verify Working Capital Delinquency Configuration CRUD
+ When Admin Calls Delinquency Template
+ When Admin creates WC Delinquency Bucket With Values
+ Then Get Delinquency Bucket has the following values
+ Then Get Delinquency Bucket With Template has the following values
+ When Admin modifies WC Delinquency Bucket With Values
+ Then Get Delinquency Bucket has the following values
+ When Admin deletes WC Delinquency Bucket With Values
\ No newline at end of file
diff --git a/fineract-investor/src/main/resources/jpa/static-weaving/module/fineract-investor/persistence.xml b/fineract-investor/src/main/resources/jpa/static-weaving/module/fineract-investor/persistence.xml
index 3ecc879408b..fd2d3078acb 100644
--- a/fineract-investor/src/main/resources/jpa/static-weaving/module/fineract-investor/persistence.xml
+++ b/fineract-investor/src/main/resources/jpa/static-weaving/module/fineract-investor/persistence.xml
@@ -48,6 +48,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java
index d4bd7d4108a..b6d9b34228d 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java
@@ -25,6 +25,12 @@ private DelinquencyApiConstants() {
}
public static final String NAME_PARAM_NAME = "name";
+ public static final String BUCKET_TYPE_PARAM_NAME = "bucketType";
+ public static final String MINIMUM_PAYMENT_PERIOD_AND_RULE_PARAM_NAME = "minimumPaymentPeriodAndRule";
+ public static final String FREQUENCY_PARAM_NAME = "frequency";
+ public static final String FREQUENCY_TYPE_PARAM_NAME = "frequencyType";
+ public static final String MINIMUM_PAYMENT_PARAM_NAME = "minimumPayment";
+ public static final String MINIMUM_PAYMENT_TYPE_PARAM_NAME = "minimumPaymentType";
public static final String CLASSIFICATION_PARAM_NAME = "classification";
public static final String RANGES_PARAM_NAME = "ranges";
public static final String MINIMUMAGEDAYS_PARAM_NAME = "minimumAgeDays";
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiResource.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiResource.java
index 811c6b539fd..71b24adaa00 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiResource.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiResource.java
@@ -33,17 +33,26 @@
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.UriInfo;
+import java.util.Arrays;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketType;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyFrequencyType;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPayment;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
import org.springframework.stereotype.Component;
@@ -58,6 +67,7 @@ public class DelinquencyApiResource {
private final DefaultToApiJsonSerializer jsonSerializerRange;
private final DelinquencyReadPlatformService readPlatformService;
private final PortfolioCommandSourceWritePlatformService commandWritePlatformService;
+ private final ApiRequestParameterHelper apiRequestParameterHelper;
public static final String DELINQUENCY_BUCKET = "DELINQUENCY_BUCKET";
@GET
@@ -127,6 +137,16 @@ public CommandProcessingResult deleteDelinquencyRange(
return commandWritePlatformService.logCommandSource(commandRequest);
}
+ @GET
+ @Path("buckets/template")
+ @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
+ @Produces(MediaType.APPLICATION_JSON)
+ @Operation(summary = "Template for Delinquency Buckets", description = "")
+ public DelinquencyBucketData getTemplate() {
+ securityContext.authenticatedUser().validateHasReadPermission(DELINQUENCY_BUCKET);
+ return handleTemplate(null);
+ }
+
@GET
@Path("buckets")
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@@ -143,9 +163,27 @@ public List getDelinquencyBuckets() {
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Retrieve a specific Delinquency Bucket based on the Id", description = "")
public DelinquencyBucketData getDelinquencyBucket(
- @PathParam("delinquencyBucketId") @Parameter(description = "delinquencyBucketId") final Long delinquencyBucketId) {
+ @PathParam("delinquencyBucketId") @Parameter(description = "delinquencyBucketId") final Long delinquencyBucketId,
+ @Context final UriInfo uriInfo) {
securityContext.authenticatedUser().validateHasReadPermission(DELINQUENCY_BUCKET);
- return this.readPlatformService.retrieveDelinquencyBucket(delinquencyBucketId);
+ final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ DelinquencyBucketData delinquencyBucketData = this.readPlatformService.retrieveDelinquencyBucket(delinquencyBucketId);
+ return settings.isTemplate() ? handleTemplate(delinquencyBucketData) : delinquencyBucketData;
+ }
+
+ private DelinquencyBucketData handleTemplate(DelinquencyBucketData delinquencyBucketData) {
+ if (delinquencyBucketData == null) {
+ delinquencyBucketData = DelinquencyBucketData.getDataInstance(null, null, List.of(), DelinquencyBucketType.REGULAR.getValue(),
+ null);
+ }
+ delinquencyBucketData.setRangesOptions(this.readPlatformService.retrieveAllDelinquencyRanges());
+ delinquencyBucketData.setBucketTypeOptions(
+ Arrays.stream(DelinquencyBucketType.values()).map(e -> CodeValueData.instance(e.getValue(), e.getCode())).toList());
+ delinquencyBucketData.setFrequencyTypeOptions(Arrays.stream(DelinquencyFrequencyType.values())
+ .map(e -> CodeValueData.instance(e.getValue().longValue(), e.getCode())).toList());
+ delinquencyBucketData.setMinimumPaymentOptions(
+ Arrays.stream(DelinquencyMinimumPayment.values()).map(e -> CodeValueData.instance(e.getValue(), e.getCode())).toList());
+ return delinquencyBucketData;
}
@POST
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyBucketRequest.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyBucketRequest.java
index ed50e91709a..67fcf117fa9 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyBucketRequest.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyBucketRequest.java
@@ -20,6 +20,7 @@
import java.io.Serial;
import java.io.Serializable;
+import java.math.BigDecimal;
import java.util.List;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -35,4 +36,20 @@ public class DelinquencyBucketRequest implements Serializable {
private String name;
private List ranges;
+ private String bucketType;
+ private MinimumPaymentPeriodAndRule minimumPaymentPeriodAndRule;
+
+ @Setter
+ @Getter
+ @NoArgsConstructor
+ public static class MinimumPaymentPeriodAndRule implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private Long frequency;
+ private Integer frequencyType;
+ private BigDecimal minimumPayment;
+ private Long minimumPaymentType;
+ }
}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/data/DelinquencyBucketData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/data/DelinquencyBucketData.java
index b078343cf4b..1f14c529081 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/data/DelinquencyBucketData.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/data/DelinquencyBucketData.java
@@ -19,11 +19,13 @@
package org.apache.fineract.portfolio.delinquency.data;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
@ToString
@AllArgsConstructor
@@ -34,5 +36,17 @@ public class DelinquencyBucketData implements Serializable {
private Long id;
private String name;
private List ranges;
+ private Long bucketType;
+ private DelinquencyMinimumPaymentPeriodAndRuleData minimumPaymentPeriodAndRule;
+
+ public static DelinquencyBucketData getDataInstance(Long id, String name, List ranges, Long bucketType,
+ DelinquencyMinimumPaymentPeriodAndRuleData minimumPaymentPeriodAndRule) {
+ return new DelinquencyBucketData(id, name, ranges, bucketType, minimumPaymentPeriodAndRule, null, null, null, null);
+ }
+
+ private List rangesOptions = new ArrayList<>();
+ private List bucketTypeOptions = new ArrayList<>();
+ private List frequencyTypeOptions = new ArrayList<>();
+ private List minimumPaymentOptions = new ArrayList<>();
}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/data/DelinquencyMinimumPaymentPeriodAndRuleData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/data/DelinquencyMinimumPaymentPeriodAndRuleData.java
new file mode 100644
index 00000000000..6eb2fda0013
--- /dev/null
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/data/DelinquencyMinimumPaymentPeriodAndRuleData.java
@@ -0,0 +1,38 @@
+/**
+ * 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.delinquency.data;
+
+import java.math.BigDecimal;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@ToString
+@AllArgsConstructor
+@Getter
+@Setter
+public class DelinquencyMinimumPaymentPeriodAndRuleData {
+
+ private Long frequency;
+ private Integer frequencyType;
+ private BigDecimal minimumPayment;
+ private Long minimumPaymentType;
+}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentPeriodAndRuleRepository.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentPeriodAndRuleRepository.java
new file mode 100644
index 00000000000..a2524032779
--- /dev/null
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentPeriodAndRuleRepository.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fineract.portfolio.delinquency.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.repository.CrudRepository;
+
+public interface DelinquencyMinimumPaymentPeriodAndRuleRepository extends JpaRepository,
+ JpaSpecificationExecutor, CrudRepository {
+
+}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/mapper/DelinquencyBucketMapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/mapper/DelinquencyBucketMapper.java
index 4b207c91aaa..454e6bd4d27 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/mapper/DelinquencyBucketMapper.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/mapper/DelinquencyBucketMapper.java
@@ -22,13 +22,23 @@
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketType;
import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
@Mapper(config = MapstructMapperConfig.class)
-public interface DelinquencyBucketMapper {
+public interface DelinquencyBucketMapper extends DelinquencyMinimumPaymentPeriodAndRuleMapper {
+ @Mapping(target = "rangesOptions", ignore = true)
+ @Mapping(target = "bucketTypeOptions", ignore = true)
+ @Mapping(target = "frequencyTypeOptions", ignore = true)
+ @Mapping(target = "minimumPaymentOptions", ignore = true)
DelinquencyBucketData map(DelinquencyBucket source);
List map(List sources);
+ default Long map(DelinquencyBucketType value) {
+ return value.getValue();
+ }
+
}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/mapper/DelinquencyMinimumPaymentPeriodAndRuleMapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/mapper/DelinquencyMinimumPaymentPeriodAndRuleMapper.java
new file mode 100644
index 00000000000..d69b7bbf312
--- /dev/null
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/mapper/DelinquencyMinimumPaymentPeriodAndRuleMapper.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fineract.portfolio.delinquency.mapper;
+
+import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyMinimumPaymentPeriodAndRuleData;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPayment;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule;
+import org.mapstruct.Mapper;
+
+@Mapper(config = MapstructMapperConfig.class)
+public interface DelinquencyMinimumPaymentPeriodAndRuleMapper {
+
+ DelinquencyMinimumPaymentPeriodAndRuleData map(DelinquencyMinimumPaymentPeriodAndRule source);
+
+ default Integer map(PeriodFrequencyType periodFrequencyType) {
+ return periodFrequencyType.getValue();
+ }
+
+ default Long map(DelinquencyMinimumPayment value) {
+ return value.getValue();
+ }
+}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
index 392e2ce814b..5803a916525 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
@@ -42,6 +42,11 @@
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappings;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketType;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyFrequencyType;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPayment;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRuleRepository;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
@@ -84,6 +89,7 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
private final BusinessEventNotifierService businessEventNotifierService;
private final DelinquencyWritePlatformServiceHelper delinquencyHelper;
+ private final DelinquencyMinimumPaymentPeriodAndRuleRepository delinquencyMinimumPaymentPeriodAndRuleRepository;
@Override
public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
@@ -338,7 +344,22 @@ private DelinquencyBucket createDelinquencyBucket(DelinquencyBucketData data, Ma
if (delinquencyBucket.isEmpty()) {
DelinquencyBucket newDelinquencyBucket = new DelinquencyBucket(data.getName());
+ newDelinquencyBucket.setBucketType(
+ data.getBucketType() != null ? DelinquencyBucketType.fromLong(data.getBucketType()) : DelinquencyBucketType.REGULAR);
+ if (DelinquencyBucketType.WORKING_CAPITAL.equals(newDelinquencyBucket.getBucketType())) {
+ // data.getMinimumPaymentPeriodAndRule()
+ newDelinquencyBucket.setMinimumPaymentPeriodAndRule(new DelinquencyMinimumPaymentPeriodAndRule());
+ newDelinquencyBucket.getMinimumPaymentPeriodAndRule().setBucket(newDelinquencyBucket);
+ newDelinquencyBucket.getMinimumPaymentPeriodAndRule().setFrequency(data.getMinimumPaymentPeriodAndRule().getFrequency());
+ newDelinquencyBucket.getMinimumPaymentPeriodAndRule().setMinimumPaymentType(
+ DelinquencyMinimumPayment.fromLong(data.getMinimumPaymentPeriodAndRule().getMinimumPaymentType()));
+ newDelinquencyBucket.getMinimumPaymentPeriodAndRule()
+ .setFrequencyType(DelinquencyFrequencyType.fromInt(data.getMinimumPaymentPeriodAndRule().getFrequencyType()));
+ newDelinquencyBucket.getMinimumPaymentPeriodAndRule()
+ .setMinimumPayment(data.getMinimumPaymentPeriodAndRule().getMinimumPayment());
+ }
repositoryBucket.save(newDelinquencyBucket);
+
setDelinquencyBucketMappings(newDelinquencyBucket, data);
return newDelinquencyBucket;
} else {
@@ -353,6 +374,52 @@ private DelinquencyBucket updateDelinquencyBucket(DelinquencyBucket delinquencyB
delinquencyBucket.setName(data.getName());
changes.put(DelinquencyApiConstants.NAME_PARAM_NAME, data.getName());
}
+ if (!data.getBucketType().equals(delinquencyBucket.getBucketType().getValue())) {
+ changes.put(DelinquencyApiConstants.BUCKET_TYPE_PARAM_NAME, data.getBucketType());
+ delinquencyBucket.setBucketType(DelinquencyBucketType.fromLong(data.getBucketType()));
+ }
+ if (delinquencyBucket.getBucketType().equals(DelinquencyBucketType.WORKING_CAPITAL)) {
+ if (delinquencyBucket.getMinimumPaymentPeriodAndRule() == null) {
+ delinquencyBucket.setMinimumPaymentPeriodAndRule(new DelinquencyMinimumPaymentPeriodAndRule());
+ delinquencyBucket.getMinimumPaymentPeriodAndRule().setBucket(delinquencyBucket);
+ }
+ if (!data.getMinimumPaymentPeriodAndRule().getFrequency()
+ .equals(delinquencyBucket.getMinimumPaymentPeriodAndRule().getFrequency())) {
+ delinquencyBucket.getMinimumPaymentPeriodAndRule().setFrequency(data.getMinimumPaymentPeriodAndRule().getFrequency());
+ changes.put(DelinquencyApiConstants.FREQUENCY_PARAM_NAME,
+ delinquencyBucket.getMinimumPaymentPeriodAndRule().getFrequency());
+ }
+ if (!data.getMinimumPaymentPeriodAndRule().getFrequencyType()
+ .equals(delinquencyBucket.getMinimumPaymentPeriodAndRule().getFrequencyType().getValue())) {
+ delinquencyBucket.getMinimumPaymentPeriodAndRule()
+ .setFrequencyType(DelinquencyFrequencyType.fromInt(data.getMinimumPaymentPeriodAndRule().getFrequencyType()));
+ changes.put(DelinquencyApiConstants.FREQUENCY_TYPE_PARAM_NAME,
+ delinquencyBucket.getMinimumPaymentPeriodAndRule().getFrequencyType().getValue());
+ }
+ if (!data.getMinimumPaymentPeriodAndRule().getMinimumPaymentType()
+ .equals(delinquencyBucket.getMinimumPaymentPeriodAndRule().getMinimumPaymentType().getValue())) {
+ changes.put(DelinquencyApiConstants.MINIMUM_PAYMENT_TYPE_PARAM_NAME,
+ delinquencyBucket.getMinimumPaymentPeriodAndRule().getMinimumPaymentType().getValue());
+ delinquencyBucket.getMinimumPaymentPeriodAndRule().setMinimumPaymentType(
+ DelinquencyMinimumPayment.fromLong(data.getMinimumPaymentPeriodAndRule().getMinimumPaymentType()));
+ }
+ if (data.getMinimumPaymentPeriodAndRule().getMinimumPayment()
+ .compareTo(delinquencyBucket.getMinimumPaymentPeriodAndRule().getMinimumPayment()) != 0) {
+ changes.put(DelinquencyApiConstants.MINIMUM_PAYMENT_PARAM_NAME,
+ delinquencyBucket.getMinimumPaymentPeriodAndRule().getMinimumPayment());
+ delinquencyBucket.getMinimumPaymentPeriodAndRule()
+ .setMinimumPayment(data.getMinimumPaymentPeriodAndRule().getMinimumPayment());
+ }
+ } else {
+ if (delinquencyBucket.getMinimumPaymentPeriodAndRule() != null) {
+ changes.put(DelinquencyApiConstants.MINIMUM_PAYMENT_PERIOD_AND_RULE_PARAM_NAME, null);
+ DelinquencyMinimumPaymentPeriodAndRule minimumPaymentPeriodAndRule = delinquencyBucket.getMinimumPaymentPeriodAndRule();
+ minimumPaymentPeriodAndRule.setBucket(null);
+ delinquencyBucket.setMinimumPaymentPeriodAndRule(null);
+ delinquencyMinimumPaymentPeriodAndRuleRepository.save(minimumPaymentPeriodAndRule);
+ delinquencyMinimumPaymentPeriodAndRuleRepository.delete(minimumPaymentPeriodAndRule);
+ }
+ }
if (!changes.isEmpty()) {
delinquencyBucket = repositoryBucket.save(delinquencyBucket);
}
@@ -363,7 +430,7 @@ private DelinquencyBucket updateDelinquencyBucket(DelinquencyBucket delinquencyB
private void setDelinquencyBucketMappings(DelinquencyBucket delinquencyBucket, DelinquencyBucketData data) {
List rangeIds = new ArrayList<>();
data.getRanges().forEach(dataRange -> rangeIds.add(dataRange.getId()));
-
+ delinquencyBucket.setBucketType(DelinquencyBucketType.fromLong(data.getBucketType()));
List ranges = repositoryRange.findAllById(rangeIds);
validateDelinquencyRanges(ranges);
List bucketMappings = repositoryBucketMappings.findByDelinquencyBucket(delinquencyBucket);
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/starter/DelinquencyConfiguration.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/starter/DelinquencyConfiguration.java
index 410f05e1958..022e3c4e47f 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/starter/DelinquencyConfiguration.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/starter/DelinquencyConfiguration.java
@@ -22,6 +22,7 @@
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRuleRepository;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyActionRepository;
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
@@ -83,12 +84,13 @@ public DelinquencyWritePlatformService delinquencyWritePlatformService(Delinquen
DelinquencyReadPlatformService delinquencyReadPlatformService, LoanDelinquencyActionRepository loanDelinquencyActionRepository,
DelinquencyActionParseAndValidator delinquencyActionParseAndValidator,
DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper,
- DelinquencyWritePlatformServiceHelper delinquencyWritePlatformServiceHelper) {
+ DelinquencyWritePlatformServiceHelper delinquencyWritePlatformServiceHelper,
+ DelinquencyMinimumPaymentPeriodAndRuleRepository delinquencyMinimumPaymentPeriodAndRuleRepository) {
return new DelinquencyWritePlatformServiceImpl(dataValidatorBucket, dataValidatorRange, repositoryRange, repositoryBucket,
repositoryBucketMappings, loanDelinquencyTagRepository, loanRepository, loanProductRepository, loanDelinquencyDomainService,
loanInstallmentDelinquencyTagRepository, delinquencyReadPlatformService, loanDelinquencyActionRepository,
delinquencyActionParseAndValidator, delinquencyEffectivePauseHelper, businessEventNotifierService,
- delinquencyWritePlatformServiceHelper);
+ delinquencyWritePlatformServiceHelper, delinquencyMinimumPaymentPeriodAndRuleRepository);
}
@Bean
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyBucketParseAndValidator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyBucketParseAndValidator.java
index 93888f2962e..ddd7e2bbc13 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyBucketParseAndValidator.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyBucketParseAndValidator.java
@@ -20,8 +20,10 @@
import com.google.gson.JsonObject;
import jakarta.validation.constraints.NotNull;
+import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
@@ -29,7 +31,9 @@
import org.apache.fineract.infrastructure.core.validator.ParseAndValidator;
import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyMinimumPaymentPeriodAndRuleData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketType;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@@ -55,7 +59,9 @@ private DelinquencyBucketData validateAndParseUpdate(final DataValidatorBuilder
}
jsonHelper.checkForUnsupportedParameters(element,
- List.of(DelinquencyApiConstants.NAME_PARAM_NAME, DelinquencyApiConstants.RANGES_PARAM_NAME));
+ List.of(DelinquencyApiConstants.NAME_PARAM_NAME, DelinquencyApiConstants.RANGES_PARAM_NAME,
+ DelinquencyApiConstants.BUCKET_TYPE_PARAM_NAME,
+ DelinquencyApiConstants.MINIMUM_PAYMENT_PERIOD_AND_RULE_PARAM_NAME));
final String name = jsonHelper.extractStringNamed(DelinquencyApiConstants.NAME_PARAM_NAME, element);
@@ -70,7 +76,49 @@ private DelinquencyBucketData validateAndParseUpdate(final DataValidatorBuilder
ranges.add(DelinquencyRangeData.reference(Long.parseLong(rangeId)));
}
}
- return dataValidator.hasError() ? null : new DelinquencyBucketData(null, name, ranges);
+
+ final Long bucketTypeParam = jsonHelper.extractLongNamed(DelinquencyApiConstants.BUCKET_TYPE_PARAM_NAME, element);
+ dataValidator.reset().parameter(DelinquencyApiConstants.BUCKET_TYPE_PARAM_NAME).value(bucketTypeParam).ignoreIfNull()
+ .isOneOfTheseValues(DelinquencyBucketType.REGULAR.getValue(), DelinquencyBucketType.WORKING_CAPITAL.getValue());
+ Long bucketType = bucketTypeParam == null ? DelinquencyBucketType.REGULAR.getValue() : bucketTypeParam;
+
+ DelinquencyMinimumPaymentPeriodAndRuleData minimumPaymentPeriodAndRule = null;
+ if (DelinquencyBucketType.WORKING_CAPITAL.getValue().equals(bucketType)) {
+ JsonObject minimumPaymentPeriodAndRuleElement = jsonHelper
+ .extractJsonObjectNamed(DelinquencyApiConstants.MINIMUM_PAYMENT_PERIOD_AND_RULE_PARAM_NAME, element);
+ minimumPaymentPeriodAndRule = validateAndParseUpdateMinimumPaymentPeriodAndRule(dataValidator,
+ minimumPaymentPeriodAndRuleElement, jsonHelper);
+
+ }
+
+ return dataValidator.hasError() ? null
+ : DelinquencyBucketData.getDataInstance(null, name, ranges, bucketType, minimumPaymentPeriodAndRule);
+ }
+
+ private DelinquencyMinimumPaymentPeriodAndRuleData validateAndParseUpdateMinimumPaymentPeriodAndRule(DataValidatorBuilder dataValidator,
+ JsonObject element, FromJsonHelper jsonHelper) {
+ dataValidator.reset().parameter(DelinquencyApiConstants.MINIMUM_PAYMENT_PERIOD_AND_RULE_PARAM_NAME).value(element).notNull();
+ if (element != null) {
+ Long frequency = jsonHelper.extractLongNamed(DelinquencyApiConstants.FREQUENCY_PARAM_NAME, element);
+ dataValidator.reset().parameter(DelinquencyApiConstants.FREQUENCY_PARAM_NAME).value(frequency).notNull();
+
+ Integer frequencyType = Math
+ .toIntExact(jsonHelper.extractLongNamed(DelinquencyApiConstants.FREQUENCY_TYPE_PARAM_NAME, element));
+ dataValidator.reset().parameter(DelinquencyApiConstants.FREQUENCY_TYPE_PARAM_NAME).value(frequencyType).notNull()
+ .inMinMaxRange(0, 3);
+
+ BigDecimal minimumPayment = jsonHelper.extractBigDecimalNamed(DelinquencyApiConstants.MINIMUM_PAYMENT_PARAM_NAME, element,
+ Locale.US);
+ dataValidator.reset().parameter(DelinquencyApiConstants.MINIMUM_PAYMENT_PARAM_NAME).value(minimumPayment).notNull()
+ .zeroOrPositiveAmount();
+
+ Long minimumPaymentType = jsonHelper.extractLongNamed(DelinquencyApiConstants.MINIMUM_PAYMENT_TYPE_PARAM_NAME, element);
+ dataValidator.reset().parameter(DelinquencyApiConstants.MINIMUM_PAYMENT_TYPE_PARAM_NAME).value(minimumPaymentType).notNull()
+ .inMinMaxRange(1, 2);
+
+ return new DelinquencyMinimumPaymentPeriodAndRuleData(frequency, frequencyType, minimumPayment, minimumPaymentType);
+ }
+ return null;
}
}
diff --git a/fineract-loan/src/main/resources/jpa/static-weaving/module/fineract-loan/persistence.xml b/fineract-loan/src/main/resources/jpa/static-weaving/module/fineract-loan/persistence.xml
index 8eb34963c21..32f739f46dd 100644
--- a/fineract-loan/src/main/resources/jpa/static-weaving/module/fineract-loan/persistence.xml
+++ b/fineract-loan/src/main/resources/jpa/static-weaving/module/fineract-loan/persistence.xml
@@ -48,6 +48,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-progressive-loan/src/main/resources/jpa/static-weaving/module/fineract-progressive-loan/persistence.xml b/fineract-progressive-loan/src/main/resources/jpa/static-weaving/module/fineract-progressive-loan/persistence.xml
index 27208079e4a..08ef888d56d 100644
--- a/fineract-progressive-loan/src/main/resources/jpa/static-weaving/module/fineract-progressive-loan/persistence.xml
+++ b/fineract-progressive-loan/src/main/resources/jpa/static-weaving/module/fineract-progressive-loan/persistence.xml
@@ -48,6 +48,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
index 895221d578a..b9abd911dcd 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
@@ -45,6 +45,7 @@
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
import org.apache.fineract.portfolio.common.service.CommonEnumerations;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketType;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
@@ -548,8 +549,8 @@ public LoanProductData mapRow(@NonNull final ResultSet rs, @SuppressWarnings("un
// Delinquency Buckets
final Long delinquencyBucketId = JdbcSupport.getLong(rs, "delinquencyBucketId");
final String delinquencyBucketName = rs.getString("delinquencyBucketName");
- final DelinquencyBucketData delinquencyBucket = new DelinquencyBucketData(delinquencyBucketId, delinquencyBucketName,
- new ArrayList<>());
+ final DelinquencyBucketData delinquencyBucket = DelinquencyBucketData.getDataInstance(delinquencyBucketId,
+ delinquencyBucketName, new ArrayList<>(), DelinquencyBucketType.REGULAR.getValue(), null);
final String loanScheduleTypeStr = rs.getString("loanScheduleType");
final LoanScheduleType loanScheduleType = LoanScheduleType.valueOf(loanScheduleTypeStr);
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 6ce8d437d8e..b66c93a6683 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -238,4 +238,5 @@
+
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0220_delinquency_for_working_capital_loans.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0220_delinquency_for_working_capital_loans.xml
new file mode 100644
index 00000000000..2c38991bd2a
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0220_delinquency_for_working_capital_loans.xml
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml b/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml
index 6ffd51d1ff3..6a2dc3b5837 100644
--- a/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml
+++ b/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml
@@ -48,6 +48,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java
index d692d1d9672..e74b9e70fd4 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java
@@ -57,6 +57,7 @@
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRuleRepository;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
@@ -130,6 +131,8 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
private LoanDelinquencyActionRepository loanDelinquencyActionRepository;
@Mock
private DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
+ @Mock
+ private DelinquencyMinimumPaymentPeriodAndRuleRepository delinquencyMinimumPaymentPeriodAndRuleRepository;
private DelinquencyWritePlatformServiceHelper delinquencyWritePlatformServiceHelper;
@@ -153,7 +156,7 @@ public void setUp() {
repositoryBucketMappings, loanDelinquencyTagRepository, loanRepository, loanProductRepository, loanDelinquencyDomainService,
loanInstallmentDelinquencyTagRepository, delinquencyReadPlatformService, loanDelinquencyActionRepository,
delinquencyActionParseAndValidator, delinquencyEffectivePauseHelper, businessEventNotifierService,
- delinquencyWritePlatformServiceHelper);
+ delinquencyWritePlatformServiceHelper, delinquencyMinimumPaymentPeriodAndRuleRepository);
}
@AfterAll
diff --git a/fineract-rates/src/main/resources/jpa/static-weaving/module/fineract-rates/persistence.xml b/fineract-rates/src/main/resources/jpa/static-weaving/module/fineract-rates/persistence.xml
index 61addd405f4..cd0da3f2429 100644
--- a/fineract-rates/src/main/resources/jpa/static-weaving/module/fineract-rates/persistence.xml
+++ b/fineract-rates/src/main/resources/jpa/static-weaving/module/fineract-rates/persistence.xml
@@ -48,6 +48,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-report/src/main/resources/jpa/static-weaving/module/fineract-report/persistence.xml b/fineract-report/src/main/resources/jpa/static-weaving/module/fineract-report/persistence.xml
index 4883cc15e81..dda569b6f16 100644
--- a/fineract-report/src/main/resources/jpa/static-weaving/module/fineract-report/persistence.xml
+++ b/fineract-report/src/main/resources/jpa/static-weaving/module/fineract-report/persistence.xml
@@ -48,6 +48,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-savings/src/main/resources/jpa/static-weaving/module/fineract-savings/persistence.xml b/fineract-savings/src/main/resources/jpa/static-weaving/module/fineract-savings/persistence.xml
index 2c9e1ba7ba1..4997a4dc594 100644
--- a/fineract-savings/src/main/resources/jpa/static-weaving/module/fineract-savings/persistence.xml
+++ b/fineract-savings/src/main/resources/jpa/static-weaving/module/fineract-savings/persistence.xml
@@ -48,6 +48,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-tax/src/main/resources/jpa/static-weaving/module/fineract-tax/persistence.xml b/fineract-tax/src/main/resources/jpa/static-weaving/module/fineract-tax/persistence.xml
index 4a482aecacb..0c5eb540a9a 100644
--- a/fineract-tax/src/main/resources/jpa/static-weaving/module/fineract-tax/persistence.xml
+++ b/fineract-tax/src/main/resources/jpa/static-weaving/module/fineract-tax/persistence.xml
@@ -48,6 +48,7 @@
org.apache.fineract.portfolio.calendar.domain.Calendar
org.apache.fineract.portfolio.calendar.domain.CalendarHistory
org.apache.fineract.portfolio.client.domain.ClientIdentifier
+ org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule
org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket
org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange
org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/businessstep/DelinquencyRangeScheduleBusinessStep.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/businessstep/DelinquencyRangeScheduleBusinessStep.java
new file mode 100644
index 00000000000..5a4dfcb9290
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/businessstep/DelinquencyRangeScheduleBusinessStep.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.cob.workingcapitalloan.businessstep;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.workingcapitalloan.repository.WcLoanDelinquencyRangeScheduleRepository;
+import org.apache.fineract.portfolio.workingcapitalloan.service.WcLoanDelinquencyRangeScheduleService;
+import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@RequiredArgsConstructor
+@Component
+public class DelinquencyRangeScheduleBusinessStep extends WorkingCapitalLoanCOBBusinessStep {
+
+ private final WcLoanDelinquencyRangeScheduleService rangeScheduleService;
+ private final WcLoanDelinquencyRangeScheduleRepository rangeScheduleRepository;
+
+ @Override
+ public WorkingCapitalLoan execute(WorkingCapitalLoan input) {
+ if (input.getActualDisbursementDate() == null) {
+ log.debug("Skipping delinquency range schedule for WC loan {} - not yet disbursed", input.getId());
+ return input;
+ }
+
+ java.time.LocalDate businessDate = DateUtils.getBusinessLocalDate();
+
+ boolean hasSchedule = rangeScheduleRepository.findTopByLoanIdOrderByPeriodNumberDesc(input.getId()).isPresent();
+ if (!hasSchedule) {
+ rangeScheduleService.generateInitialPeriod(input);
+ }
+
+ rangeScheduleService.generateNextPeriodIfNeeded(input, businessDate);
+ rangeScheduleService.evaluateExpiredPeriods(input, businessDate);
+
+ return input;
+ }
+
+ @Override
+ public String getEnumStyledName() {
+ return "WC_DELINQUENCY_RANGE_SCHEDULE";
+ }
+
+ @Override
+ public String getHumanReadableName() {
+ return "WC Delinquency Range Schedule";
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WcLoanDelinquencyRangeScheduleApiResource.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WcLoanDelinquencyRangeScheduleApiResource.java
new file mode 100644
index 00000000000..c5ef37b54f8
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WcLoanDelinquencyRangeScheduleApiResource.java
@@ -0,0 +1,65 @@
+/**
+ * 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.workingcapitalloan.api;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+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.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.workingcapitalloan.data.WcLoanDelinquencyRangeScheduleData;
+import org.apache.fineract.portfolio.workingcapitalloan.service.WcLoanDelinquencyRangeScheduleService;
+import org.springframework.stereotype.Component;
+
+@Path("/v1/working-capital-loans/{loanId}/delinquency-range-schedule")
+@Component
+@Tag(name = "Working Capital Loan Delinquency Range Schedule", description = "Manages delinquency range schedule periods for Working Capital loans")
+@RequiredArgsConstructor
+public class WcLoanDelinquencyRangeScheduleApiResource {
+
+ private static final String RESOURCE_NAME_FOR_PERMISSIONS = "WORKINGCAPITALLOAN";
+
+ private final PlatformSecurityContext context;
+ private final WcLoanDelinquencyRangeScheduleService rangeScheduleService;
+
+ @GET
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Retrieve Delinquency Range Schedule", description = "Retrieves the delinquency range schedule periods for a Working Capital loan")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = WcLoanDelinquencyRangeScheduleData.class)))) })
+ public List retrieveDelinquencyRangeSchedule(
+ @PathParam("loanId") @Parameter(description = "loanId") final Long loanId) {
+ this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
+ return rangeScheduleService.retrieveRangeSchedule(loanId);
+ }
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WcLoanDelinquencyRangeScheduleData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WcLoanDelinquencyRangeScheduleData.java
new file mode 100644
index 00000000000..ef4dc087938
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WcLoanDelinquencyRangeScheduleData.java
@@ -0,0 +1,42 @@
+/**
+ * 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.workingcapitalloan.data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class WcLoanDelinquencyRangeScheduleData {
+
+ private Long id;
+ private Long loanId;
+ private Integer periodNumber;
+ private LocalDate fromDate;
+ private LocalDate toDate;
+ private BigDecimal expectedAmount;
+ private BigDecimal paidAmount;
+ private BigDecimal outstandingAmount;
+ private Boolean minPaymentCriteriaMet;
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WcLoanDelinquencyRangeSchedule.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WcLoanDelinquencyRangeSchedule.java
new file mode 100644
index 00000000000..2e45ae55b08
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WcLoanDelinquencyRangeSchedule.java
@@ -0,0 +1,69 @@
+/**
+ * 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.workingcapitalloan.domain;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
+import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_wc_loan_delinquency_range_schedule", uniqueConstraints = {
+ @UniqueConstraint(columnNames = { "wc_loan_id", "period_number" }, name = "uc_wc_delinquency_range_schedule_loan_period") })
+public class WcLoanDelinquencyRangeSchedule extends AbstractAuditableWithUTCDateTimeCustom {
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "wc_loan_id", nullable = false)
+ private WorkingCapitalLoan loan;
+
+ @Column(name = "period_number", nullable = false)
+ private Integer periodNumber;
+
+ @Column(name = "from_date", nullable = false)
+ private LocalDate fromDate;
+
+ @Column(name = "to_date", nullable = false)
+ private LocalDate toDate;
+
+ @Column(name = "expected_amount", scale = 6, precision = 19)
+ private BigDecimal expectedAmount;
+
+ @Column(name = "paid_amount", scale = 6, precision = 19)
+ private BigDecimal paidAmount;
+
+ @Column(name = "outstanding_amount", scale = 6, precision = 19)
+ private BigDecimal outstandingAmount;
+
+ @Column(name = "min_payment_criteria_met")
+ private Boolean minPaymentCriteriaMet;
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WcLoanDelinquencyRangeScheduleMapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WcLoanDelinquencyRangeScheduleMapper.java
new file mode 100644
index 00000000000..c031c74ce94
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WcLoanDelinquencyRangeScheduleMapper.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.workingcapitalloan.mapper;
+
+import java.util.List;
+import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
+import org.apache.fineract.portfolio.workingcapitalloan.data.WcLoanDelinquencyRangeScheduleData;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.WcLoanDelinquencyRangeSchedule;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(config = MapstructMapperConfig.class)
+public interface WcLoanDelinquencyRangeScheduleMapper {
+
+ @Mapping(target = "loanId", source = "loan.id")
+ WcLoanDelinquencyRangeScheduleData toData(WcLoanDelinquencyRangeSchedule entity);
+
+ List toDataList(List entities);
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WcLoanDelinquencyRangeScheduleRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WcLoanDelinquencyRangeScheduleRepository.java
new file mode 100644
index 00000000000..8760b348230
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WcLoanDelinquencyRangeScheduleRepository.java
@@ -0,0 +1,38 @@
+/**
+ * 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.workingcapitalloan.repository;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Optional;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.WcLoanDelinquencyRangeSchedule;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface WcLoanDelinquencyRangeScheduleRepository extends JpaRepository {
+
+ List findByLoanIdOrderByPeriodNumberAsc(Long loanId);
+
+ Optional findTopByLoanIdOrderByPeriodNumberDesc(Long loanId);
+
+ Optional findByLoanIdAndFromDateLessThanEqualAndToDateGreaterThanEqual(Long loanId, LocalDate date,
+ LocalDate date2);
+
+ List findByLoanIdAndToDateLessThanAndMinPaymentCriteriaMetIsNull(Long loanId, LocalDate businessDate);
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WcLoanDelinquencyRangeScheduleService.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WcLoanDelinquencyRangeScheduleService.java
new file mode 100644
index 00000000000..74bafb844c9
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WcLoanDelinquencyRangeScheduleService.java
@@ -0,0 +1,39 @@
+/**
+ * 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.workingcapitalloan.service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+import org.apache.fineract.portfolio.workingcapitalloan.data.WcLoanDelinquencyRangeScheduleData;
+import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan;
+
+public interface WcLoanDelinquencyRangeScheduleService {
+
+ void generateInitialPeriod(WorkingCapitalLoan loan);
+
+ void generateNextPeriodIfNeeded(WorkingCapitalLoan loan, LocalDate businessDate);
+
+ void applyRepayment(Long loanId, LocalDate transactionDate, BigDecimal amount);
+
+ void evaluateExpiredPeriods(WorkingCapitalLoan loan, LocalDate businessDate);
+
+ List retrieveRangeSchedule(Long loanId);
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WcLoanDelinquencyRangeScheduleServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WcLoanDelinquencyRangeScheduleServiceImpl.java
new file mode 100644
index 00000000000..18ff8af0d3c
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WcLoanDelinquencyRangeScheduleServiceImpl.java
@@ -0,0 +1,172 @@
+/**
+ * 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.workingcapitalloan.service;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyFrequencyType;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule;
+import org.apache.fineract.portfolio.workingcapitalloan.data.WcLoanDelinquencyRangeScheduleData;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.WcLoanDelinquencyRangeSchedule;
+import org.apache.fineract.portfolio.workingcapitalloan.mapper.WcLoanDelinquencyRangeScheduleMapper;
+import org.apache.fineract.portfolio.workingcapitalloan.repository.WcLoanDelinquencyRangeScheduleRepository;
+import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan;
+import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProduct;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Slf4j
+@Service
+public class WcLoanDelinquencyRangeScheduleServiceImpl implements WcLoanDelinquencyRangeScheduleService {
+
+ private final WcLoanDelinquencyRangeScheduleRepository repository;
+ private final WcLoanDelinquencyRangeScheduleMapper mapper;
+
+ @Override
+ public void generateInitialPeriod(WorkingCapitalLoan loan) {
+ DelinquencyMinimumPaymentPeriodAndRule rule = getMinimumPaymentRule(loan);
+ if (rule == null) {
+ return;
+ }
+
+ LocalDate fromDate = loan.getActualDisbursementDate();
+ LocalDate toDate = calculateToDate(fromDate, rule.getFrequency(), rule.getFrequencyType());
+ BigDecimal expectedAmount = calculateExpectedAmount(loan, rule);
+
+ WcLoanDelinquencyRangeSchedule period = new WcLoanDelinquencyRangeSchedule();
+ period.setLoan(loan);
+ period.setPeriodNumber(1);
+ period.setFromDate(fromDate);
+ period.setToDate(toDate);
+ period.setExpectedAmount(expectedAmount);
+ period.setPaidAmount(BigDecimal.ZERO);
+ period.setOutstandingAmount(expectedAmount);
+ period.setMinPaymentCriteriaMet(null);
+
+ repository.saveAndFlush(period);
+ log.debug("Generated initial delinquency range schedule period for WC loan {}", loan.getId());
+ }
+
+ @Override
+ public void generateNextPeriodIfNeeded(WorkingCapitalLoan loan, LocalDate businessDate) {
+ DelinquencyMinimumPaymentPeriodAndRule rule = getMinimumPaymentRule(loan);
+ if (rule == null) {
+ return;
+ }
+
+ Optional latestPeriodOpt = repository.findTopByLoanIdOrderByPeriodNumberDesc(loan.getId());
+ if (latestPeriodOpt.isEmpty()) {
+ return;
+ }
+
+ WcLoanDelinquencyRangeSchedule latestPeriod = latestPeriodOpt.get();
+ while (latestPeriod.getToDate().isBefore(businessDate)) {
+ LocalDate newFromDate = latestPeriod.getToDate().plusDays(1);
+ LocalDate newToDate = calculateToDate(newFromDate, rule.getFrequency(), rule.getFrequencyType());
+ BigDecimal expectedAmount = calculateExpectedAmount(loan, rule);
+
+ WcLoanDelinquencyRangeSchedule nextPeriod = new WcLoanDelinquencyRangeSchedule();
+ nextPeriod.setLoan(loan);
+ nextPeriod.setPeriodNumber(latestPeriod.getPeriodNumber() + 1);
+ nextPeriod.setFromDate(newFromDate);
+ nextPeriod.setToDate(newToDate);
+ nextPeriod.setExpectedAmount(expectedAmount);
+ nextPeriod.setPaidAmount(BigDecimal.ZERO);
+ nextPeriod.setOutstandingAmount(expectedAmount);
+ nextPeriod.setMinPaymentCriteriaMet(null);
+
+ latestPeriod = repository.saveAndFlush(nextPeriod);
+ log.debug("Generated next delinquency range schedule period {} for WC loan {}", nextPeriod.getPeriodNumber(), loan.getId());
+ }
+ }
+
+ @Override
+ public void applyRepayment(Long loanId, LocalDate transactionDate, BigDecimal amount) {
+ Optional periodOpt = repository
+ .findByLoanIdAndFromDateLessThanEqualAndToDateGreaterThanEqual(loanId, transactionDate, transactionDate);
+ if (periodOpt.isPresent()) {
+ WcLoanDelinquencyRangeSchedule period = periodOpt.get();
+ BigDecimal newPaidAmount = period.getPaidAmount().add(amount);
+ period.setPaidAmount(newPaidAmount);
+ period.setOutstandingAmount(period.getExpectedAmount().subtract(newPaidAmount).max(BigDecimal.ZERO));
+ repository.saveAndFlush(period);
+ log.debug("Applied repayment of {} to delinquency range schedule period {} for WC loan {}", amount, period.getPeriodNumber(),
+ loanId);
+ }
+ }
+
+ @Override
+ public void evaluateExpiredPeriods(WorkingCapitalLoan loan, LocalDate businessDate) {
+ List unevaluatedPeriods = repository
+ .findByLoanIdAndToDateLessThanAndMinPaymentCriteriaMetIsNull(loan.getId(), businessDate);
+ for (WcLoanDelinquencyRangeSchedule period : unevaluatedPeriods) {
+ boolean criteriaMet = period.getPaidAmount().compareTo(period.getExpectedAmount()) >= 0;
+ period.setMinPaymentCriteriaMet(criteriaMet);
+ repository.saveAndFlush(period);
+ log.debug("Evaluated delinquency range schedule period {} for WC loan {}: criteriaMet={}", period.getPeriodNumber(),
+ loan.getId(), criteriaMet);
+ }
+ }
+
+ @Override
+ public List retrieveRangeSchedule(Long loanId) {
+ List periods = repository.findByLoanIdOrderByPeriodNumberAsc(loanId);
+ return mapper.toDataList(periods);
+ }
+
+ private DelinquencyMinimumPaymentPeriodAndRule getMinimumPaymentRule(WorkingCapitalLoan loan) {
+ WorkingCapitalLoanProduct product = loan.getProduct();
+ if (product == null) {
+ return null;
+ }
+ DelinquencyBucket bucket = product.getDelinquencyBucket();
+ if (bucket == null) {
+ return null;
+ }
+ return bucket.getMinimumPaymentPeriodAndRule();
+ }
+
+ private LocalDate calculateToDate(LocalDate fromDate, Long frequency, DelinquencyFrequencyType frequencyType) {
+ return switch (frequencyType) {
+ case DAYS -> fromDate.plusDays(frequency - 1);
+ case WEEKS -> fromDate.plusWeeks(frequency).minusDays(1);
+ case MONTHS -> fromDate.plusMonths(frequency).minusDays(1);
+ case YEARS -> fromDate.plusYears(frequency).minusDays(1);
+ };
+ }
+
+ private BigDecimal calculateExpectedAmount(WorkingCapitalLoan loan, DelinquencyMinimumPaymentPeriodAndRule rule) {
+ BigDecimal principal = loan.getApprovedPrincipal();
+ if (principal == null) {
+ return BigDecimal.ZERO;
+ }
+ BigDecimal minimumPayment = rule.getMinimumPayment();
+ if (minimumPayment == null) {
+ return BigDecimal.ZERO;
+ }
+ return principal.multiply(minimumPayment).divide(BigDecimal.valueOf(100), MathContext.DECIMAL128);
+ }
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoan.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoan.java
index 8544302098c..91e4808ffb0 100644
--- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoan.java
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoan.java
@@ -21,9 +21,13 @@
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.persistence.Version;
+import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.AccessLevel;
import lombok.Getter;
@@ -63,4 +67,13 @@ public class WorkingCapitalLoan extends AbstractAuditableWithUTCDateTimeCustom
+
diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0005_delinquency_range_schedule.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0005_delinquency_range_schedule.xml
new file mode 100644
index 00000000000..8730a083762
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0005_delinquency_range_schedule.xml
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(*) FROM m_permission WHERE code = 'READ_WORKINGCAPITALLOAN'
+
+
+
+
+
+
+
+
+
+
+
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WcLoanDelinquencyRangeScheduleIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WcLoanDelinquencyRangeScheduleIntegrationTest.java
new file mode 100644
index 00000000000..416292568ce
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WcLoanDelinquencyRangeScheduleIntegrationTest.java
@@ -0,0 +1,145 @@
+/**
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.DeleteDelinquencyBucketResponse;
+import org.apache.fineract.client.models.PostDelinquencyBucketResponse;
+import org.apache.fineract.client.models.PostDelinquencyRangeResponse;
+import org.apache.fineract.client.models.PutDelinquencyBucketResponse;
+import org.apache.fineract.client.util.JSON;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
+import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
+import org.apache.fineract.integrationtests.common.products.DelinquencyRangesHelper;
+import org.apache.fineract.integrationtests.common.workingcapitalloan.WcLoanDelinquencyRangeScheduleHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@Slf4j
+@ExtendWith(LoanTestLifecycleExtension.class)
+@Order(1)
+public class WcLoanDelinquencyRangeScheduleIntegrationTest {
+
+ private static final Gson GSON = new JSON().getGson();
+
+ private ResponseSpecification responseSpec;
+ private RequestSpecification requestSpec;
+
+ @BeforeEach
+ public void setup() {
+ Utils.initializeRESTAssured();
+
+ requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+ requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+ responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+ }
+
+ @Test
+ public void testDelinquencyBucketWithMinPaymentValue() {
+ // given - Create delinquency ranges
+ final String jsonRange1 = DelinquencyRangesHelper.getAsJSON(1, 30);
+ final PostDelinquencyRangeResponse rangeResponse1 = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
+ jsonRange1);
+ assertNotNull(rangeResponse1);
+
+ final String jsonRange2 = DelinquencyRangesHelper.getAsJSON(31, 60);
+ final PostDelinquencyRangeResponse rangeResponse2 = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
+ jsonRange2);
+ assertNotNull(rangeResponse2);
+
+ final List rangeIds = new ArrayList<>();
+ rangeIds.add(Math.toIntExact(rangeResponse1.getResourceId()));
+ rangeIds.add(Math.toIntExact(rangeResponse2.getResourceId()));
+
+ // when - Create a WC delinquency bucket with minimumPayment percentage
+ final BigDecimal initialMinPayment = new BigDecimal("3");
+ final PostDelinquencyBucketResponse createResponse = WcLoanDelinquencyRangeScheduleHelper.createWcDelinquencyBucket(requestSpec,
+ responseSpec, rangeIds, 30, 0, initialMinPayment, 1);
+
+ // then - Verify creation was successful
+ assertNotNull(createResponse);
+ assertNotNull(createResponse.getResourceId());
+ log.info("Created WC delinquency bucket with id: {}", createResponse.getResourceId());
+
+ // Retrieve the bucket and verify minimumPaymentValue is persisted
+ final String bucketJson = Utils.performServerGet(requestSpec, responseSpec,
+ "/fineract-provider/api/v1/delinquency/buckets/" + createResponse.getResourceId() + "?" + Utils.TENANT_IDENTIFIER);
+ assertNotNull(bucketJson);
+ log.info("Retrieved bucket JSON: {}", bucketJson);
+
+ final Type mapType = new TypeToken