This is an automated email from the ASF dual-hosted git repository. adamsaghy pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit 7a157fae46a4621673bb92c6f5b82db836596802 Author: Soma Sörös <[email protected]> AuthorDate: Wed Mar 11 15:49:16 2026 +0100 FINERACT-2455: Working Capital - Delinquency Management - Configuration --- .../module/fineract-accounting/persistence.xml | 1 + .../module/fineract-branch/persistence.xml | 1 + .../module/fineract-charge/persistence.xml | 1 + .../module/fineract-cob/persistence.xml | 1 + .../delinquency/domain/DelinquencyBucket.java | 10 ++ .../delinquency/domain/DelinquencyBucketType.java | 37 +++-- .../domain/DelinquencyBucketTypeConverter.java | 24 ++-- .../domain/DelinquencyFrequencyType.java | 51 +++++++ .../domain/DelinquencyFrequencyTypeConverter.java | 23 ++-- .../domain/DelinquencyMinimumPayment.java | 50 +++++++ .../domain/DelinquencyMinimumPaymentConverter.java | 24 ++-- ...=> DelinquencyMinimumPaymentPeriodAndRule.java} | 39 +++--- .../module/fineract-core/persistence.xml | 1 + .../test/stepdef/loan/WorkingCapitalStepDef.java | 64 +++++++++ .../WorkingCapitalDelinquencyConfiguration.feature | 12 ++ .../module/fineract-investor/persistence.xml | 1 + .../delinquency/api/DelinquencyApiConstants.java | 6 + .../delinquency/api/DelinquencyApiResource.java | 42 +++++- .../delinquency/api/DelinquencyBucketRequest.java | 17 +++ .../delinquency/data/DelinquencyBucketData.java | 14 ++ ...elinquencyMinimumPaymentPeriodAndRuleData.java} | 15 +- ...encyMinimumPaymentPeriodAndRuleRepository.java} | 21 +-- .../mapper/DelinquencyBucketMapper.java | 12 +- ...inquencyMinimumPaymentPeriodAndRuleMapper.java} | 26 +++- .../DelinquencyReadPlatformServiceImpl.java | 4 + .../DelinquencyWritePlatformServiceImpl.java | 77 ++++++++++- .../starter/DelinquencyConfiguration.java | 6 +- .../DelinquencyBucketParseAndValidator.java | 60 +++++++- .../module/fineract-loan/persistence.xml | 1 + .../fineract-progressive-loan/persistence.xml | 1 + .../LoanProductReadPlatformServiceImpl.java | 5 +- .../db/changelog/tenant/changelog-tenant.xml | 1 + .../0222_delinquency_for_working_capital_loans.xml | 151 +++++++++++++++++++++ .../module/fineract-provider/persistence.xml | 1 + ...cyWritePlatformServiceRangeChangeEventTest.java | 5 +- .../module/fineract-rates/persistence.xml | 1 + .../module/fineract-report/persistence.xml | 1 + .../module/fineract-savings/persistence.xml | 1 + .../module/fineract-tax/persistence.xml | 1 + 39 files changed, 706 insertions(+), 103 deletions(-) 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 d1e6be5a80..23ade62e2f 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class> 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 ee2b5fcfef..18fae21bef 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class> 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 7482e322cd..94c1a2f8de 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class> 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 946e34b707..d7fdec2037 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class> 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 6bda195237..ec44dfeabf 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<Lo @JoinTable(name = "m_delinquency_bucket_mappings", joinColumns = @JoinColumn(name = "delinquency_bucket_id"), inverseJoinColumns = @JoinColumn(name = "delinquency_range_id")) private List<DelinquencyRange> 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-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyBucketRequest.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketType.java similarity index 55% copy from fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyBucketRequest.java copy to fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketType.java index ed50e91709..443537b2c5 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyBucketRequest.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketType.java @@ -16,23 +16,32 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.portfolio.delinquency.api; -import java.io.Serial; -import java.io.Serializable; -import java.util.List; +package org.apache.fineract.portfolio.delinquency.domain; + +import java.util.Arrays; import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -@Setter -@Getter -@NoArgsConstructor -public class DelinquencyBucketRequest implements Serializable { +public enum DelinquencyBucketType { + + REGULAR(1L, "bucketType.regular"), // + WORKING_CAPITAL(2L, "bucketType.workingCapital"); + + @Getter + private final Long value; + + @Getter + private final String code; - @Serial - private static final long serialVersionUID = 1L; + DelinquencyBucketType(Long value, String code) { + this.value = value; + this.code = code; + } - private String name; - private List<Long> ranges; + 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-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketTypeConverter.java similarity index 55% copy from fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java copy to fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketTypeConverter.java index d4bd7d4108..650955fd4d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketTypeConverter.java @@ -16,19 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.portfolio.delinquency.api; -public final class DelinquencyApiConstants { +package org.apache.fineract.portfolio.delinquency.domain; - private DelinquencyApiConstants() { +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import java.util.Optional; - } +@Converter(autoApply = true) +public class DelinquencyBucketTypeConverter implements AttributeConverter<DelinquencyBucketType, Long> { - public static final String NAME_PARAM_NAME = "name"; - public static final String CLASSIFICATION_PARAM_NAME = "classification"; - public static final String RANGES_PARAM_NAME = "ranges"; - public static final String MINIMUMAGEDAYS_PARAM_NAME = "minimumAgeDays"; - public static final String MAXIMUMAGEDAYS_PARAM_NAME = "maximumAgeDays"; - public static final String LOCALE_PARAM_NAME = "locale"; + @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 0000000000..55764d0337 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyFrequencyType.java @@ -0,0 +1,51 @@ +/** + * 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; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; + +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 EnumOptionData toData() { + return new EnumOptionData(getValue().longValue(), getCode(), getCode()); + } + + public static DelinquencyFrequencyType fromInt(final Integer v) { + return Arrays.stream(values()).filter(e -> e.getValue().equals(v)).findAny().orElse(null); + } + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyFrequencyTypeConverter.java similarity index 53% copy from fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java copy to fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyFrequencyTypeConverter.java index d4bd7d4108..960daf2844 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyFrequencyTypeConverter.java @@ -16,19 +16,24 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.portfolio.delinquency.api; -public final class DelinquencyApiConstants { +package org.apache.fineract.portfolio.delinquency.domain; - private DelinquencyApiConstants() { +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import java.util.Optional; +@Converter(autoApply = true) +public class DelinquencyFrequencyTypeConverter implements AttributeConverter<DelinquencyFrequencyType, Integer> { + + @Override + public Integer convertToDatabaseColumn(DelinquencyFrequencyType delinquencyFrequencyType) { + return Optional.ofNullable(delinquencyFrequencyType).map(DelinquencyFrequencyType::getValue).orElse(null); } - public static final String NAME_PARAM_NAME = "name"; - public static final String CLASSIFICATION_PARAM_NAME = "classification"; - public static final String RANGES_PARAM_NAME = "ranges"; - public static final String MINIMUMAGEDAYS_PARAM_NAME = "minimumAgeDays"; - public static final String MAXIMUMAGEDAYS_PARAM_NAME = "maximumAgeDays"; - public static final String LOCALE_PARAM_NAME = "locale"; + @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 0000000000..8f0ba62b57 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPayment.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; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; + +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); + } + + public EnumOptionData toData() { + return new EnumOptionData(getValue(), getCode(), getCode()); + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentConverter.java similarity index 54% copy from fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java copy to fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentConverter.java index d4bd7d4108..2aa3ee8937 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyApiConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentConverter.java @@ -16,19 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.portfolio.delinquency.api; -public final class DelinquencyApiConstants { +package org.apache.fineract.portfolio.delinquency.domain; - private DelinquencyApiConstants() { +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import java.util.Optional; - } +@Converter(autoApply = true) +public class DelinquencyMinimumPaymentConverter implements AttributeConverter<DelinquencyMinimumPayment, Long> { - public static final String NAME_PARAM_NAME = "name"; - public static final String CLASSIFICATION_PARAM_NAME = "classification"; - public static final String RANGES_PARAM_NAME = "ranges"; - public static final String MINIMUMAGEDAYS_PARAM_NAME = "minimumAgeDays"; - public static final String MAXIMUMAGEDAYS_PARAM_NAME = "maximumAgeDays"; - public static final String LOCALE_PARAM_NAME = "locale"; + @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/DelinquencyBucket.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentPeriodAndRule.java similarity index 57% copy from fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucket.java copy to fineract-core/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentPeriodAndRule.java index 6bda195237..ca1ee989cd 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/DelinquencyMinimumPaymentPeriodAndRule.java @@ -16,18 +16,18 @@ * 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.JoinTable; -import jakarta.persistence.ManyToMany; +import jakarta.persistence.OneToOne; import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import jakarta.persistence.Version; -import java.util.List; +import java.io.Serial; +import java.math.BigDecimal; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -37,22 +37,25 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDa @Setter @NoArgsConstructor @Entity -@Table(name = "m_delinquency_bucket", uniqueConstraints = { - @UniqueConstraint(name = "uq_delinquency_bucket_name", columnNames = { "name" }) }) -public class DelinquencyBucket extends AbstractAuditableWithUTCDateTimeCustom<Long> { +@Table(name = "m_delinquency_payment_rule") +public class DelinquencyMinimumPaymentPeriodAndRule extends AbstractAuditableWithUTCDateTimeCustom<Long> { + + @Serial + private static final long serialVersionUID = -9204385885041120403L; - @Column(name = "name", nullable = false) - private String name; + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "bucket_id", nullable = false, unique = true) + private DelinquencyBucket bucket; - @ManyToMany(fetch = FetchType.LAZY) - @JoinTable(name = "m_delinquency_bucket_mappings", joinColumns = @JoinColumn(name = "delinquency_bucket_id"), inverseJoinColumns = @JoinColumn(name = "delinquency_range_id")) - private List<DelinquencyRange> ranges; + @Column(name = "frequency", nullable = false) + private Long frequency; - @Version - private Long version; + @Column(name = "frequency_type", nullable = false) + private DelinquencyFrequencyType frequencyType; - public DelinquencyBucket(String name) { - this.name = name; - } + @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 4883cc15e8..dda569b6f1 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class> 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 f74b8d8163..759c11404d 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 static org.assertj.core.api.Assertions.assertThat; 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.FineractFeignClient; 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; @@ -800,4 +806,62 @@ public class WorkingCapitalStepDef extends AbstractStepDef { 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 0000000000..c619212217 --- /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 3ecc879408..fd2d3078ac 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class> 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 d4bd7d4108..b6d9b34228 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 @@ public final class 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 811c6b539f..71b24adaa0 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.PUT; 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<DelinquencyRangeData> 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 class DelinquencyApiResource { 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 class DelinquencyApiResource { @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 ed50e91709..67fcf117fa 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 @@ package org.apache.fineract.portfolio.delinquency.api; 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<Long> 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 b078343cf4..1f14c52908 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<DelinquencyRangeData> ranges; + private Long bucketType; + private DelinquencyMinimumPaymentPeriodAndRuleData minimumPaymentPeriodAndRule; + + public static DelinquencyBucketData getDataInstance(Long id, String name, List<DelinquencyRangeData> ranges, Long bucketType, + DelinquencyMinimumPaymentPeriodAndRuleData minimumPaymentPeriodAndRule) { + return new DelinquencyBucketData(id, name, ranges, bucketType, minimumPaymentPeriodAndRule, null, null, null, null); + } + + private List<DelinquencyRangeData> rangesOptions = new ArrayList<>(); + private List<CodeValueData> bucketTypeOptions = new ArrayList<>(); + private List<CodeValueData> frequencyTypeOptions = new ArrayList<>(); + private List<CodeValueData> minimumPaymentOptions = new ArrayList<>(); } 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/DelinquencyMinimumPaymentPeriodAndRuleData.java similarity index 76% copy from fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/data/DelinquencyBucketData.java copy to fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/data/DelinquencyMinimumPaymentPeriodAndRuleData.java index b078343cf4..4db8c187b3 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/DelinquencyMinimumPaymentPeriodAndRuleData.java @@ -16,23 +16,24 @@ * specific language governing permissions and limitations * under the License. */ + package org.apache.fineract.portfolio.delinquency.data; -import java.io.Serializable; -import java.util.List; +import java.math.BigDecimal; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; @ToString @AllArgsConstructor @Getter @Setter -public class DelinquencyBucketData implements Serializable { - - private Long id; - private String name; - private List<DelinquencyRangeData> ranges; +public class DelinquencyMinimumPaymentPeriodAndRuleData { + private Long frequency; + private EnumOptionData frequencyType; + private BigDecimal minimumPayment; + private EnumOptionData minimumPaymentType; } 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/domain/DelinquencyMinimumPaymentPeriodAndRuleRepository.java similarity index 60% copy from fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/api/DelinquencyBucketRequest.java copy to fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyMinimumPaymentPeriodAndRuleRepository.java index ed50e91709..a252403277 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/domain/DelinquencyMinimumPaymentPeriodAndRuleRepository.java @@ -16,23 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.portfolio.delinquency.api; -import java.io.Serial; -import java.io.Serializable; -import java.util.List; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +package org.apache.fineract.portfolio.delinquency.domain; -@Setter -@Getter -@NoArgsConstructor -public class DelinquencyBucketRequest implements Serializable { +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; - @Serial - private static final long serialVersionUID = 1L; +public interface DelinquencyMinimumPaymentPeriodAndRuleRepository extends JpaRepository<DelinquencyMinimumPaymentPeriodAndRule, Long>, + JpaSpecificationExecutor<DelinquencyMinimumPaymentPeriodAndRule>, CrudRepository<DelinquencyMinimumPaymentPeriodAndRule, Long> { - private String name; - private List<Long> ranges; } 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 4b207c91aa..454e6bd4d2 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 java.util.List; 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<DelinquencyBucketData> map(List<DelinquencyBucket> sources); + default Long map(DelinquencyBucketType value) { + return value.getValue(); + } + } 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/DelinquencyMinimumPaymentPeriodAndRuleMapper.java similarity index 50% copy from fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/mapper/DelinquencyBucketMapper.java copy to fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/mapper/DelinquencyMinimumPaymentPeriodAndRuleMapper.java index 4b207c91aa..0f22ccaf91 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/DelinquencyMinimumPaymentPeriodAndRuleMapper.java @@ -16,19 +16,33 @@ * specific language governing permissions and limitations * under the License. */ + package org.apache.fineract.portfolio.delinquency.mapper; -import java.util.List; 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.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.portfolio.delinquency.data.DelinquencyMinimumPaymentPeriodAndRuleData; +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.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; @Mapper(config = MapstructMapperConfig.class) -public interface DelinquencyBucketMapper { +public interface DelinquencyMinimumPaymentPeriodAndRuleMapper { - DelinquencyBucketData map(DelinquencyBucket source); + @Mapping(target = "frequencyType", source = "frequencyType", qualifiedByName = "frequencyTypeData") + @Mapping(target = "minimumPaymentType", source = "minimumPaymentType", qualifiedByName = "minimumPaymentTypeData") + DelinquencyMinimumPaymentPeriodAndRuleData map(DelinquencyMinimumPaymentPeriodAndRule source); - List<DelinquencyBucketData> map(List<DelinquencyBucket> sources); + @Named("frequencyTypeData") + default EnumOptionData frequencyTypeData(final DelinquencyFrequencyType frequencyType) { + return frequencyType.toData(); + } + @Named("minimumPaymentTypeData") + default EnumOptionData minimumPaymentTypeData(final DelinquencyMinimumPayment minimumPaymentType) { + return minimumPaymentType.toData(); + } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java index 9f29937e70..f14b9deeca 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java @@ -46,6 +46,7 @@ import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyActionRep import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistory; import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository; import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository; +import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException; import org.apache.fineract.portfolio.delinquency.helper.DelinquencyEffectivePauseHelper; import org.apache.fineract.portfolio.delinquency.helper.InstallmentDelinquencyAggregator; import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyBucketMapper; @@ -106,6 +107,9 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo @Override public DelinquencyBucketData retrieveDelinquencyBucket(Long delinquencyBucketId) { + if (!repositoryBucket.existsById(delinquencyBucketId)) { + throw DelinquencyBucketNotFoundException.notFound(delinquencyBucketId); + } final DelinquencyBucket delinquencyBucket = repositoryBucket.getReferenceById(delinquencyBucketId); final DelinquencyBucketData delinquencyBucketData = mapperBucket.map(delinquencyBucket); delinquencyBucketData.setRanges(mapperRange.map(delinquencyBucket.getRanges())); 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 392e2ce814..91c15e332a 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.DelinquencyBucket; 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; @@ -50,6 +55,7 @@ import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistor import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository; import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository; import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketAgesOverlapedException; +import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException; import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeInvalidAgesException; import org.apache.fineract.portfolio.delinquency.helper.DelinquencyEffectivePauseHelper; import org.apache.fineract.portfolio.delinquency.validator.DelinquencyActionParseAndValidator; @@ -84,6 +90,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) { @@ -141,14 +148,17 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat @Override public CommandProcessingResult deleteDelinquencyBucket(Long delinquencyBucketId, JsonCommand command) { - final DelinquencyBucket delinquencyBucket = repositoryBucket.getReferenceById(delinquencyBucketId); - if (delinquencyBucket != null) { + if (repositoryBucket.existsById(delinquencyBucketId)) { + final DelinquencyBucket delinquencyBucket = repositoryBucket.getReferenceById(delinquencyBucketId); Long delinquencyBucketLinked = this.loanProductRepository.countByDelinquencyBucket(delinquencyBucket); if (delinquencyBucketLinked > 0) { throw new PlatformDataIntegrityException("error.msg.data.integrity.issue.entity.linked", "Data integrity issue with resource: " + delinquencyBucket.getId()); } repositoryBucket.delete(delinquencyBucket); + } else { + throw new DelinquencyBucketNotFoundException("error.msg.delinquency.bucket.id.not.exist", + "Delinquency bucket with id `" + delinquencyBucketId + "` does not exist.", delinquencyBucketId); } return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucketId).build(); } @@ -338,7 +348,22 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat 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().getId())); + newDelinquencyBucket.getMinimumPaymentPeriodAndRule().setFrequencyType( + DelinquencyFrequencyType.fromInt(data.getMinimumPaymentPeriodAndRule().getFrequencyType().getId().intValue())); + newDelinquencyBucket.getMinimumPaymentPeriodAndRule() + .setMinimumPayment(data.getMinimumPaymentPeriodAndRule().getMinimumPayment()); + } repositoryBucket.save(newDelinquencyBucket); + setDelinquencyBucketMappings(newDelinquencyBucket, data); return newDelinquencyBucket; } else { @@ -353,6 +378,52 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat 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().getId() + .equals(delinquencyBucket.getMinimumPaymentPeriodAndRule().getFrequencyType().getValue().longValue())) { + delinquencyBucket.getMinimumPaymentPeriodAndRule().setFrequencyType( + DelinquencyFrequencyType.fromInt(data.getMinimumPaymentPeriodAndRule().getFrequencyType().getId().intValue())); + changes.put(DelinquencyApiConstants.FREQUENCY_TYPE_PARAM_NAME, + delinquencyBucket.getMinimumPaymentPeriodAndRule().getFrequencyType().getValue()); + } + if (!data.getMinimumPaymentPeriodAndRule().getMinimumPaymentType().getId() + .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().getId())); + } + 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 +434,7 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat private void setDelinquencyBucketMappings(DelinquencyBucket delinquencyBucket, DelinquencyBucketData data) { List<Long> rangeIds = new ArrayList<>(); data.getRanges().forEach(dataRange -> rangeIds.add(dataRange.getId())); - + delinquencyBucket.setBucketType(DelinquencyBucketType.fromLong(data.getBucketType())); List<DelinquencyRange> ranges = repositoryRange.findAllById(rangeIds); validateDelinquencyRanges(ranges); List<DelinquencyBucketMappings> 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 410f05e195..022e3c4e47 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.configuration.domain.ConfigurationDoma 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 class DelinquencyConfiguration { 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 93888f2962..2422e802ac 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 @@ package org.apache.fineract.portfolio.delinquency.validator; 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,11 @@ import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; 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.apache.fineract.portfolio.delinquency.domain.DelinquencyFrequencyType; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPayment; import org.springframework.stereotype.Component; @RequiredArgsConstructor @@ -55,22 +61,70 @@ public class DelinquencyBucketParseAndValidator extends ParseAndValidator { } 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); dataValidator.reset().parameter(DelinquencyApiConstants.NAME_PARAM_NAME).value(name).notBlank(); + 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; + + ArrayList<DelinquencyRangeData> ranges = new ArrayList<>(); final String[] rangeIds = jsonHelper.extractArrayNamed(DelinquencyApiConstants.RANGES_PARAM_NAME, element); dataValidator.reset().parameter(DelinquencyApiConstants.RANGES_PARAM_NAME).value(rangeIds).notNull().arrayNotEmpty(); - ArrayList<DelinquencyRangeData> ranges = new ArrayList<>(); if (rangeIds != null) { for (String rangeId : rangeIds) { ranges.add(DelinquencyRangeData.reference(Long.parseLong(rangeId))); } } - return dataValidator.hasError() ? null : new DelinquencyBucketData(null, name, ranges); + + 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); + final DelinquencyFrequencyType delinquencyFrequencyType = DelinquencyFrequencyType.fromInt(frequencyType); + + 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); + final DelinquencyMinimumPayment delinquencyMinimumPayment = DelinquencyMinimumPayment.fromLong(minimumPaymentType); + + return dataValidator.hasError() ? null + : new DelinquencyMinimumPaymentPeriodAndRuleData(frequency, delinquencyFrequencyType.toData(), minimumPayment, + delinquencyMinimumPayment.toData()); + } + 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 8eb34963c2..32f739f46d 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class> 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 27208079e4..08ef888d56 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class> 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 895221d578..b9abd911dc 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.charge.service.ChargeReadPlatformService; 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 class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo // 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 5597a2e341..9217a2d445 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 @@ -240,4 +240,5 @@ <include file="parts/0219_remove_self_service_feature.xml" relativeToChangelogFile="true"/> <include file="parts/0220_add_login_retry_configuration.xml" relativeToChangelogFile="true" /> <include file="parts/0221_transaction_summary_reports_fix_after_originator_details.xml" relativeToChangelogFile="true" /> + <include file="parts/0222_delinquency_for_working_capital_loans.xml" relativeToChangelogFile="true" /> </databaseChangeLog> diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0222_delinquency_for_working_capital_loans.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0222_delinquency_for_working_capital_loans.xml new file mode 100644 index 0000000000..2c38991bd2 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0222_delinquency_for_working_capital_loans.xml @@ -0,0 +1,151 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--> + +<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog + https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd"> + + <changeSet id="create-m-delinquency-payment-rule-table2" author="fineract"> + + <preConditions onFail="MARK_RAN"> + <not> + <tableExists tableName="m_delinquency_payment_rule"/> + </not> + </preConditions> + + <createTable tableName="m_delinquency_payment_rule"> + + <column autoIncrement="true" name="id" type="BIGINT"> + <constraints primaryKey="true" + primaryKeyName="pk_m_delinquency_payment_rule" + nullable="false"/> + </column> + + <column name="created_by" type="BIGINT"> + <constraints nullable="false"/> + </column> + + <column name="last_modified_by" type="BIGINT"> + <constraints nullable="false"/> + </column> + + + <column name="bucket_id" type="BIGINT"> + <constraints nullable="false"/> + </column> + + <column name="frequency" type="BIGINT"> + <constraints nullable="false"/> + </column> + + <column name="frequency_type" type="BIGINT"> + <constraints nullable="false"/> + </column> + + <column name="minimum_payment" type="DECIMAL(19, 6)"> + <constraints nullable="false"/> + </column> + + <column name="minimum_payment_type" type="BIGINT"> + <constraints nullable="false"/> + </column> + + </createTable> + + </changeSet> + <changeSet author="fineract" id="create-m-delinquency-payment-rule-table-3" context="mysql"> + <preConditions onFail="MARK_RAN"> + <tableExists tableName="m_delinquency_payment_rule"/> + <not> + <columnExists tableName="m_delinquency_payment_rule" columnName="created_on_utc"/> + </not> + </preConditions> + <addColumn tableName="m_delinquency_payment_rule"> + <column name="created_on_utc" type="DATETIME"/> + <column name="last_modified_on_utc" type="DATETIME"/> + </addColumn> + </changeSet> + <changeSet author="fineract" id="create-m-delinquency-payment-rule-table-4" context="postgresql"> + <preConditions onFail="MARK_RAN"> + <tableExists tableName="m_delinquency_payment_rule"/> + <not> + <columnExists tableName="m_delinquency_payment_rule" columnName="created_on_utc"/> + </not> + </preConditions> + <addColumn tableName="m_delinquency_payment_rule"> + <column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE"/> + <column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE"/> + </addColumn> + </changeSet> + <changeSet id="add-unique-m-delinquency-payment-rule-bucket1" author="fineract"> + + <preConditions onFail="MARK_RAN"> + <not> + <uniqueConstraintExists + tableName="m_delinquency_payment_rule" + constraintName="uc_m_delinquency_payment_rule_bucket"/> + </not> + </preConditions> + + <addUniqueConstraint + tableName="m_delinquency_payment_rule" + columnNames="bucket_id" + constraintName="uc_m_delinquency_payment_rule_bucket"/> + + </changeSet> + <changeSet id="add-fk-m-delinquency-payment-rule-bucket1" author="dev"> + + <preConditions onFail="MARK_RAN"> + <not> + <foreignKeyConstraintExists + foreignKeyName="FK_M_DELINQUENCY_PAYMENT_RULE_ON_BUCKET"/> + </not> + </preConditions> + + <addForeignKeyConstraint + baseTableName="m_delinquency_payment_rule" + baseColumnNames="bucket_id" + referencedTableName="m_delinquency_bucket" + referencedColumnNames="id" + constraintName="FK_M_DELINQUENCY_PAYMENT_RULE_ON_BUCKET"/> + + </changeSet> + <changeSet id="add-bucket-type-to-m-delinquency-bucket1" author="fineract"> + + <preConditions onFail="MARK_RAN"> + <and> + <tableExists tableName="m_delinquency_bucket"/> + <not> + <columnExists tableName="m_delinquency_bucket" columnName="bucket_type"/> + </not> + </and> + </preConditions> + + <addColumn tableName="m_delinquency_bucket"> + <column name="bucket_type" type="BIGINT" defaultValueNumeric="1"> + <constraints nullable="false"/> + </column> + </addColumn> + + </changeSet> +</databaseChangeLog> 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 19f793c2b9..768165e298 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class> 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 d692d1d967..e74b9e70fd 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.DelinquencyAction; 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 class DelinquencyWritePlatformServiceRangeChangeEventTest { 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 61addd405f..cd0da3f242 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class> 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 4883cc15e8..dda569b6f1 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class> 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 2c9e1ba7ba..4997a4dc59 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class> 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 4a482aecac..0c5eb540a9 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 @@ <class>org.apache.fineract.portfolio.calendar.domain.Calendar</class> <class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class> <class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class> + <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyMinimumPaymentPeriodAndRule</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class> <class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class> <class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class>
