You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by GitBox <gi...@apache.org> on 2022/07/26 10:49:20 UTC
[GitHub] [fineract] galovics commented on a diff in pull request #2442: Delinquency buckets management for Loans
galovics commented on code in PR #2442:
URL: https://github.com/apache/fineract/pull/2442#discussion_r929795946
##########
fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java:
##########
@@ -49,7 +49,8 @@ public enum JobName {
"Update Trial Balance Details"), EXECUTE_DIRTY_JOBS(
"Execute All Dirty Jobs"), INCREASE_BUSINESS_DATE_BY_1_DAY(
"Increase Business Date by 1 day"), INCREASE_COB_DATE_BY_1_DAY(
- "Increase COB Date by 1 day");
+ "Increase COB Date by 1 day"), LOAN_DELINQUENCY_CLASSIFICATION(
+ "Loan Delinquency Classification");
Review Comment:
Let's not add a new job. This PR should be only about the ability to configure delinquency buckets. Job will follow later.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketMappings.java:
##########
@@ -0,0 +1,60 @@
+/**
+ * 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 javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.persistence.Version;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_delinquency_bucket_mappings", uniqueConstraints = {
+ @UniqueConstraint(name = "uq_delinquency_bucket_mapping", columnNames = { "delinquencyBucket", "delinquencyRange" }) })
+public class DelinquencyBucketMappings extends AbstractAuditableCustom {
Review Comment:
Why do we need this mapping table in between the Bucket and the Ranges? Couldn't it be simplified to a Bucket -> OneToMany Range relationship?
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucket.java:
##########
@@ -0,0 +1,53 @@
+/**
+ * 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 javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.persistence.Version;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_delinquency_bucket", uniqueConstraints = {
+ @UniqueConstraint(name = "uq_delinquency_bucket_name", columnNames = { "name" }) })
+public class DelinquencyBucket extends AbstractAuditableCustom {
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
Review Comment:
Why doesn't the DelinquencyBucket entity has a relationship to all the related DelinquencyRange entities? It'd be definitely useful to have that.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucket.java:
##########
@@ -0,0 +1,53 @@
+/**
+ * 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 javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.persistence.Version;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_delinquency_bucket", uniqueConstraints = {
+ @UniqueConstraint(name = "uq_delinquency_bucket_name", columnNames = { "name" }) })
+public class DelinquencyBucket extends AbstractAuditableCustom {
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Version
+ private Long version;
+
+ protected DelinquencyBucket(String name) {
+ this.name = name;
+ }
+
+ public static DelinquencyBucket instance(String name) {
Review Comment:
Not sure why the static factory here as well.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanDelinquencyTags.java:
##########
@@ -0,0 +1,59 @@
+/**
+ * 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 javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Version;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_loan_delinquency_tags")
+public class LoanDelinquencyTags extends AbstractAuditableCustom {
+
+ @OneToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "delinquency_range_id", nullable = false)
+ private DelinquencyRange delinquencyRange;
+
+ @OneToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "loan_id", nullable = false)
+ private Loan loan;
+
+ @Version
+ private Long version;
+
+ public LoanDelinquencyTags(DelinquencyRange delinquencyRange, Loan loan) {
+ this.delinquencyRange = delinquencyRange;
+ this.loan = loan;
+ }
+
+ public static LoanDelinquencyTags instance(DelinquencyRange delinquencyRange, Loan loan) {
Review Comment:
Not sure about the factory here either.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyRange.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 javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.persistence.Version;
+import javax.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_delinquency_range", uniqueConstraints = {
+ @UniqueConstraint(name = "uq_delinquency_range_classification", columnNames = { "classification" }) })
+public class DelinquencyRange extends AbstractAuditableCustom {
+
+ @Column(name = "classification", nullable = false)
+ private String classification;
+
+ @Column(name = "min_age", nullable = false)
Review Comment:
Let's call out the unit as well. `min_age_days` or something like that.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java:
##########
@@ -0,0 +1,195 @@
+/**
+ * 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.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
+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.DelinquencyRange;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
+import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException;
+import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeNotFoundException;
+import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator;
+import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlatformService {
+
+ private final DelinquencyBucketParseAndValidator dataValidatorBucket;
+ private final DelinquencyRangeParseAndValidator dataValidatorRange;
+
+ private final DelinquencyRangeRepository repositoryRange;
+ private final DelinquencyBucketRepository repositoryBucket;
+ private final DelinquencyBucketMappingsRepository repositoryBucketMappings;
+
+ @Override
+ public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
+ DelinquencyRangeData data = dataValidatorRange.validateAndParseUpdate(command);
+ Map<String, Object> changes = new HashMap<>();
+ DelinquencyRange delinquencyRange = updateOrCreateDelinquencyRange(data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult updateDelinquencyRange(Long delinquencyRangeId, JsonCommand command) {
+ DelinquencyRangeData data = dataValidatorRange.validateAndParseUpdate(command);
+ DelinquencyRange delinquencyRange = this.repositoryRange.getReferenceById(delinquencyRangeId);
+ if (delinquencyRange == null) {
+ throw DelinquencyRangeNotFoundException.notFound(delinquencyRangeId);
+ }
+ Map<String, Object> changes = new HashMap<>();
+ delinquencyRange = updateDelinquencyRange(delinquencyRange, data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult deleteDelinquencyRange(Long delinquencyRangeId, JsonCommand command) {
+ final DelinquencyRange delinquencyRange = repositoryRange.getReferenceById(delinquencyRangeId);
+ repositoryRange.delete(delinquencyRange);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).build();
+ }
+
+ @Override
+ public CommandProcessingResult createDelinquencyBucket(JsonCommand command) {
+ DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
+ Map<String, Object> changes = new HashMap<>();
+ DelinquencyBucket delinquencyBucket = updateOrCreateDelinquencyBucket(data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult updateDelinquencyBucket(Long delinquencyBucketId, JsonCommand command) {
+ DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
+ DelinquencyBucket delinquencyBucket = this.repositoryBucket.getReferenceById(delinquencyBucketId);
+ if (delinquencyBucket == null) {
+ throw DelinquencyBucketNotFoundException.notFound(delinquencyBucketId);
+ }
+ Map<String, Object> changes = new HashMap<>();
+ delinquencyBucket = updateDelinquencyBucket(delinquencyBucket, data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult deleteDelinquencyBucket(Long delinquencyBucketId, JsonCommand command) {
+ final DelinquencyBucket delinquencyBucket = repositoryBucket.getReferenceById(delinquencyBucketId);
Review Comment:
Shouldn't we handle the case when the method is called for a non-existing delinquencyBucketId; i.e. not found?
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java:
##########
@@ -0,0 +1,195 @@
+/**
+ * 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.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
+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.DelinquencyRange;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
+import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException;
+import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeNotFoundException;
+import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator;
+import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlatformService {
+
+ private final DelinquencyBucketParseAndValidator dataValidatorBucket;
+ private final DelinquencyRangeParseAndValidator dataValidatorRange;
+
+ private final DelinquencyRangeRepository repositoryRange;
+ private final DelinquencyBucketRepository repositoryBucket;
+ private final DelinquencyBucketMappingsRepository repositoryBucketMappings;
+
+ @Override
+ public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
+ DelinquencyRangeData data = dataValidatorRange.validateAndParseUpdate(command);
+ Map<String, Object> changes = new HashMap<>();
+ DelinquencyRange delinquencyRange = updateOrCreateDelinquencyRange(data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult updateDelinquencyRange(Long delinquencyRangeId, JsonCommand command) {
+ DelinquencyRangeData data = dataValidatorRange.validateAndParseUpdate(command);
+ DelinquencyRange delinquencyRange = this.repositoryRange.getReferenceById(delinquencyRangeId);
+ if (delinquencyRange == null) {
+ throw DelinquencyRangeNotFoundException.notFound(delinquencyRangeId);
+ }
+ Map<String, Object> changes = new HashMap<>();
+ delinquencyRange = updateDelinquencyRange(delinquencyRange, data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult deleteDelinquencyRange(Long delinquencyRangeId, JsonCommand command) {
+ final DelinquencyRange delinquencyRange = repositoryRange.getReferenceById(delinquencyRangeId);
+ repositoryRange.delete(delinquencyRange);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).build();
+ }
+
+ @Override
+ public CommandProcessingResult createDelinquencyBucket(JsonCommand command) {
+ DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
+ Map<String, Object> changes = new HashMap<>();
+ DelinquencyBucket delinquencyBucket = updateOrCreateDelinquencyBucket(data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult updateDelinquencyBucket(Long delinquencyBucketId, JsonCommand command) {
+ DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
+ DelinquencyBucket delinquencyBucket = this.repositoryBucket.getReferenceById(delinquencyBucketId);
+ if (delinquencyBucket == null) {
+ throw DelinquencyBucketNotFoundException.notFound(delinquencyBucketId);
Review Comment:
This code is never going to run because the delinquencyBucket variable is never null (as IntelliJ suggests it too).
the getReferenceById call will return the reference if found and "throw an javax.persistence.EntityNotFoundException on first access"
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java:
##########
@@ -0,0 +1,195 @@
+/**
+ * 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.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
+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.DelinquencyRange;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
+import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException;
+import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeNotFoundException;
+import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator;
+import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlatformService {
+
+ private final DelinquencyBucketParseAndValidator dataValidatorBucket;
+ private final DelinquencyRangeParseAndValidator dataValidatorRange;
+
+ private final DelinquencyRangeRepository repositoryRange;
+ private final DelinquencyBucketRepository repositoryBucket;
+ private final DelinquencyBucketMappingsRepository repositoryBucketMappings;
+
+ @Override
+ public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
+ DelinquencyRangeData data = dataValidatorRange.validateAndParseUpdate(command);
+ Map<String, Object> changes = new HashMap<>();
+ DelinquencyRange delinquencyRange = updateOrCreateDelinquencyRange(data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult updateDelinquencyRange(Long delinquencyRangeId, JsonCommand command) {
+ DelinquencyRangeData data = dataValidatorRange.validateAndParseUpdate(command);
+ DelinquencyRange delinquencyRange = this.repositoryRange.getReferenceById(delinquencyRangeId);
+ if (delinquencyRange == null) {
+ throw DelinquencyRangeNotFoundException.notFound(delinquencyRangeId);
+ }
+ Map<String, Object> changes = new HashMap<>();
+ delinquencyRange = updateDelinquencyRange(delinquencyRange, data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult deleteDelinquencyRange(Long delinquencyRangeId, JsonCommand command) {
+ final DelinquencyRange delinquencyRange = repositoryRange.getReferenceById(delinquencyRangeId);
+ repositoryRange.delete(delinquencyRange);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).build();
+ }
+
+ @Override
+ public CommandProcessingResult createDelinquencyBucket(JsonCommand command) {
+ DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
+ Map<String, Object> changes = new HashMap<>();
+ DelinquencyBucket delinquencyBucket = updateOrCreateDelinquencyBucket(data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult updateDelinquencyBucket(Long delinquencyBucketId, JsonCommand command) {
+ DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
+ DelinquencyBucket delinquencyBucket = this.repositoryBucket.getReferenceById(delinquencyBucketId);
+ if (delinquencyBucket == null) {
+ throw DelinquencyBucketNotFoundException.notFound(delinquencyBucketId);
+ }
+ Map<String, Object> changes = new HashMap<>();
+ delinquencyBucket = updateDelinquencyBucket(delinquencyBucket, data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult deleteDelinquencyBucket(Long delinquencyBucketId, JsonCommand command) {
+ final DelinquencyBucket delinquencyBucket = repositoryBucket.getReferenceById(delinquencyBucketId);
+ repositoryBucket.delete(delinquencyBucket);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).build();
+ }
+
+ @Override
+ @CronTarget(jobName = JobName.LOAN_DELINQUENCY_CLASSIFICATION)
Review Comment:
Not needed in the context of this PR.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java:
##########
@@ -292,6 +293,10 @@ public class Loan extends AbstractPersistableCustom {
@Column(name = "loan_product_counter")
private Integer loanProductCounter;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "delinquency_range_id", nullable = true)
+ private DelinquencyRange delinquencyRange;
Review Comment:
Maybe I misunderstood but isn't the LoanDelinquencyTags storing which delinquency range a loan is related to? What's the point of this? Thanks.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyRange.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 javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.persistence.Version;
+import javax.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_delinquency_range", uniqueConstraints = {
+ @UniqueConstraint(name = "uq_delinquency_range_classification", columnNames = { "classification" }) })
+public class DelinquencyRange extends AbstractAuditableCustom {
Review Comment:
AbstractAuditableWithUTCDateTimeCustom
##########
fineract-provider/src/main/resources/db/changelog/tenant/parts/0020_add_delinquency_buckets.xml:
##########
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+ <changeSet author="fineract" id="1">
+ <createTable tableName="m_delinquency_range">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column name="classification" type="VARCHAR(100)">
+ <constraints unique="true" nullable="false"/>
+ </column>
+ <column name="min_age" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="max_age" type="BIGINT">
+ <constraints nullable="true" />
+ </column>
+ <column name="createdby_id" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="created_date" type="DATETIME">
+ <constraints nullable="false" />
+ </column>
+ <column name="version" type="BIGINT">
+ <constraints nullable="false"/>
+ </column>
+ <column defaultValueComputed="NULL" name="lastmodifiedby_id" type="BIGINT"/>
+ <column defaultValueComputed="NULL" name="lastmodified_date" type="DATETIME"/>
+ </createTable>
+ </changeSet>
+ <changeSet author="fineract" id="2">
+ <createTable tableName="m_delinquency_bucket">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column name="name" type="VARCHAR(100)">
+ <constraints unique="true" nullable="false"/>
+ </column>
+ <column name="createdby_id" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="created_date" type="DATETIME">
+ <constraints nullable="false" />
+ </column>
+ <column name="version" type="BIGINT">
+ <constraints nullable="false"/>
+ </column>
+ <column defaultValueComputed="NULL" name="lastmodifiedby_id" type="BIGINT"/>
+ <column defaultValueComputed="NULL" name="lastmodified_date" type="DATETIME"/>
+ </createTable>
+ </changeSet>
+ <changeSet author="fineract" id="3">
+ <createTable tableName="m_delinquency_bucket_mappings">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column name="delinquency_range_id" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="delinquency_bucket_id" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="createdby_id" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="created_date" type="DATETIME">
+ <constraints nullable="false" />
+ </column>
+ <column name="version" type="BIGINT">
+ <constraints nullable="false"/>
+ </column>
+ <column defaultValueComputed="NULL" name="lastmodifiedby_id" type="BIGINT"/>
+ <column defaultValueComputed="NULL" name="lastmodified_date" type="DATETIME"/>
+ </createTable>
+ </changeSet>
+ <changeSet author="fineract" id="4">
+ <addForeignKeyConstraint baseColumnNames="delinquency_range_id" baseTableName="m_delinquency_bucket_mappings"
+ constraintName="FK_m_delinquency_range_mapping" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_delinquency_range" validate="true"/>
+ <addForeignKeyConstraint baseColumnNames="delinquency_bucket_id" baseTableName="m_delinquency_bucket_mappings"
+ constraintName="FK_m_delinquency_bucket_mapping" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_delinquency_bucket" validate="true"/>
+ </changeSet>
+ <changeSet author="fineract" id="5">
+ <addColumn tableName="m_product_loan">
+ <column name="delinquency_bucket_id" type="BIGINT" defaultValueComputed="NULL"/>
+ </addColumn>
+ <addForeignKeyConstraint baseColumnNames="delinquency_bucket_id" baseTableName="m_product_loan"
+ constraintName="FK_m_product_loan_m_delinquency_bucket" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_delinquency_bucket" validate="true"/>
+ </changeSet>
+ <changeSet author="fineract" id="6">
+ <addColumn tableName="m_loan">
+ <column name="delinquency_range_id" type="BIGINT" defaultValueComputed="NULL"/>
+ </addColumn>
+ <addForeignKeyConstraint baseColumnNames="delinquency_range_id" baseTableName="m_loan"
+ constraintName="FK_m_delinquency_range_loan" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_delinquency_range" validate="true"/>
+ </changeSet>
+ <changeSet author="fineract" id="7">
+ <createTable tableName="m_loan_delinquency_tags">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column name="delinquency_range_id" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="loan_id" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="createdby_id" type="BIGINT">
+ <constraints nullable="false" />
+ </column>
+ <column name="created_date" type="DATETIME">
+ <constraints nullable="false" />
+ </column>
+ <column defaultValueComputed="NULL" name="lastmodifiedby_id" type="BIGINT"/>
+ <column defaultValueComputed="NULL" name="lastmodified_date" type="DATETIME"/>
+ </createTable>
+ <addForeignKeyConstraint baseColumnNames="delinquency_range_id" baseTableName="m_loan_delinquency_tags"
+ constraintName="FK_m_delinquency_tags_range" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_delinquency_range" validate="true"/>
+ <addForeignKeyConstraint baseColumnNames="loan_id" baseTableName="m_loan_delinquency_tags"
+ constraintName="FK_m_delinquency_tags_loan" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_loan" validate="true"/>
+ </changeSet>
+ <changeSet author="fineract" id="8">
+ <insert tableName="m_permission">
+ <column name="grouping" value="organisation"/>
+ <column name="code" value="READ_DELINQUENCY_BUCKET"/>
+ <column name="entity_name" value="DELINQUENCY_BUCKET"/>
+ <column name="action_name" value="READ"/>
+ <column name="can_maker_checker" valueBoolean="false"/>
+ </insert>
+ <insert tableName="m_permission">
+ <column name="grouping" value="organisation"/>
+ <column name="code" value="CREATE_DELINQUENCY_BUCKET"/>
+ <column name="entity_name" value="DELINQUENCY_BUCKET"/>
+ <column name="action_name" value="CREATE"/>
+ <column name="can_maker_checker" valueBoolean="false"/>
+ </insert>
+ <insert tableName="m_permission">
+ <column name="grouping" value="organisation"/>
+ <column name="code" value="UPDATE_DELINQUENCY_BUCKET"/>
+ <column name="entity_name" value="DELINQUENCY_BUCKET"/>
+ <column name="action_name" value="UPDATE"/>
+ <column name="can_maker_checker" valueBoolean="false"/>
+ </insert>
+ <insert tableName="m_permission">
+ <column name="grouping" value="organisation"/>
+ <column name="code" value="DELETE_DELINQUENCY_BUCKET"/>
+ <column name="entity_name" value="DELINQUENCY_BUCKET"/>
+ <column name="action_name" value="DELETE"/>
+ <column name="can_maker_checker" valueBoolean="false"/>
+ </insert>
+ <insert tableName="m_permission">
+ <column name="grouping" value="organisation"/>
+ <column name="code" value="CREATE_DELINQUENCY_RANGE"/>
+ <column name="entity_name" value="DELINQUENCY_RANGE"/>
+ <column name="action_name" value="CREATE"/>
+ <column name="can_maker_checker" valueBoolean="false"/>
+ </insert>
+ <insert tableName="m_permission">
+ <column name="grouping" value="organisation"/>
+ <column name="code" value="UPDATE_DELINQUENCY_RANGE"/>
+ <column name="entity_name" value="DELINQUENCY_RANGE"/>
+ <column name="action_name" value="UPDATE"/>
+ <column name="can_maker_checker" valueBoolean="false"/>
+ </insert>
+ <insert tableName="m_permission">
+ <column name="grouping" value="organisation"/>
+ <column name="code" value="DELETE_DELINQUENCY_RANGE"/>
+ <column name="entity_name" value="DELINQUENCY_RANGE"/>
+ <column name="action_name" value="DELETE"/>
+ <column name="can_maker_checker" valueBoolean="false"/>
+ </insert>
+ </changeSet>
+ <changeSet author="fineract" id="9">
Review Comment:
No need for this in this ticket.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/data/DelinquencyBucketData.java:
##########
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.delinquency.data;
+
+import java.io.Serializable;
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@ToString
+@Getter
+@Setter
+public class DelinquencyBucketData implements Serializable {
+
+ private Long id;
+ private String name;
+ private List<DelinquencyRangeData> ranges;
+
+ public DelinquencyBucketData(String name, List<DelinquencyRangeData> ranges) {
+ this.name = name;
+ this.ranges = ranges;
+ }
+
+ public DelinquencyBucketData(Long id, String name, List<DelinquencyRangeData> ranges) {
+ this.id = id;
+ this.name = name;
+ this.ranges = ranges;
+ }
+
+ public static DelinquencyBucketData instance(Long id, String name, List<DelinquencyRangeData> ranges) {
Review Comment:
Do you mind explaining why these static factories are needed? Thanks.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanDelinquencyTags.java:
##########
@@ -0,0 +1,59 @@
+/**
+ * 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 javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Version;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_loan_delinquency_tags")
+public class LoanDelinquencyTags extends AbstractAuditableCustom {
+
+ @OneToOne(fetch = FetchType.EAGER)
Review Comment:
This is a big no-no. We don't want EAGER relationships for 99% of the cases, let's not do it.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyRange.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 javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.persistence.Version;
+import javax.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_delinquency_range", uniqueConstraints = {
+ @UniqueConstraint(name = "uq_delinquency_range_classification", columnNames = { "classification" }) })
+public class DelinquencyRange extends AbstractAuditableCustom {
+
+ @Column(name = "classification", nullable = false)
Review Comment:
I thought we agreed that the classification name is going to come from the m_code table as a reference data. This looks to me like any user-entered String is accepted.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanDelinquencyTags.java:
##########
@@ -0,0 +1,59 @@
+/**
+ * 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 javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Version;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_loan_delinquency_tags")
+public class LoanDelinquencyTags extends AbstractAuditableCustom {
Review Comment:
For the Tags, we're missing the historical information when a Loan gets moved in and out of a range.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java:
##########
@@ -0,0 +1,122 @@
+/**
+ * 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.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
+import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException;
+import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyRangeMapper;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatformService {
+
+ private final JdbcTemplate jdbcTemplate;
+ private final DelinquencyRangeRepository repositoryRange;
+ private final DelinquencyRangeMapper mapperRange;
+
+ @Override
+ public Collection<DelinquencyRangeData> retrieveAllDelinquencyRanges() {
+ List<DelinquencyRange> delinquencyRangeList = repositoryRange.findAll();
+ return mapperRange.map(delinquencyRangeList);
+ }
+
+ @Override
+ public DelinquencyRangeData retrieveDelinquencyRange(Long delinquencyRangeId) {
+ DelinquencyRange delinquencyRangeList = repositoryRange.getReferenceById(delinquencyRangeId);
+ return mapperRange.map(delinquencyRangeList);
+ }
+
+ @Override
+ public Collection<DelinquencyBucketData> retrieveAllDelinquencyBuckets() {
+ final DelinquencyBucketMapper rm = new DelinquencyBucketMapper();
Review Comment:
Big no-no. Let's not introduce native queries for places where we don't need to. The only place we should use native queries is where things couldn't be solved by using JPQL/CriteriaAPI/etc.
This whole thing could be replaced by a simple Repository call with a Spring Data/JPQL Projection.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanDelinquencyTags.java:
##########
@@ -0,0 +1,59 @@
+/**
+ * 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 javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Version;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_loan_delinquency_tags")
+public class LoanDelinquencyTags extends AbstractAuditableCustom {
+
+ @OneToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "delinquency_range_id", nullable = false)
+ private DelinquencyRange delinquencyRange;
+
+ @OneToOne(fetch = FetchType.EAGER)
Review Comment:
Same here.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java:
##########
@@ -0,0 +1,122 @@
+/**
+ * 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.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
+import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException;
+import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyRangeMapper;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatformService {
+
+ private final JdbcTemplate jdbcTemplate;
+ private final DelinquencyRangeRepository repositoryRange;
+ private final DelinquencyRangeMapper mapperRange;
+
+ @Override
+ public Collection<DelinquencyRangeData> retrieveAllDelinquencyRanges() {
+ List<DelinquencyRange> delinquencyRangeList = repositoryRange.findAll();
+ return mapperRange.map(delinquencyRangeList);
+ }
+
+ @Override
+ public DelinquencyRangeData retrieveDelinquencyRange(Long delinquencyRangeId) {
+ DelinquencyRange delinquencyRangeList = repositoryRange.getReferenceById(delinquencyRangeId);
+ return mapperRange.map(delinquencyRangeList);
+ }
+
+ @Override
+ public Collection<DelinquencyBucketData> retrieveAllDelinquencyBuckets() {
+ final DelinquencyBucketMapper rm = new DelinquencyBucketMapper();
+ final String sql = "select " + rm.schema() + " order by db.name";
+
+ return this.jdbcTemplate.query(sql, rm, new Object[] {}); // NOSONAR
+ }
+
+ @Override
+ public DelinquencyBucketData retrieveDelinquencyBucket(Long delinquencyBucketId) {
+ try {
+ final DelinquencyBucketMapper rm = new DelinquencyBucketMapper();
Review Comment:
Same here.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java:
##########
@@ -0,0 +1,195 @@
+/**
+ * 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.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
+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.DelinquencyRange;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
+import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException;
+import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeNotFoundException;
+import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator;
+import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlatformService {
+
+ private final DelinquencyBucketParseAndValidator dataValidatorBucket;
+ private final DelinquencyRangeParseAndValidator dataValidatorRange;
+
+ private final DelinquencyRangeRepository repositoryRange;
+ private final DelinquencyBucketRepository repositoryBucket;
+ private final DelinquencyBucketMappingsRepository repositoryBucketMappings;
+
+ @Override
+ public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
+ DelinquencyRangeData data = dataValidatorRange.validateAndParseUpdate(command);
+ Map<String, Object> changes = new HashMap<>();
+ DelinquencyRange delinquencyRange = updateOrCreateDelinquencyRange(data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult updateDelinquencyRange(Long delinquencyRangeId, JsonCommand command) {
+ DelinquencyRangeData data = dataValidatorRange.validateAndParseUpdate(command);
+ DelinquencyRange delinquencyRange = this.repositoryRange.getReferenceById(delinquencyRangeId);
+ if (delinquencyRange == null) {
+ throw DelinquencyRangeNotFoundException.notFound(delinquencyRangeId);
+ }
+ Map<String, Object> changes = new HashMap<>();
+ delinquencyRange = updateDelinquencyRange(delinquencyRange, data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult deleteDelinquencyRange(Long delinquencyRangeId, JsonCommand command) {
+ final DelinquencyRange delinquencyRange = repositoryRange.getReferenceById(delinquencyRangeId);
+ repositoryRange.delete(delinquencyRange);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).build();
+ }
+
+ @Override
+ public CommandProcessingResult createDelinquencyBucket(JsonCommand command) {
+ DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
+ Map<String, Object> changes = new HashMap<>();
+ DelinquencyBucket delinquencyBucket = updateOrCreateDelinquencyBucket(data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult updateDelinquencyBucket(Long delinquencyBucketId, JsonCommand command) {
+ DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
+ DelinquencyBucket delinquencyBucket = this.repositoryBucket.getReferenceById(delinquencyBucketId);
+ if (delinquencyBucket == null) {
+ throw DelinquencyBucketNotFoundException.notFound(delinquencyBucketId);
+ }
+ Map<String, Object> changes = new HashMap<>();
+ delinquencyBucket = updateDelinquencyBucket(delinquencyBucket, data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult deleteDelinquencyBucket(Long delinquencyBucketId, JsonCommand command) {
+ final DelinquencyBucket delinquencyBucket = repositoryBucket.getReferenceById(delinquencyBucketId);
+ repositoryBucket.delete(delinquencyBucket);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).build();
+ }
+
+ @Override
+ @CronTarget(jobName = JobName.LOAN_DELINQUENCY_CLASSIFICATION)
+ public void setLoanDelinquencyClassification() throws JobExecutionException {
+
+ }
+
+ private DelinquencyRange updateOrCreateDelinquencyRange(DelinquencyRangeData data, Map<String, Object> changes) {
Review Comment:
I'm not really sure why these 2 cases have been implemented as one method.
Let's separate the responsibilities and have one method for creation and one for update.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucket.java:
##########
@@ -0,0 +1,53 @@
+/**
+ * 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 javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.persistence.Version;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_delinquency_bucket", uniqueConstraints = {
+ @UniqueConstraint(name = "uq_delinquency_bucket_name", columnNames = { "name" }) })
+public class DelinquencyBucket extends AbstractAuditableCustom {
Review Comment:
Please use the new AbstractAuditableWithUTCDateTimeCustom
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java:
##########
@@ -0,0 +1,195 @@
+/**
+ * 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.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
+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.DelinquencyRange;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
+import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException;
+import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeNotFoundException;
+import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator;
+import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlatformService {
+
+ private final DelinquencyBucketParseAndValidator dataValidatorBucket;
+ private final DelinquencyRangeParseAndValidator dataValidatorRange;
+
+ private final DelinquencyRangeRepository repositoryRange;
+ private final DelinquencyBucketRepository repositoryBucket;
+ private final DelinquencyBucketMappingsRepository repositoryBucketMappings;
+
+ @Override
+ public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
+ DelinquencyRangeData data = dataValidatorRange.validateAndParseUpdate(command);
+ Map<String, Object> changes = new HashMap<>();
+ DelinquencyRange delinquencyRange = updateOrCreateDelinquencyRange(data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult updateDelinquencyRange(Long delinquencyRangeId, JsonCommand command) {
+ DelinquencyRangeData data = dataValidatorRange.validateAndParseUpdate(command);
+ DelinquencyRange delinquencyRange = this.repositoryRange.getReferenceById(delinquencyRangeId);
+ if (delinquencyRange == null) {
+ throw DelinquencyRangeNotFoundException.notFound(delinquencyRangeId);
+ }
+ Map<String, Object> changes = new HashMap<>();
+ delinquencyRange = updateDelinquencyRange(delinquencyRange, data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult deleteDelinquencyRange(Long delinquencyRangeId, JsonCommand command) {
+ final DelinquencyRange delinquencyRange = repositoryRange.getReferenceById(delinquencyRangeId);
+ repositoryRange.delete(delinquencyRange);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).build();
+ }
+
+ @Override
+ public CommandProcessingResult createDelinquencyBucket(JsonCommand command) {
+ DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
+ Map<String, Object> changes = new HashMap<>();
+ DelinquencyBucket delinquencyBucket = updateOrCreateDelinquencyBucket(data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult updateDelinquencyBucket(Long delinquencyBucketId, JsonCommand command) {
+ DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
+ DelinquencyBucket delinquencyBucket = this.repositoryBucket.getReferenceById(delinquencyBucketId);
+ if (delinquencyBucket == null) {
+ throw DelinquencyBucketNotFoundException.notFound(delinquencyBucketId);
+ }
+ Map<String, Object> changes = new HashMap<>();
+ delinquencyBucket = updateDelinquencyBucket(delinquencyBucket, data, changes);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).with(changes)
+ .build();
+ }
+
+ @Override
+ public CommandProcessingResult deleteDelinquencyBucket(Long delinquencyBucketId, JsonCommand command) {
+ final DelinquencyBucket delinquencyBucket = repositoryBucket.getReferenceById(delinquencyBucketId);
+ repositoryBucket.delete(delinquencyBucket);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).build();
+ }
+
+ @Override
+ @CronTarget(jobName = JobName.LOAN_DELINQUENCY_CLASSIFICATION)
+ public void setLoanDelinquencyClassification() throws JobExecutionException {
+
+ }
+
+ private DelinquencyRange updateOrCreateDelinquencyRange(DelinquencyRangeData data, Map<String, Object> changes) {
+ Optional<DelinquencyRange> delinquencyRange = repositoryRange.findByClassification(data.getClassification());
+
+ if (delinquencyRange.isEmpty()) {
+ DelinquencyRange newDelinquencyRange = DelinquencyRange.instance(data.getClassification(), data.getMinimumAge(),
+ data.getMaximumAge());
+ return repositoryRange.saveAndFlush(newDelinquencyRange);
+ } else {
+ return updateDelinquencyRange(delinquencyRange.get(), data, changes);
+ }
+ }
+
+ private DelinquencyRange updateDelinquencyRange(DelinquencyRange delinquencyRange, DelinquencyRangeData data,
+ Map<String, Object> changes) {
+ if (!data.getClassification().equalsIgnoreCase(delinquencyRange.getClassification())) {
+ delinquencyRange.setClassification(data.getClassification());
+ changes.put(DelinquencyApiConstants.CLASSIFICATION_PARAM_NAME, data.getClassification());
+ }
+ if (!data.getMinimumAge().equals(delinquencyRange.getMinimumAge())) {
+ delinquencyRange.setMinimumAge(data.getMinimumAge());
+ changes.put(DelinquencyApiConstants.MINIMUMAGE_PARAM_NAME, data.getMinimumAge());
+ }
+ if (!data.getMaximumAge().equals(delinquencyRange.getMaximumAge())) {
+ delinquencyRange.setMaximumAge(data.getMaximumAge());
+ changes.put(DelinquencyApiConstants.MAXIMUMAGE_PARAM_NAME, data.getMaximumAge());
+ }
+ if (!changes.isEmpty()) {
+ delinquencyRange = repositoryRange.saveAndFlush(delinquencyRange);
+ }
+ return delinquencyRange;
+ }
+
+ private DelinquencyBucket updateOrCreateDelinquencyBucket(DelinquencyBucketData data, Map<String, Object> changes) {
Review Comment:
Same here.
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/DelinquencyBucketMappings.java:
##########
@@ -0,0 +1,60 @@
+/**
+ * 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 javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.persistence.Version;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_delinquency_bucket_mappings", uniqueConstraints = {
+ @UniqueConstraint(name = "uq_delinquency_bucket_mapping", columnNames = { "delinquencyBucket", "delinquencyRange" }) })
+public class DelinquencyBucketMappings extends AbstractAuditableCustom {
Review Comment:
AbstractAuditableWithUTCDateTimeCustom
##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/domain/LoanDelinquencyTags.java:
##########
@@ -0,0 +1,59 @@
+/**
+ * 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 javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Version;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_loan_delinquency_tags")
+public class LoanDelinquencyTags extends AbstractAuditableCustom {
Review Comment:
AbstractAuditableWithUTCDateTimeCustom
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fineract.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org