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: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]