FINCN-351 Make the scorecard integration into a plugin
- Credit Scorecard module:
- Credit Scorecard organisational module API
- Rule Based and ML Scoring Entities
- Credit Scorecard Validators and JSON assemblers
- Credit Scorecard Read and Write Services
- Credit Scorecard Flyway Migration
- Credit scorecard Loan Product Feature Configurations
- Credit Scorecard integration in Loan Account Creation
- FINCN-331 Statistical Credit Scorecard
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index b21dfa8..5fe9490 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -3442,4 +3442,28 @@
return this;
}
+ public CommandWrapperBuilder createCreditScorecardFeature() {
+ this.actionName = "CREATE";
+ this.entityName = "CREDIT_SCORECARD_FEATURE";
+ this.entityId = null;
+ this.href = "/scorecard/features/template";
+ return this;
+ }
+
+ public CommandWrapperBuilder updateCreditScorecardFeature(Long featureId) {
+ this.actionName = "UPDATE";
+ this.entityName = "CREDIT_SCORECARD_FEATURE";
+ this.entityId = featureId;
+ this.href = "/scorecard/features/" + featureId;
+ return this;
+ }
+
+ public CommandWrapperBuilder deleteCreditScorecardFeature(Long featureId) {
+ this.actionName = "DELETE";
+ this.entityName = "CREDIT_SCORECARD_FEATURE";
+ this.entityId = featureId;
+ this.href = "/scorecard/features/" + featureId;
+ return this;
+ }
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/provider/ReportingProcessServiceProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/provider/ReportingProcessServiceProvider.java
index 84e812a..f6feae9 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/provider/ReportingProcessServiceProvider.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/report/provider/ReportingProcessServiceProvider.java
@@ -49,6 +49,7 @@
mapBuilder.put(type, s);
}
LOGGER.info("Registered report service '{}' for type/s '{}'", s, reportTypes);
+ LOGGER.warn("Registered report service '{}' for type/s '{}'", s, reportTypes);
}
this.reportingProcessServices = mapBuilder.build();
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferTransaction.java
index a4c34bf..312b486 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferTransaction.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferTransaction.java
@@ -25,6 +25,7 @@
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
+import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@@ -40,23 +41,23 @@
@Table(name = "m_account_transfer_transaction")
public class AccountTransferTransaction extends AbstractPersistableCustom {
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "account_transfer_details_id", nullable = true)
private AccountTransferDetails accountTransferDetails;
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "from_savings_transaction_id", nullable = true)
private SavingsAccountTransaction fromSavingsTransaction;
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "to_savings_transaction_id", nullable = true)
private SavingsAccountTransaction toSavingsTransaction;
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "to_loan_transaction_id", nullable = true)
private LoanTransaction toLoanTransaction;
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "from_loan_transaction_id", nullable = true)
private LoanTransaction fromLoanTransaction;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/annotation/ScorecardService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/annotation/ScorecardService.java
new file mode 100644
index 0000000..0d7e1d4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/annotation/ScorecardService.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.creditscorecard.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.springframework.stereotype.Service;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+@Service
+public @interface ScorecardService {
+
+ /**
+ * @return the name of the service
+ */
+ String name();
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/api/CreditScorecardApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/api/CreditScorecardApiResource.java
new file mode 100644
index 0000000..40b3e24
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/api/CreditScorecardApiResource.java
@@ -0,0 +1,237 @@
+/**
+ * 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.creditscorecard.api;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardFeatureData;
+import org.apache.fineract.portfolio.creditscorecard.domain.CreditScorecardFeature;
+import org.apache.fineract.portfolio.creditscorecard.provider.ScorecardServiceProvider;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/creditScorecard")
+@Component
+@Scope("singleton")
+@Tag(name = "CreditScorecard", description = "")
+public class CreditScorecardApiResource {
+
+ private final Set<String> featuresDataParameters = new HashSet<>(
+ Arrays.asList("id", "name", "valueType", "dataType", "category", "valueTypeOptions", "dataTypeOptions", "categoryOptions"));
+
+ private final String resourceNameForPermissions = "CREDIT_SCORECARD";
+
+ private final PlatformSecurityContext context;
+ private final ScorecardServiceProvider scorecardServiceProvider;
+ private final ScorecardServiceProvider serviceProvider;
+ private final DefaultToApiJsonSerializer<CreditScorecardFeatureData> toApiJsonSerializer;
+ private final ApiRequestParameterHelper apiRequestParameterHelper;
+ private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+ @Autowired
+ public CreditScorecardApiResource(final PlatformSecurityContext context, final ScorecardServiceProvider scorecardServiceProvider,
+ final ScorecardServiceProvider serviceProvider,
+ final DefaultToApiJsonSerializer<CreditScorecardFeatureData> toApiJsonSerializer,
+ final ApiRequestParameterHelper apiRequestParameterHelper,
+ final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+ this.context = context;
+ this.scorecardServiceProvider = scorecardServiceProvider;
+ this.serviceProvider = serviceProvider;
+ this.toApiJsonSerializer = toApiJsonSerializer;
+ this.apiRequestParameterHelper = apiRequestParameterHelper;
+ this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+ }
+
+ @GET
+ @Path("features")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Retrieve Scorecard Features", description = "Returns the list of defined scorecard features.\n" + "\n"
+ + "Example Requests:\n" + "\n" + "features")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = CreditScorecardApiResourceSwagger.GetScorecardFeatureResponse.class)))) })
+ public String retrieveAllScoringFeatures(@Context final UriInfo uriInfo) {
+
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+ final String serviceName = "CreditScorecardReadPlatformService";
+ final CreditScorecardReadPlatformService scorecardService = (CreditScorecardReadPlatformService) scorecardServiceProvider
+ .getScorecardService(serviceName);
+
+ if (scorecardService == null) {
+ throw new PlatformServiceUnavailableException("err.msg.credit.scorecard.service.implementation.missing",
+ ScorecardServiceProvider.SERVICE_MISSING + serviceName, serviceName);
+ }
+
+ final Collection<CreditScorecardFeatureData> scoringFeatures = scorecardService.findAllFeaturesWithNotFoundDetection().stream()
+ .map(CreditScorecardFeature::toData).collect(Collectors.toList());
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, scoringFeatures, this.featuresDataParameters);
+ }
+
+ @GET
+ @Path("features/{featureId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Retrieve a Scorecard Feature", description = "Returns the details of a defined Scorecard Feature.\n" + "\n"
+ + "Example Requests:\n" + "\n" + "scorecard/features/1")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CreditScorecardApiResourceSwagger.GetScorecardFeatureResponse.class))) })
+ public String retrieveScoringFeature(@PathParam("featureId") @Parameter(description = "featureId") final Long featureId,
+ @Context final UriInfo uriInfo) {
+
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+ CreditScorecardFeatureData scorecardFeature = null;
+ if (settings.isTemplate()) {
+
+ final String serviceName = "CreditScorecardReadPlatformService";
+ final CreditScorecardReadPlatformService scorecardService = (CreditScorecardReadPlatformService) scorecardServiceProvider
+ .getScorecardService(serviceName);
+
+ if (scorecardService == null) {
+ throw new PlatformServiceUnavailableException("err.msg.credit.scorecard.service.implementation.missing",
+ ScorecardServiceProvider.SERVICE_MISSING + serviceName, serviceName);
+ }
+
+ scorecardFeature = scorecardService.findOneFeatureWithNotFoundDetection(featureId).toData();
+ final CreditScorecardFeatureData templateData = scorecardService.retrieveNewScorecardFeatureDetails();
+ scorecardFeature = CreditScorecardFeatureData.withTemplate(scorecardFeature, templateData);
+ }
+
+ return this.toApiJsonSerializer.serialize(settings, scorecardFeature, this.featuresDataParameters);
+ }
+
+ @GET
+ @Path("features/template")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Retrieve Scorecard Feature Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n"
+ + "\n" + "Field Defaults\n" + "Allowed description Lists\n" + "Example Request:\n" + "\n" + "scorecard/features/template\n")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CreditScorecardApiResourceSwagger.GetScorecardFeaturesTemplateResponse.class))) })
+ public String retrieveNewScorecardFeatureDetails(@Context final UriInfo uriInfo) {
+
+ this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+ final String serviceName = "CreditScorecardReadPlatformService";
+ final CreditScorecardReadPlatformService scorecardService = (CreditScorecardReadPlatformService) scorecardServiceProvider
+ .getScorecardService(serviceName);
+
+ if (scorecardService == null) {
+ throw new PlatformServiceUnavailableException("err.msg.credit.scorecard.service.implementation.missing",
+ ScorecardServiceProvider.SERVICE_MISSING + serviceName, serviceName);
+ }
+
+ final CreditScorecardFeatureData scorecardFeature = scorecardService.retrieveNewScorecardFeatureDetails();
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ return this.toApiJsonSerializer.serialize(settings, scorecardFeature, this.featuresDataParameters);
+ }
+
+ @POST
+ @Path("features")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Create/Define a Scorecard Feature", description = "Define a new scorecard feature that can later be associated with loans and savings through their respective product definitions or directly on each account instance.")
+ @RequestBody(required = true, content = @Content(schema = @Schema(implementation = CreditScorecardApiResourceSwagger.PostScorecardFeatureRequest.class)))
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CreditScorecardApiResourceSwagger.PostScorecardFeatureResponse.class))) })
+ public String createScoringFeature(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().createCreditScorecardFeature().withJson(apiRequestBodyAsJson)
+ .build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ @PUT
+ @Path("features/{featureId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Update a Scorecard Feature", description = "Updates the details of a Scorecard Feature.")
+ @RequestBody(required = true, content = @Content(schema = @Schema(implementation = CreditScorecardApiResourceSwagger.PutScorecardFeaturesFeatureIdRequest.class)))
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CreditScorecardApiResourceSwagger.PutScorecardFeaturesFeatureIdResponse.class))) })
+ public String updateScoringFeature(@PathParam("featureId") @Parameter(description = "featureId") final Long featureId,
+ @Parameter(hidden = true) final String apiRequestBodyAsJson) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCreditScorecardFeature(featureId)
+ .withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ @DELETE
+ @Path("features/{featureId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Delete a Scorecard Feature", description = "Deletes a Scorecard Feature.")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CreditScorecardApiResourceSwagger.DeleteScorecardFeaturesFeatureIdResponse.class))) })
+ public String deleteScoringFeature(@PathParam("featureId") @Parameter(description = "featureId") final Long featureId) {
+
+ final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteCreditScorecardFeature(featureId).build();
+
+ final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/api/CreditScorecardApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/api/CreditScorecardApiResourceSwagger.java
new file mode 100644
index 0000000..92af466
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/api/CreditScorecardApiResourceSwagger.java
@@ -0,0 +1,133 @@
+/**
+ * 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.creditscorecard.api;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.Set;
+
+public class CreditScorecardApiResourceSwagger {
+
+ @Schema(description = "GetScorecardFeatureResponse")
+ public static final class GetScorecardFeatureResponse {
+
+ private GetScorecardFeatureResponse() {
+ //
+ }
+
+ static final class GetScorecardFeatureValueTypeResponse {
+
+ @Schema(example = "1")
+ public Integer id;
+ @Schema(example = "valueType.nominal")
+ public String code;
+ @Schema(example = "Nominal")
+ public String description;
+ }
+
+ static final class GetScorecardFeatureDataTypeResponse {
+
+ @Schema(example = "1")
+ public Integer id;
+ @Schema(example = "dataType.string")
+ public String code;
+ @Schema(example = "String")
+ public String description;
+ }
+
+ static final class GetScorecardFeatureCategoryResponse {
+
+ @Schema(example = "0")
+ public Integer id;
+ @Schema(example = "category.individual")
+ public String code;
+ @Schema(example = "Individual")
+ public String description;
+ }
+ }
+
+ @Schema(description = "PostScorecardFeatureRequest")
+ public static final class PostScorecardFeatureRequest {
+
+ private PostScorecardFeatureRequest() {}
+
+ @Schema(example = "Gender")
+ public String name;
+ @Schema(example = "1")
+ public Integer valueType;
+ @Schema(example = "1")
+ public String dataType;
+ @Schema(example = "1")
+ public Integer category;
+ @Schema(example = "true")
+ public String active;
+ @Schema(example = "en")
+ public String locale;
+ }
+
+ @Schema(description = "PostScorecardFeatureResponse")
+ public static final class PostScorecardFeatureResponse {
+
+ private PostScorecardFeatureResponse() {}
+
+ @Schema(example = "1")
+ public Integer resourceId;
+ }
+
+ @Schema(description = "PutScorecardFeaturesFeatureIdRequest")
+ public static final class PutScorecardFeaturesFeatureIdRequest {
+
+ private PutScorecardFeaturesFeatureIdRequest() {}
+
+ @Schema(example = "Loan service fee(changed)")
+ public String name;
+ }
+
+ @Schema(description = "PutScorecardFeaturesFeatureIdResponse")
+ public static final class PutScorecardFeaturesFeatureIdResponse {
+
+ private PutScorecardFeaturesFeatureIdResponse() {}
+
+ @Schema(example = "1")
+ public Integer resourceId;
+ public PutScorecardFeaturesFeatureIdResponse changes;
+ }
+
+ @Schema(description = "DeleteScorecardFeaturesFeatureIdResponse")
+ public static final class DeleteScorecardFeaturesFeatureIdResponse {
+
+ private DeleteScorecardFeaturesFeatureIdResponse() {}
+
+ @Schema(example = "1")
+ public Integer resourceId;
+ }
+
+ @Schema(description = "GetScorecardFeaturesTemplateResponse")
+ public static final class GetScorecardFeaturesTemplateResponse {
+
+ private GetScorecardFeaturesTemplateResponse() {}
+
+ @Schema(example = "false")
+ public String active;
+ @Schema(example = "false")
+ public String penalty;
+ public Set<GetScorecardFeatureResponse.GetScorecardFeatureValueTypeResponse> valueTypeOptions;
+ public Set<GetScorecardFeatureResponse.GetScorecardFeatureDataTypeResponse> dataTypeOptions;
+ public Set<GetScorecardFeatureResponse.GetScorecardFeatureCategoryResponse> categoryOptions;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/CreditScorecardData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/CreditScorecardData.java
new file mode 100644
index 0000000..118c985
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/CreditScorecardData.java
@@ -0,0 +1,135 @@
+/**
+ * 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.creditscorecard.data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+
+public final class CreditScorecardData implements Serializable {
+
+ private final Long id;
+ private final String scoringMethod;
+ private final String scoringModel;
+
+ private final MLScorecardData mlScorecard;
+ private final StatScorecardData statScorecard;
+ private final RuleBasedScorecardData ruleBasedScorecard;
+
+ // Options
+ private final Collection<Map<String, Object>> scoringMethods;
+
+ private CreditScorecardData(final Long id, final String scoringMethod, final String scoringModel, final MLScorecardData mlScorecard,
+ final StatScorecardData statScorecard, final RuleBasedScorecardData ruleBasedScorecard,
+ final Collection<Map<String, Object>> scoringMethods) {
+ this.id = id;
+ this.scoringMethod = scoringMethod;
+ this.scoringModel = scoringModel;
+
+ this.mlScorecard = mlScorecard;
+ this.statScorecard = statScorecard;
+ this.ruleBasedScorecard = ruleBasedScorecard;
+
+ this.scoringMethods = scoringMethods;
+ }
+
+ public static CreditScorecardData instance(final Long id, final String scoringMethod, final String scoringModel) {
+ final MLScorecardData mlScorecardData = null;
+ final StatScorecardData statScorecardData = null;
+ final RuleBasedScorecardData ruleBasedScorecardData = null;
+
+ final Collection<Map<String, Object>> scoringMethods = null;
+
+ return new CreditScorecardData(id, scoringMethod, scoringModel, mlScorecardData, statScorecardData, ruleBasedScorecardData,
+ scoringMethods);
+ }
+
+ public static CreditScorecardData ruleBasedInstance(final Long id, final String scoringMethod, final String scoringModel,
+ final RuleBasedScorecardData ruleBasedScorecard) {
+ final MLScorecardData mlScorecardData = null;
+ final StatScorecardData statScorecardData = null;
+
+ final Collection<Map<String, Object>> scoringMethods = null;
+
+ return new CreditScorecardData(id, scoringMethod, scoringModel, mlScorecardData, statScorecardData, ruleBasedScorecard,
+ scoringMethods);
+ }
+
+ public static CreditScorecardData statInstance(Long id, String scoringMethod, String scoringModel,
+ StatScorecardData statScorecardData) {
+ final MLScorecardData mlScorecardData = null;
+ final RuleBasedScorecardData ruleBasedScorecardData = null;
+
+ final Collection<Map<String, Object>> scoringMethods = null;
+
+ return new CreditScorecardData(id, scoringMethod, scoringModel, mlScorecardData, statScorecardData, ruleBasedScorecardData,
+ scoringMethods);
+ }
+
+ public static CreditScorecardData mlInstance(final Long id, final String scoringMethod, final String scoringModel,
+ final MLScorecardData mlScorecard) {
+ final StatScorecardData statScorecardData = null;
+ final RuleBasedScorecardData ruleBasedScorecardData = null;
+
+ final Collection<Map<String, Object>> scoringMethods = null;
+
+ return new CreditScorecardData(id, scoringMethod, scoringModel, mlScorecard, statScorecardData, ruleBasedScorecardData,
+ scoringMethods);
+ }
+
+ public static CreditScorecardData loanTemplate() {
+ final Long id = null;
+ final String scoringMethod = null;
+ final String scoringModel = null;
+
+ final MLScorecardData mlScorecardData = MLScorecardData.template();
+ final StatScorecardData statScorecardData = StatScorecardData.template();
+ final RuleBasedScorecardData ruleBasedScorecardData = RuleBasedScorecardData.template();
+
+ final Collection<Map<String, Object>> scoringMethods = new ArrayList<>(
+ Arrays.asList(Map.of("code", "ruleBased", "value", "Rule Based"), Map.of("code", "stat", "value", "Statistical"),
+ Map.of("code", "ml", "value", "Machine Learning")));
+
+ return new CreditScorecardData(id, scoringMethod, scoringModel, mlScorecardData, statScorecardData, ruleBasedScorecardData,
+ scoringMethods);
+ }
+
+ public static CreditScorecardData loanScorecardWithTemplate(CreditScorecardData sc) {
+
+ final Collection<Map<String, Object>> scoringMethods = new ArrayList<>(
+ Arrays.asList(Map.of("code", "ruleBased", "value", "Rule Based"), Map.of("code", "stat", "value", "Statistical"),
+ Map.of("code", "ml", "value", "Machine Learning")));
+
+ final StatScorecardData statScorecardData = StatScorecardData.template();
+ final RuleBasedScorecardData ruleBasedScorecardData = RuleBasedScorecardData.template();
+
+ if (sc == null) {
+ return null;
+ }
+
+ return new CreditScorecardData(sc.id, sc.scoringMethod, sc.scoringModel, sc.mlScorecard, sc.statScorecard, sc.ruleBasedScorecard,
+ scoringMethods);
+ }
+
+ public Long getId() {
+ return id;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/CreditScorecardFeatureData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/CreditScorecardFeatureData.java
new file mode 100644
index 0000000..07788c0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/CreditScorecardFeatureData.java
@@ -0,0 +1,203 @@
+/**
+ * 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.creditscorecard.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.jetbrains.annotations.NotNull;
+
+public final class CreditScorecardFeatureData implements Comparable<CreditScorecardFeatureData>, Serializable {
+
+ private final Long id;
+ private final Long featureId;
+ private final String name;
+ private final EnumOptionData valueType;
+ private final EnumOptionData dataType;
+ private final EnumOptionData category;
+ private final Boolean active;
+
+ private final BigDecimal weightage;
+ private final Integer greenMin;
+ private final Integer greenMax;
+ private final Integer amberMin;
+ private final Integer amberMax;
+ private final Integer redMin;
+ private final Integer redMax;
+
+ private final Collection<ScorecardFeatureCriteriaData> criteria;
+
+ private final Collection<EnumOptionData> valueTypeOptions;
+ private final Collection<EnumOptionData> dataTypeOptions;
+ private final Collection<EnumOptionData> categoryOptions;
+
+ public CreditScorecardFeatureData(final Long id, final Long featureId, final String name, final EnumOptionData valueType,
+ final EnumOptionData dataType, final EnumOptionData category, final Boolean active, final BigDecimal weightage,
+ final Integer greenMin, final Integer greenMax, final Integer amberMin, final Integer amberMax, final Integer redMin,
+ final Integer redMax, final Collection<ScorecardFeatureCriteriaData> criteria,
+ final Collection<EnumOptionData> valueTypeOptions, final Collection<EnumOptionData> dataTypeOptions,
+ final Collection<EnumOptionData> categoryOptions) {
+ this.id = id;
+
+ this.featureId = featureId;
+ this.name = name;
+ this.valueType = valueType;
+ this.dataType = dataType;
+ this.category = category;
+ this.active = active;
+
+ this.weightage = weightage;
+ this.greenMin = greenMin;
+ this.greenMax = greenMax;
+ this.amberMin = amberMin;
+ this.amberMax = amberMax;
+ this.redMin = redMin;
+ this.redMax = redMax;
+
+ this.criteria = criteria;
+
+ this.valueTypeOptions = valueTypeOptions;
+ this.dataTypeOptions = dataTypeOptions;
+ this.categoryOptions = categoryOptions;
+ }
+
+ public static CreditScorecardFeatureData instance(final Long id, final Long featureId, final String name,
+ final EnumOptionData valueType, final EnumOptionData dataType, final EnumOptionData category, final Boolean active,
+ final BigDecimal weightage, final Integer greenMin, final Integer greenMax, final Integer amberMin, final Integer amberMax,
+ final Integer redMin, final Integer redMax) {
+
+ final Collection<ScorecardFeatureCriteriaData> criteria = new ArrayList<>();
+
+ final Collection<EnumOptionData> valueTypeOptions = null;
+ final Collection<EnumOptionData> dataTypeOptions = null;
+ final Collection<EnumOptionData> categoryOptions = null;
+
+ return new CreditScorecardFeatureData(id, featureId, name, valueType, dataType, category, active, weightage, greenMin, greenMax,
+ amberMin, amberMax, redMin, redMax, criteria, valueTypeOptions, dataTypeOptions, categoryOptions);
+ }
+
+ public static CreditScorecardFeatureData template(final Collection<EnumOptionData> valueTypeOptions,
+ final Collection<EnumOptionData> dataTypeOptions, final Collection<EnumOptionData> categoryOptions) {
+
+ final Long id = null;
+ final Long featureId = null;
+ final String name = null;
+ final EnumOptionData valueType = null;
+ final EnumOptionData dataType = null;
+ final EnumOptionData category = null;
+ final Boolean active = null;
+
+ final BigDecimal weightage = null;
+ final Integer greenMin = null;
+ final Integer greenMax = null;
+ final Integer amberMin = null;
+ final Integer amberMax = null;
+ final Integer redMin = null;
+ final Integer redMax = null;
+
+ final Collection<ScorecardFeatureCriteriaData> criteria = null;
+
+ return new CreditScorecardFeatureData(id, featureId, name, valueType, dataType, category, active, weightage, greenMin, greenMax,
+ amberMin, amberMax, redMin, redMax, criteria, valueTypeOptions, dataTypeOptions, categoryOptions);
+ }
+
+ public static CreditScorecardFeatureData withTemplate(CreditScorecardFeatureData scf, CreditScorecardFeatureData template) {
+
+ return new CreditScorecardFeatureData(scf.id, scf.featureId, scf.name, scf.valueType, scf.dataType, scf.category, scf.active,
+ scf.weightage, scf.greenMin, scf.greenMax, scf.amberMin, scf.amberMax, scf.redMin, scf.redMax, scf.criteria,
+ template.valueTypeOptions, template.dataTypeOptions, template.categoryOptions);
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public EnumOptionData getValueType() {
+ return valueType;
+ }
+
+ public EnumOptionData getDataType() {
+ return dataType;
+ }
+
+ public EnumOptionData getCategory() {
+ return category;
+ }
+
+ public Boolean getActive() {
+ return active;
+ }
+
+ public BigDecimal getWeightage() {
+ return weightage;
+ }
+
+ public Integer getGreenMin() {
+ return greenMin;
+ }
+
+ public Integer getGreenMax() {
+ return greenMax;
+ }
+
+ public Integer getAmberMin() {
+ return amberMin;
+ }
+
+ public Integer getAmberMax() {
+ return amberMax;
+ }
+
+ public Integer getRedMin() {
+ return redMin;
+ }
+
+ public Integer getRedMax() {
+ return redMax;
+ }
+
+ public Collection<ScorecardFeatureCriteriaData> getCriteria() {
+ return criteria;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof CreditScorecardFeatureData)) {
+ return false;
+ }
+ final CreditScorecardFeatureData creditScorecardFeatureData = (CreditScorecardFeatureData) obj;
+ return this.id.equals(creditScorecardFeatureData.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.id.hashCode();
+ }
+
+ @Override
+ public int compareTo(@NotNull CreditScorecardFeatureData obj) {
+ return obj.id.compareTo(this.id);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/FeatureCriteriaScoreData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/FeatureCriteriaScoreData.java
new file mode 100644
index 0000000..1ff799d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/FeatureCriteriaScoreData.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.creditscorecard.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import org.apache.fineract.portfolio.creditscorecard.domain.FeatureCriteriaScore;
+
+public final class FeatureCriteriaScoreData implements Serializable {
+
+ private final Long id;
+ private final String feature;
+ private final String value;
+ private final BigDecimal score;
+ private final String color;
+
+ private FeatureCriteriaScoreData(final Long id, final String feature, final String value, final BigDecimal score, final String color) {
+ this.id = id;
+ this.feature = feature;
+ this.value = value;
+ this.score = score;
+ this.color = color;
+ }
+
+ public static FeatureCriteriaScoreData instance(final FeatureCriteriaScore ctScore) {
+ final String feature = ctScore.getFeature().getScorecardFeature().getName();
+
+ return new FeatureCriteriaScoreData(ctScore.getId(), feature, ctScore.getValue(), ctScore.getScore(), ctScore.getColor());
+ }
+
+ public BigDecimal getScore() {
+ return score;
+ }
+
+ public String getColor() {
+ return color;
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/MLScorecardData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/MLScorecardData.java
new file mode 100644
index 0000000..9add0b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/MLScorecardData.java
@@ -0,0 +1,126 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.creditscorecard.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import org.apache.fineract.portfolio.creditscorecard.domain.MLScorecard;
+import org.apache.fineract.portfolio.creditscorecard.domain.MLScorecardFields;
+
+public class MLScorecardData implements Serializable {
+
+ private final Long id;
+ private final Integer age;
+ private final String sex;
+ private final String job;
+ private final String housing;
+ private final BigDecimal creditAmount;
+ private final Integer duration;
+ private final String purpose;
+
+ private final String risk;
+ private final BigDecimal accuracy;
+
+ private final Collection<Map<String, Object>> scoringModels;
+
+ private final Collection<Map<String, Object>> jobOptions;
+ private final Collection<Map<String, Object>> genderOptions;
+ private final Collection<Map<String, Object>> purposeOptions;
+ private final Collection<Map<String, Object>> housingOptions;
+
+ public MLScorecardData(Long id, Integer age, String sex, String job, String housing, BigDecimal creditAmount, Integer duration,
+ String purpose, String risk, BigDecimal accuracy, Collection<Map<String, Object>> scoringModels,
+ Collection<Map<String, Object>> jobOptions, Collection<Map<String, Object>> genderOptions,
+ Collection<Map<String, Object>> purposeOptions, Collection<Map<String, Object>> housingOptions) {
+ this.id = id;
+ this.age = age;
+ this.sex = sex;
+ this.job = job;
+ this.housing = housing;
+ this.creditAmount = creditAmount;
+ this.duration = duration;
+ this.purpose = purpose;
+
+ this.risk = risk;
+ this.accuracy = accuracy;
+
+ this.jobOptions = jobOptions;
+ this.genderOptions = genderOptions;
+ this.purposeOptions = purposeOptions;
+ this.housingOptions = housingOptions;
+ this.scoringModels = scoringModels;
+ }
+
+ public static MLScorecardData instance(final MLScorecard sc) {
+ final MLScorecardFields scf = sc.getScorecardFields();
+
+ final Collection<Map<String, Object>> scoringModels = null;
+
+ final Collection<Map<String, Object>> jobOptions = null;
+ final Collection<Map<String, Object>> genderOptions = null;
+ final Collection<Map<String, Object>> purposeOptions = null;
+ final Collection<Map<String, Object>> housingOptions = null;
+
+ return new MLScorecardData(sc.getId(), scf.getAge(), scf.getSex(), scf.getJob(), scf.getHousing(), scf.getCreditAmount(),
+ scf.getDuration(), scf.getPurpose(), sc.getPredictedRisk(), sc.getAccuracy(), scoringModels, jobOptions, genderOptions,
+ purposeOptions, housingOptions);
+ }
+
+ public static MLScorecardData template() {
+
+ final Long id = null;
+ final Integer age = null;
+ final String sex = null;
+ final String job = null;
+ final String housing = null;
+
+ final BigDecimal creditAmount = null;
+ final Integer duration = null;
+ final String purpose = null;
+
+ final String risk = null;
+ final BigDecimal accuracy = null;
+
+ final Collection<Map<String, Object>> scoringModels = new ArrayList<>(
+ Arrays.asList(Map.of("code", "RandomForestClassifier", "value", "Random Forest Classifier"),
+ Map.of("code", "GradientBoostClassifier", "value", "Gradient Boost Classifier"),
+ Map.of("code", "SVC", "value", "SVC"), Map.of("code", "MLP", "value", "MLP")));
+
+ final Collection<Map<String, Object>> jobOptions = new ArrayList<>(
+ Arrays.asList(Map.of("code", 2, "value", "Unemployed"), Map.of("code", 3, "value", "Self-employed")));
+
+ final Collection<Map<String, Object>> purposeOptions = new ArrayList<>(
+ Arrays.asList(Map.of("code", "radio/TV", "value", "Radio/TV"), Map.of("code", "repairs", "value", "Repairs"),
+ Map.of("code", "business", "value", "Business"), Map.of("code", "education", "value", "Education")));
+
+ final Collection<Map<String, Object>> housingOptions = new ArrayList<>(Arrays.asList(Map.of("code", "own", "value", "Own"),
+ Map.of("code", "free", "value", "Free"), Map.of("code", "rent", "value", "Rent")));
+
+ final Collection<Map<String, Object>> genderOptions = new ArrayList<>(
+ Arrays.asList(Map.of("code", "male", "value", "Male"), Map.of("code", "female", "value", "Female")));
+
+ return new MLScorecardData(id, age, sex, job, housing, creditAmount, duration, purpose, risk, accuracy, scoringModels, jobOptions,
+ genderOptions, purposeOptions, housingOptions);
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/RuleBasedScorecardData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/RuleBasedScorecardData.java
new file mode 100644
index 0000000..c4a10b7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/RuleBasedScorecardData.java
@@ -0,0 +1,71 @@
+/**
+ * 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.creditscorecard.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.fineract.portfolio.creditscorecard.domain.FeatureCriteriaScore;
+import org.apache.fineract.portfolio.creditscorecard.domain.RuleBasedScorecard;
+
+public final class RuleBasedScorecardData implements Serializable {
+
+ private final Long id;
+ private final Collection<FeatureCriteriaScoreData> criteriaScores;
+ private final BigDecimal overallScore;
+ private final String overallColor;
+
+ private final Collection<Map<String, Object>> scoringModels;
+
+ private RuleBasedScorecardData(final Long id, final Collection<FeatureCriteriaScoreData> criteriaScores, final BigDecimal overallScore,
+ final String overallColor, final Collection<Map<String, Object>> scoringModels) {
+ this.id = id;
+ this.criteriaScores = criteriaScores;
+ this.overallScore = overallScore;
+ this.overallColor = overallColor;
+ this.scoringModels = scoringModels;
+ }
+
+ public static RuleBasedScorecardData instance(RuleBasedScorecard rbs) {
+ final List<FeatureCriteriaScore> ctScores = rbs.getCriteriaScores();
+ final List<FeatureCriteriaScoreData> ctScoresData = ctScores.stream().map(FeatureCriteriaScoreData::instance)
+ .collect(Collectors.toList());
+
+ final Collection<Map<String, Object>> scoringModels = null;
+
+ return new RuleBasedScorecardData(rbs.getId(), ctScoresData, rbs.getOverallScore(), rbs.getOverallColor(), scoringModels);
+ }
+
+ public static RuleBasedScorecardData template() {
+ final Long id = null;
+ final Collection<FeatureCriteriaScoreData> criteriaScores = null;
+ final BigDecimal overallScore = null;
+ final String overallColor = null;
+
+ final Collection<Map<String, Object>> scoringModels = new ArrayList<>(
+ Arrays.asList(Map.of("code", "ruleBased", "value", "Rule Based")));
+
+ return new RuleBasedScorecardData(id, criteriaScores, overallScore, overallColor, scoringModels);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/ScorecardFeatureCriteriaData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/ScorecardFeatureCriteriaData.java
new file mode 100644
index 0000000..bc94a3b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/ScorecardFeatureCriteriaData.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.creditscorecard.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+public final class ScorecardFeatureCriteriaData implements Serializable {
+
+ private final Long id;
+ private final String criteria;
+ private final BigDecimal score;
+
+ private ScorecardFeatureCriteriaData(Long id, String criteria, BigDecimal score) {
+ this.id = id;
+ this.criteria = criteria;
+ this.score = score;
+ }
+
+ public static ScorecardFeatureCriteriaData instance(final Long id, final String criteria, final BigDecimal score) {
+ return new ScorecardFeatureCriteriaData(id, criteria, score);
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/StatScorecardData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/StatScorecardData.java
new file mode 100644
index 0000000..e600246
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/data/StatScorecardData.java
@@ -0,0 +1,117 @@
+/**
+ * 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.creditscorecard.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import org.apache.fineract.portfolio.creditscorecard.domain.MLScorecardFields;
+import org.apache.fineract.portfolio.creditscorecard.domain.StatScorecard;
+
+public final class StatScorecardData implements Serializable {
+
+ private final Long id;
+ private final Integer age;
+ private final String sex;
+ private final String job;
+ private final String housing;
+ private final BigDecimal creditAmount;
+ private final Integer duration;
+ private final String purpose;
+
+ private final String method;
+
+ private final String color;
+ private final BigDecimal prediction;
+
+ private final BigDecimal wilkisLambda;
+ private final BigDecimal pillaisTrace;
+ private final BigDecimal hotellingLawley;
+ private final BigDecimal roysGreatestRoots;
+
+ private final Collection<Map<String, Object>> scoringModels;
+
+ public StatScorecardData(Long id, Integer age, String sex, String job, String housing, BigDecimal creditAmount, Integer duration,
+ String purpose, String method, String color, BigDecimal prediction, BigDecimal wilkisLambda, BigDecimal pillaisTrace,
+ BigDecimal hotellingLawley, BigDecimal roysGreatestRoots, Collection<Map<String, Object>> scoringModels) {
+ this.id = id;
+ this.age = age;
+ this.sex = sex;
+ this.job = job;
+ this.housing = housing;
+ this.creditAmount = creditAmount;
+ this.duration = duration;
+ this.purpose = purpose;
+
+ this.method = method;
+
+ this.color = color;
+ this.prediction = prediction;
+
+ this.wilkisLambda = wilkisLambda;
+ this.pillaisTrace = pillaisTrace;
+ this.hotellingLawley = hotellingLawley;
+ this.roysGreatestRoots = roysGreatestRoots;
+
+ this.scoringModels = scoringModels;
+ }
+
+ public static StatScorecardData instance(final StatScorecard sc) {
+ final MLScorecardFields scf = sc.getScorecardFields();
+
+ final Collection<Map<String, Object>> scoringModels = null;
+
+ return new StatScorecardData(sc.getId(), scf.getAge(), scf.getSex(), scf.getJob(), scf.getHousing(), scf.getCreditAmount(),
+ scf.getDuration(), scf.getPurpose(), sc.getMethod(), sc.getColor(), sc.getPrediction(), sc.getWilkisLambda(),
+ sc.getPillaisTrace(), sc.getHotellingLawley(), sc.getRoysGreatestRoots(), scoringModels);
+ }
+
+ public static StatScorecardData template() {
+
+ final Long id = null;
+ final Integer age = null;
+ final String sex = null;
+ final String job = null;
+ final String housing = null;
+
+ final BigDecimal creditAmount = null;
+ final Integer duration = null;
+ final String purpose = null;
+
+ final String method = null;
+
+ final String color = null;
+ final BigDecimal prediction = null;
+
+ final BigDecimal wilkisLambda = null;
+ final BigDecimal pillaisTrace = null;
+ final BigDecimal hotellingLawley = null;
+ final BigDecimal roysGreatestRoots = null;
+
+ final Collection<Map<String, Object>> scoringModels = new ArrayList<>(
+ Arrays.asList(Map.of("code", "manova", "value", "Manova"), Map.of("code", "linearRegression", "value", "Linear Regression"),
+ Map.of("code", "polynomialRegression", "value", "Polynomial Regression")));
+
+ return new StatScorecardData(id, age, sex, job, housing, creditAmount, duration, purpose, method, color, prediction, wilkisLambda,
+ pillaisTrace, hotellingLawley, roysGreatestRoots, scoringModels);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/CreditScorecard.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/CreditScorecard.java
new file mode 100644
index 0000000..b743cc0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/CreditScorecard.java
@@ -0,0 +1,114 @@
+/**
+ * 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.creditscorecard.domain;
+
+import java.util.Objects;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+
+@Entity
+@Table(name = "m_credit_scorecard")
+public class CreditScorecard extends AbstractPersistableCustom {
+
+ @Column(name = "scorecard_scoring_method")
+ private String scoringMethod;
+
+ @Column(name = "scorecard_scoring_model")
+ private String scoringModel;
+
+ @OneToOne(cascade = CascadeType.ALL)
+ @JoinColumn(name = "rule_based_scorecard_id", referencedColumnName = "id")
+ private RuleBasedScorecard ruleBasedScorecard;
+
+ @OneToOne(cascade = CascadeType.ALL)
+ @JoinColumn(name = "stat_scorecard_id", referencedColumnName = "id")
+ private StatScorecard statScorecard;
+
+ @OneToOne(cascade = CascadeType.ALL)
+ @JoinColumn(name = "ml_scorecard_id", referencedColumnName = "id")
+ private MLScorecard mlScorecard;
+
+ public CreditScorecard() {
+ //
+ }
+
+ public CreditScorecard(String scoringMethod, String scoringModel) {
+ this.scoringMethod = scoringMethod;
+ this.scoringModel = scoringModel;
+ }
+
+ public CreditScorecard(String scoringMethod, String scoringModel, RuleBasedScorecard ruleBasedScorecard, StatScorecard statScorecard,
+ MLScorecard mlScorecard) {
+ this.scoringMethod = scoringMethod;
+ this.scoringModel = scoringModel;
+ this.ruleBasedScorecard = ruleBasedScorecard;
+ this.statScorecard = statScorecard;
+ this.mlScorecard = mlScorecard;
+ }
+
+ public String getScoringMethod() {
+ return scoringMethod;
+ }
+
+ public String getScoringModel() {
+ return scoringModel;
+ }
+
+ public RuleBasedScorecard getRuleBasedScorecard() {
+ return ruleBasedScorecard;
+ }
+
+ public StatScorecard getStatScorecard() {
+ return statScorecard;
+ }
+
+ public MLScorecard getMlScorecard() {
+ return mlScorecard;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof CreditScorecard)) {
+ return false;
+ }
+ CreditScorecard that = (CreditScorecard) o;
+ return Objects.equals(scoringMethod, that.scoringMethod) && Objects.equals(scoringModel, that.scoringModel)
+ && Objects.equals(ruleBasedScorecard, that.ruleBasedScorecard) && Objects.equals(statScorecard, that.statScorecard)
+ && Objects.equals(mlScorecard, that.mlScorecard);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(scoringMethod, scoringModel, ruleBasedScorecard, statScorecard, mlScorecard);
+ }
+
+ @Override
+ public String toString() {
+ return "CreditScorecard{" + "scoringMethod='" + scoringMethod + '\'' + ", scoringModel='" + scoringModel + '\''
+ + ", ruleBasedScorecard=" + ruleBasedScorecard + ", statScorecard=" + statScorecard + ", mlScorecard=" + mlScorecard + '}';
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/CreditScorecardFeature.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/CreditScorecardFeature.java
new file mode 100644
index 0000000..2cdb705
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/CreditScorecardFeature.java
@@ -0,0 +1,178 @@
+/**
+ * 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.creditscorecard.domain;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardFeatureData;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardEnumerations;
+
+@Entity
+@Table(name = "m_credit_scorecard_feature")
+public class CreditScorecardFeature extends AbstractPersistableCustom implements Serializable {
+
+ @Column(name = "name", length = 100, unique = true, nullable = false)
+ private String name;
+
+ @Column(name = "value_type_enum", nullable = false)
+ private Integer valueType;
+
+ @Column(name = "data_type_enum", nullable = false)
+ private Integer dataType;
+
+ @Column(name = "category_enum", nullable = false)
+ private Integer category;
+
+ @Column(name = "is_active", nullable = false)
+ private Boolean active = false;
+
+ @Column(name = "is_deleted", nullable = false)
+ private boolean deleted = false;
+
+ public CreditScorecardFeature() {
+ //
+ }
+
+ public CreditScorecardFeature(String name, org.apache.fineract.portfolio.creditscorecard.domain.FeatureValueType valueType,
+ org.apache.fineract.portfolio.creditscorecard.domain.FeatureDataType dataType,
+ org.apache.fineract.portfolio.creditscorecard.domain.FeatureCategory category, Boolean active) {
+ this.name = name;
+ this.valueType = valueType.getValue();
+ this.dataType = dataType.getValue();
+ this.category = category.getValue();
+ this.active = active;
+ }
+
+ public static CreditScorecardFeature fromJson(JsonCommand command) {
+ final String name = command.stringValueOfParameterNamed("name");
+ final org.apache.fineract.portfolio.creditscorecard.domain.FeatureValueType valueType = org.apache.fineract.portfolio.creditscorecard.domain.FeatureValueType
+ .fromInt(command.integerValueOfParameterNamed("valueType"));
+ final org.apache.fineract.portfolio.creditscorecard.domain.FeatureDataType dataType = org.apache.fineract.portfolio.creditscorecard.domain.FeatureDataType
+ .fromInt(command.integerValueOfParameterNamed("dataType"));
+ final org.apache.fineract.portfolio.creditscorecard.domain.FeatureCategory category = org.apache.fineract.portfolio.creditscorecard.domain.FeatureCategory
+ .fromInt(command.integerValueOfParameterNamed("category"));
+ final boolean active = command.booleanPrimitiveValueOfParameterNamed("active");
+
+ return new CreditScorecardFeature(name, valueType, dataType, category, active);
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public EnumOptionData getValueType() {
+ return CreditScorecardEnumerations.featureValueType(valueType);
+ }
+
+ public EnumOptionData getDataType() {
+ return CreditScorecardEnumerations.featureDataType(dataType);
+ }
+
+ public EnumOptionData getCategory() {
+ return CreditScorecardEnumerations.featureCategory(category);
+ }
+
+ public boolean isActive() {
+ return this.active;
+ }
+
+ public boolean isDeleted() {
+ return this.deleted;
+ }
+
+ /**
+ * Delete is a <i>soft delete</i>. Updates flag on feature so it wont appear in query/report results.
+ *
+ * Any fields with unique constraints and prepended with id of record.
+ */
+ public void delete() {
+ this.deleted = true;
+ this.name = getId() + "_" + this.name;
+ }
+
+ public CreditScorecardFeatureData toData() {
+ final Long id = null;
+ final Long featureId = getId();
+ final String name = this.name;
+ final EnumOptionData valueType = CreditScorecardEnumerations.featureValueType(this.valueType);
+ final EnumOptionData dataType = CreditScorecardEnumerations.featureDataType(this.dataType);
+ final EnumOptionData category = CreditScorecardEnumerations.featureCategory(this.category);
+ final Boolean active = this.active;
+ final BigDecimal weightage = null;
+ final Integer greenMin = null;
+ final Integer greenMax = null;
+ final Integer amberMin = null;
+ final Integer amberMax = null;
+ final Integer redMin = null;
+ final Integer redMax = null;
+
+ return CreditScorecardFeatureData.instance(id, featureId, name, valueType, dataType, category, active, weightage, greenMin,
+ greenMax, amberMin, amberMax, redMin, redMax);
+ }
+
+ public Map<String, Object> update(final JsonCommand command) {
+ final Map<String, Object> actualChanges = new LinkedHashMap<>(6);
+
+ final String nameParamName = "name";
+ if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
+ final String newValue = command.stringValueOfParameterNamed(nameParamName);
+ actualChanges.put(nameParamName, newValue);
+ this.name = newValue;
+ }
+
+ final String valueTypeParamName = "valueType";
+ if (command.isChangeInIntegerParameterNamed(valueTypeParamName, this.valueType)) {
+ final Integer newValue = command.integerValueOfParameterNamed(valueTypeParamName);
+ actualChanges.put(valueTypeParamName, newValue);
+ this.valueType = newValue;
+ }
+
+ final String dataTypeParamName = "dataType";
+ if (command.isChangeInIntegerParameterNamed(dataTypeParamName, this.dataType)) {
+ final Integer newValue = command.integerValueOfParameterNamed(dataTypeParamName);
+ actualChanges.put(dataTypeParamName, newValue);
+ this.dataType = newValue;
+ }
+
+ final String categoryParamName = "category";
+ if (command.isChangeInIntegerParameterNamed(categoryParamName, this.category)) {
+ final Integer newValue = command.integerValueOfParameterNamed(categoryParamName);
+ actualChanges.put(categoryParamName, newValue);
+ this.category = newValue;
+ }
+
+ final String activeParamName = "active";
+ if (command.isChangeInBooleanParameterNamed(activeParamName, this.active)) {
+ final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(activeParamName);
+ actualChanges.put(activeParamName, newValue);
+ this.active = newValue;
+ }
+
+ return actualChanges;
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/CreditScorecardFeatureRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/CreditScorecardFeatureRepository.java
new file mode 100644
index 0000000..243bbaa
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/CreditScorecardFeatureRepository.java
@@ -0,0 +1,25 @@
+/**
+ * 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.creditscorecard.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface CreditScorecardFeatureRepository
+ extends JpaRepository<CreditScorecardFeature, Long>, JpaSpecificationExecutor<CreditScorecardFeature> {}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/CreditScorecardRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/CreditScorecardRepository.java
new file mode 100644
index 0000000..afc379d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/CreditScorecardRepository.java
@@ -0,0 +1,26 @@
+/**
+ * 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.creditscorecard.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface CreditScorecardRepository extends JpaRepository<CreditScorecard, Long>, JpaSpecificationExecutor<CreditScorecard> {}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureCannotBeDeletedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureCannotBeDeletedException.java
new file mode 100644
index 0000000..9366159
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureCannotBeDeletedException.java
@@ -0,0 +1,31 @@
+/**
+ * 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.creditscorecard.domain;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class FeatureCannotBeDeletedException extends AbstractPlatformDomainRuleException {
+
+ public FeatureCannotBeDeletedException(final String globalisationMessageCode, final String defaultUserMessage,
+ final Object... defaultUserMessageArgs) {
+
+ super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureCategory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureCategory.java
new file mode 100644
index 0000000..f76add9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureCategory.java
@@ -0,0 +1,108 @@
+/**
+ * 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.creditscorecard.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public enum FeatureCategory {
+
+ INDIVIDUAL(0, "featureCategory.individual"), ORGANISATION(1, "featureCategory.organisation"), COUNTRY(2,
+ "featureCategory.country"), CREDIT_HISTORY(3,
+ "featureCategory.creditHistory"), LOAN(3, "featureCategory.loan"), INVALID(4, "featureCategory.invalid");
+
+ private final Integer value;
+ private final String code;
+
+ FeatureCategory(final Integer value, final String code) {
+ this.value = value;
+ this.code = code;
+ }
+
+ public Integer getValue() {
+ return this.value;
+ }
+
+ public String getCode() {
+ return this.code;
+ }
+
+ public static FeatureCategory fromInt(final Integer selectedType) {
+ FeatureCategory featureCategory = INVALID;
+ if (selectedType != null) {
+ switch (selectedType) {
+ case 0:
+ featureCategory = INDIVIDUAL;
+ break;
+ case 1:
+ featureCategory = ORGANISATION;
+ break;
+ case 2:
+ featureCategory = COUNTRY;
+ break;
+ case 3:
+ featureCategory = CREDIT_HISTORY;
+ break;
+ case 4:
+ featureCategory = LOAN;
+ break;
+ }
+ }
+ return featureCategory;
+ }
+
+ public boolean isIndividual() {
+ return this.value.equals(INDIVIDUAL.getValue());
+ }
+
+ public boolean isOrganisation() {
+ return this.value.equals(ORGANISATION.getValue());
+ }
+
+ public boolean isCountry() {
+ return this.value.equals(COUNTRY.getValue());
+ }
+
+ public boolean isCreditHistory() {
+ return this.value.equals(CREDIT_HISTORY.getValue());
+ }
+
+ public boolean isLoan() {
+ return this.value.equals(LOAN.getValue());
+ }
+
+ public boolean isInvalid() {
+ return this.value.equals(INVALID.getValue());
+ }
+
+ public static Object[] integerValues() {
+ final List<Integer> values = new ArrayList<>();
+ for (final FeatureCategory enumType : values()) {
+ if (!enumType.isInvalid()) {
+ values.add(enumType.getValue());
+ }
+ }
+ return values.toArray();
+ }
+
+ public static Object[] validValues() {
+ return new Integer[] { INDIVIDUAL.getValue(), ORGANISATION.getValue(), COUNTRY.getValue(), CREDIT_HISTORY.getValue(),
+ LOAN.getValue() };
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureConfiguration.java
new file mode 100644
index 0000000..d7f3564
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureConfiguration.java
@@ -0,0 +1,184 @@
+/**
+ * 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.creditscorecard.domain;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Locale;
+import java.util.Map;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+
+@Embeddable
+public class FeatureConfiguration implements Serializable {
+
+ @Column(name = "weightage", scale = 6, precision = 5, nullable = false)
+ private BigDecimal weightage;
+
+ @Column(name = "green_min", nullable = false)
+ private Integer greenMin;
+
+ @Column(name = "green_max", nullable = false)
+ private Integer greenMax;
+
+ @Column(name = "amber_min", nullable = false)
+ private Integer amberMin;
+
+ @Column(name = "amber_max", nullable = false)
+ private Integer amberMax;
+
+ @Column(name = "red_min", nullable = false)
+ private Integer redMin;
+
+ @Column(name = "red_max", nullable = false)
+ private Integer redMax;
+
+ protected FeatureConfiguration() {
+ //
+ }
+
+ public FeatureConfiguration(final BigDecimal weightage, final Integer greenMin, final Integer greenMax, final Integer amberMin,
+ final Integer amberMax, final Integer redMin, final Integer redMax) {
+ this.weightage = weightage;
+ this.greenMin = greenMin;
+ this.greenMax = greenMax;
+ this.amberMin = amberMin;
+ this.amberMax = amberMax;
+ this.redMin = redMin;
+ this.redMax = redMax;
+ }
+
+ public static FeatureConfiguration from(final BigDecimal weightage, final Integer greenMin, final Integer greenMax,
+ final Integer amberMin, final Integer amberMax, final Integer redMin, final Integer redMax) {
+ return new FeatureConfiguration(weightage, greenMin, greenMax, amberMin, amberMax, redMin, redMax);
+ }
+
+ public void update(final JsonCommand command, final Map<String, Object> actualChanges, final DataValidatorBuilder baseDataValidator,
+ final Locale locale) {
+
+ validateConfiguration(baseDataValidator);
+
+ if (command.isChangeInBigDecimalParameterNamed("weightage", this.weightage, locale)) {
+ final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("weightage", locale);
+ actualChanges.put("weightage", newValue);
+ this.weightage = newValue;
+ }
+
+ if (command.isChangeInIntegerParameterNamed("greenMin", this.greenMin, locale)) {
+ final Integer newValue = command.integerValueOfParameterNamed("greenMin", locale);
+ actualChanges.put("greenMin", newValue);
+ this.greenMin = newValue;
+ }
+
+ if (command.isChangeInIntegerParameterNamed("greenMax", this.greenMax, locale)) {
+ final Integer newValue = command.integerValueOfParameterNamed("greenMax", locale);
+ actualChanges.put("greenMax", newValue);
+ this.greenMax = newValue;
+ }
+
+ if (command.isChangeInIntegerParameterNamed("amberMin", this.amberMin, locale)) {
+ final Integer newValue = command.integerValueOfParameterNamed("amberMin", locale);
+ actualChanges.put("amberMin", newValue);
+ this.amberMin = newValue;
+ }
+
+ if (command.isChangeInIntegerParameterNamed("amberMax", this.amberMax, locale)) {
+ final Integer newValue = command.integerValueOfParameterNamed("amberMax", locale);
+ actualChanges.put("amberMax", newValue);
+ this.amberMax = newValue;
+ }
+
+ if (command.isChangeInIntegerParameterNamed("redMin", this.greenMin, locale)) {
+ final Integer newValue = command.integerValueOfParameterNamed("redMin", locale);
+ actualChanges.put("redMin", newValue);
+ this.redMin = newValue;
+ }
+
+ if (command.isChangeInIntegerParameterNamed("redMax", this.greenMin, locale)) {
+ final Integer newValue = command.integerValueOfParameterNamed("redMax", locale);
+ actualChanges.put("redMax", newValue);
+ this.redMax = newValue;
+ }
+
+ }
+
+ public void validateConfiguration(final DataValidatorBuilder baseDataValidator) {
+
+ baseDataValidator.reset().parameter("weightage").value(this.weightage).notNull().inMinMaxRange(0, 1);
+
+ baseDataValidator.reset().parameter("greenMin").value(this.greenMin).notNull().integerGreaterThanZero();
+ baseDataValidator.reset().parameter("greenMax").value(this.greenMax).notNull().integerGreaterThanZero();
+
+ baseDataValidator.reset().parameter("amberMin").value(this.amberMin).notNull().integerGreaterThanZero();
+ baseDataValidator.reset().parameter("amberMax").value(this.amberMax).notNull().integerGreaterThanZero();
+
+ baseDataValidator.reset().parameter("redMin").value(this.redMin).notNull().integerGreaterThanZero();
+ baseDataValidator.reset().parameter("redMax").value(this.redMax).notNull().integerGreaterThanZero();
+
+ }
+
+ public String getColorFromScore(final BigDecimal score) {
+ String color;
+ if (score.longValue() >= this.greenMin.longValue() && score.longValue() <= this.greenMax.longValue()) {
+ color = "green";
+
+ } else if (score.longValue() >= this.amberMin.longValue() && score.longValue() <= this.amberMax.longValue()) {
+ color = "amber";
+
+ } else if (score.longValue() >= this.redMin.longValue() && score.longValue() <= this.redMax.longValue()) {
+ color = "red";
+
+ } else {
+ color = "orange";
+
+ }
+
+ return color;
+ }
+
+ public BigDecimal getWeightage() {
+ return weightage;
+ }
+
+ public Integer getGreenMin() {
+ return greenMin;
+ }
+
+ public Integer getGreenMax() {
+ return greenMax;
+ }
+
+ public Integer getAmberMin() {
+ return amberMin;
+ }
+
+ public Integer getAmberMax() {
+ return amberMax;
+ }
+
+ public Integer getRedMin() {
+ return redMin;
+ }
+
+ public Integer getRedMax() {
+ return redMax;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureCriteria.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureCriteria.java
new file mode 100644
index 0000000..1bb4f7e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureCriteria.java
@@ -0,0 +1,89 @@
+/**
+ * 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.creditscorecard.domain;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+import org.apache.fineract.portfolio.creditscorecard.data.ScorecardFeatureCriteriaData;
+
+@Entity
+@Table(name = "m_scorecard_feature_criteria")
+public class FeatureCriteria extends AbstractPersistableCustom {
+
+ @Column(name = "criteria", nullable = false)
+ private String criteria;
+
+ @Column(name = "score", nullable = false)
+ private BigDecimal score;
+
+ public FeatureCriteria() {
+ //
+ }
+
+ public FeatureCriteria(String criteria, BigDecimal score) {
+ this.criteria = criteria;
+ this.score = score;
+ }
+
+ public String getCriteria() {
+ return criteria;
+ }
+
+ public void setCriteria(String criteria) {
+ this.criteria = criteria;
+ }
+
+ public BigDecimal getScore() {
+ return score;
+ }
+
+ public void setScore(BigDecimal score) {
+ this.score = score;
+ }
+
+ public ScorecardFeatureCriteriaData toData() {
+ return ScorecardFeatureCriteriaData.instance(this.getId(), this.criteria, this.score);
+ }
+
+ @Override
+ public String toString() {
+ return "ScorecardFeatureCriteria{" + "criteria='" + criteria + '\'' + ", score=" + score + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof FeatureCriteria)) {
+ return false;
+ }
+ FeatureCriteria that = (FeatureCriteria) o;
+ return Objects.equals(criteria, that.criteria) && Objects.equals(score, that.score);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(criteria, score);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureCriteriaScore.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureCriteriaScore.java
new file mode 100644
index 0000000..c6a0519
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureCriteriaScore.java
@@ -0,0 +1,108 @@
+/**
+ * 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.creditscorecard.domain;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductScorecardFeature;
+
+@Entity
+@Table(name = "m_scorecard_feature_criteria_score")
+public class FeatureCriteriaScore extends AbstractPersistableCustom {
+
+ @OneToOne(cascade = CascadeType.ALL)
+ @JoinColumn(name = "product_loan_scorecard_feature_id", referencedColumnName = "id")
+ private LoanProductScorecardFeature feature;
+
+ @Column(name = "value", nullable = false)
+ private String value;
+
+ @Column(name = "score")
+ private BigDecimal score;
+
+ @Column(name = "color")
+ private String color;
+
+ public FeatureCriteriaScore() {
+ //
+ }
+
+ public FeatureCriteriaScore(LoanProductScorecardFeature feature, String value) {
+ this.feature = feature;
+ this.value = value;
+ }
+
+ public FeatureCriteriaScore(LoanProductScorecardFeature feature, BigDecimal score, String color) {
+ this.feature = feature;
+ this.score = score;
+ this.color = color;
+ }
+
+ public LoanProductScorecardFeature getFeature() {
+ return feature;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public BigDecimal getScore() {
+ return score;
+ }
+
+ public String getColor() {
+ return color;
+ }
+
+ public void setScore(final BigDecimal score, final String color) {
+ this.score = score;
+ this.color = color;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof FeatureCriteriaScore)) {
+ return false;
+ }
+ FeatureCriteriaScore that = (FeatureCriteriaScore) o;
+ return Objects.equals(feature, that.feature) && Objects.equals(value, that.value) && Objects.equals(score, that.score)
+ && Objects.equals(color, that.color);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(feature, value, score, color);
+ }
+
+ @Override
+ public String toString() {
+ return "FeatureCriteriaScore{" + "feature=" + feature + ", value='" + value + '\'' + ", score=" + score + ", color='" + color + '\''
+ + '}';
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureDataType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureDataType.java
new file mode 100644
index 0000000..cdeb935
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureDataType.java
@@ -0,0 +1,92 @@
+/**
+ * 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.creditscorecard.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public enum FeatureDataType {
+
+ NUMERIC(0, "featureDataType.numeric"), STRING(1, "featureDataType.string"), DATE(2, "featureDataType.date"), INVALID(3,
+ "featureDataType.invalid");
+
+ private final Integer value;
+ private final String code;
+
+ FeatureDataType(final Integer value, final String code) {
+ this.value = value;
+ this.code = code;
+ }
+
+ public Integer getValue() {
+ return this.value;
+ }
+
+ public String getCode() {
+ return this.code;
+ }
+
+ public static FeatureDataType fromInt(final Integer selectedType) {
+ FeatureDataType featureDataType = INVALID;
+ if (selectedType != null) {
+ switch (selectedType) {
+ case 0:
+ featureDataType = NUMERIC;
+ break;
+ case 1:
+ featureDataType = STRING;
+ break;
+ case 2:
+ featureDataType = DATE;
+ break;
+ }
+ }
+ return featureDataType;
+ }
+
+ public boolean isNumeric() {
+ return this.value.equals(NUMERIC.getValue());
+ }
+
+ public boolean isString() {
+ return this.value.equals(STRING.getValue());
+ }
+
+ public boolean isDate() {
+ return this.value.equals(DATE.getValue());
+ }
+
+ public boolean isInvalid() {
+ return this.value.equals(INVALID.getValue());
+ }
+
+ public static Object[] integerValues() {
+ final List<Integer> values = new ArrayList<>();
+ for (final FeatureDataType enumType : values()) {
+ if (!enumType.isInvalid()) {
+ values.add(enumType.getValue());
+ }
+ }
+ return values.toArray();
+ }
+
+ public static Object[] validValues() {
+ return new Integer[] { NUMERIC.getValue(), STRING.getValue(), DATE.getValue() };
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureNotFoundException.java
new file mode 100644
index 0000000..5844ca8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureNotFoundException.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.creditscorecard.domain;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+import org.springframework.dao.EmptyResultDataAccessException;
+
+public class FeatureNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+ public FeatureNotFoundException(final Long id) {
+ super("error.msg.scorecard.feature.id.invalid", "Scorecard Feature with identifier " + id + " does not exist", id);
+ }
+
+ public FeatureNotFoundException(Long id, EmptyResultDataAccessException e) {
+ super("error.msg.scorecard.feature.id.invalid", "Scorecard Feature with identifier " + id + " does not exist", id, e);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureValueType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureValueType.java
new file mode 100644
index 0000000..0dc4f32
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/FeatureValueType.java
@@ -0,0 +1,100 @@
+/**
+ * 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.creditscorecard.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public enum FeatureValueType {
+
+ BINARY(0, "featureValueType.binary"), NOMINAL(1, "featureValueType.nominal"), INTERVAL(2, "featureValueType.interval"), RATIO(3,
+ "featureValueType.ratio"), INVALID(4, "featureValueType.invalid");
+
+ private final Integer value;
+ private final String code;
+
+ FeatureValueType(final Integer value, final String code) {
+ this.value = value;
+ this.code = code;
+ }
+
+ public Integer getValue() {
+ return this.value;
+ }
+
+ public String getCode() {
+ return this.code;
+ }
+
+ public static FeatureValueType fromInt(final Integer selectedType) {
+ FeatureValueType featureValueType = INVALID;
+ if (selectedType != null) {
+ switch (selectedType) {
+ case 0:
+ featureValueType = BINARY;
+ break;
+ case 1:
+ featureValueType = NOMINAL;
+ break;
+ case 2:
+ featureValueType = INTERVAL;
+ break;
+ case 3:
+ featureValueType = RATIO;
+ break;
+ }
+ }
+ return featureValueType;
+ }
+
+ public boolean isBinary() {
+ return this.value.equals(BINARY.getValue());
+ }
+
+ public boolean isNominal() {
+ return this.value.equals(NOMINAL.getValue());
+ }
+
+ public boolean isInterval() {
+ return this.value.equals(INTERVAL.getValue());
+ }
+
+ public boolean isRatio() {
+ return this.value.equals(RATIO.getValue());
+ }
+
+ public boolean isInvalid() {
+ return this.value.equals(INVALID.getValue());
+ }
+
+ public static Object[] integerValues() {
+ final List<Integer> values = new ArrayList<>();
+ for (final FeatureValueType enumType : values()) {
+ if (!enumType.isInvalid()) {
+ values.add(enumType.getValue());
+ }
+ }
+ return values.toArray();
+ }
+
+ public static Object[] validValues() {
+ return new Integer[] { BINARY.getValue(), NOMINAL.getValue(), INTERVAL.getValue(), RATIO.getValue() };
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/MLScorecard.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/MLScorecard.java
new file mode 100644
index 0000000..c32ccee
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/MLScorecard.java
@@ -0,0 +1,136 @@
+/**
+ * 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.creditscorecard.domain;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+
+@Entity
+@Table(name = "m_ml_scorecard")
+public class MLScorecard extends AbstractPersistableCustom {
+
+ @Embedded
+ private MLScorecardFields scorecardFields;
+
+ @Column(name = "predicted_risk")
+ private String predictedRisk;
+
+ @Column(name = "accuracy")
+ private BigDecimal accuracy;
+
+ @Column(name = "actual_risk")
+ private String actualRisk;
+
+ @Column(name = "prediction_request_id")
+ private Integer predictionRequestId;
+
+ public MLScorecard() {
+
+ }
+
+ public MLScorecard(final MLScorecardFields scorecardFields, final String predictedRisk, final String actualRisk,
+ final Integer predictionRequestId) {
+ this.scorecardFields = scorecardFields;
+ this.predictedRisk = predictedRisk;
+ this.actualRisk = actualRisk;
+ this.predictionRequestId = predictionRequestId;
+ }
+
+ public MLScorecard(final MLScorecardFields mlScorecardFields) {
+ this.scorecardFields = mlScorecardFields;
+ }
+
+ public MLScorecard scorecardFields(final MLScorecardFields scorecardFields) {
+ this.scorecardFields = scorecardFields;
+ return this;
+ }
+
+ public MLScorecardFields getScorecardFields() {
+ return scorecardFields;
+ }
+
+ public void setScorecardFields(MLScorecardFields scorecardFields) {
+ this.scorecardFields = scorecardFields;
+ }
+
+ public BigDecimal getAccuracy() {
+ return accuracy;
+ }
+
+ public String getPredictedRisk() {
+ return predictedRisk;
+ }
+
+ public void setPredictedRisk(final String predictedRisk) {
+ this.predictedRisk = predictedRisk;
+ }
+
+ public String getActualRisk() {
+ return actualRisk;
+ }
+
+ public void setActualRisk(final String actualRisk) {
+ this.actualRisk = actualRisk;
+ }
+
+ public Integer getPredictionRequestId() {
+ return predictionRequestId;
+ }
+
+ public void setPredictionRequestId(final Integer predictionRequestId) {
+ this.predictionRequestId = predictionRequestId;
+ }
+
+ public MLScorecard setPredictionResponse(BigDecimal accuracy, String predictedRisk, Integer predictionRequestId) {
+ this.accuracy = accuracy;
+ this.predictedRisk = predictedRisk;
+ this.predictionRequestId = predictionRequestId;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof MLScorecard)) {
+ return false;
+ }
+ MLScorecard loanScorecard = (MLScorecard) o;
+ return Objects.equals(scorecardFields, loanScorecard.scorecardFields) && Objects.equals(predictedRisk, loanScorecard.predictedRisk)
+ && Objects.equals(actualRisk, loanScorecard.actualRisk)
+ && Objects.equals(predictionRequestId, loanScorecard.predictionRequestId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(scorecardFields, predictedRisk, actualRisk, predictionRequestId);
+ }
+
+ @Override
+ public String toString() {
+ return "MLScorecard{" + "scorecardFields=" + scorecardFields + ", predictedRisk='" + predictedRisk + '\'' + ", actualRisk='"
+ + actualRisk + '\'' + ", predictionRequestId=" + predictionRequestId + '}';
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/MLScorecardFields.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/MLScorecardFields.java
new file mode 100644
index 0000000..e005d86
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/MLScorecardFields.java
@@ -0,0 +1,146 @@
+/**
+ * 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.creditscorecard.domain;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Objects;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+@Embeddable
+public class MLScorecardFields implements Serializable {
+
+ @Column(name = "age")
+ private Integer age;
+
+ @Column(name = "sex")
+ private String sex;
+
+ @Column(name = "job")
+ private String job;
+
+ @Column(name = "housing")
+ private String housing;
+
+ @Column(name = "credit_amount", scale = 6, precision = 19)
+ private BigDecimal creditAmount;
+
+ @Column(name = "duration")
+ private Integer duration;
+
+ @Column(name = "purpose")
+ private String purpose;
+
+ public MLScorecardFields() {
+
+ }
+
+ public MLScorecardFields(Integer age, String sex, String job, String housing, BigDecimal creditAmount, Integer duration,
+ String purpose) {
+ this.age = age;
+ this.sex = sex;
+ this.job = job;
+ this.housing = housing;
+ this.creditAmount = creditAmount;
+ this.duration = duration;
+ this.purpose = purpose;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public String getSex() {
+ return sex;
+ }
+
+ public void setSex(String sex) {
+ this.sex = sex;
+ }
+
+ public String getJob() {
+ return job;
+ }
+
+ public void setJob(String job) {
+ this.job = job;
+ }
+
+ public String getHousing() {
+ return housing;
+ }
+
+ public void setHousing(String housing) {
+ this.housing = housing;
+ }
+
+ public BigDecimal getCreditAmount() {
+ return creditAmount;
+ }
+
+ public void setCreditAmount(BigDecimal creditAmount) {
+ this.creditAmount = creditAmount;
+ }
+
+ public Integer getDuration() {
+ return duration;
+ }
+
+ public void setDuration(Integer duration) {
+ this.duration = duration;
+ }
+
+ public String getPurpose() {
+ return purpose;
+ }
+
+ public void setPurpose(String purpose) {
+ this.purpose = purpose;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof MLScorecardFields)) {
+ return false;
+ }
+ MLScorecardFields that = (MLScorecardFields) o;
+ return Objects.equals(age, that.age) && Objects.equals(sex, that.sex) && Objects.equals(job, that.job)
+ && Objects.equals(housing, that.housing) && Objects.equals(creditAmount, that.creditAmount)
+ && Objects.equals(duration, that.duration) && Objects.equals(purpose, that.purpose);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(age, sex, job, housing, creditAmount, duration, purpose);
+ }
+
+ @Override
+ public String toString() {
+ return "MLScorecardFields{" + "age=" + age + ", sex='" + sex + '\'' + ", job='" + job + '\'' + ", housing='" + housing + '\''
+ + ", creditAmount=" + creditAmount + ", duration=" + duration + ", purpose='" + purpose + '\'' + '}';
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/MLScorecardRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/MLScorecardRepository.java
new file mode 100644
index 0000000..066d8c5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/MLScorecardRepository.java
@@ -0,0 +1,28 @@
+/**
+ * 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.creditscorecard.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface MLScorecardRepository extends JpaRepository<MLScorecard, Long>, JpaSpecificationExecutor<MLScorecard> {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/RuleBasedScorecard.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/RuleBasedScorecard.java
new file mode 100644
index 0000000..b3477ed
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/RuleBasedScorecard.java
@@ -0,0 +1,70 @@
+/**
+ * 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.creditscorecard.domain;
+
+import java.math.BigDecimal;
+import java.util.List;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+
+@Entity
+@Table(name = "m_rule_based_scorecard")
+public class RuleBasedScorecard extends AbstractPersistableCustom {
+
+ @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
+ @JoinColumn(name = "rule_based_scorecard_id", referencedColumnName = "id")
+ private List<FeatureCriteriaScore> criteriaScores;
+
+ @Column(name = "overall_score")
+ private BigDecimal overallScore;
+
+ @Column(name = "overall_color")
+ private String overallColor;
+
+ public RuleBasedScorecard() {
+ //
+ }
+
+ public BigDecimal getOverallScore() {
+ return overallScore;
+ }
+
+ public String getOverallColor() {
+ return overallColor;
+ }
+
+ public void setScore(final BigDecimal overallScore, final String overallColor) {
+ this.overallScore = overallScore;
+ this.overallColor = overallColor;
+ }
+
+ public void setCriteriaScores(final List<FeatureCriteriaScore> criteriaScores) {
+ this.criteriaScores = criteriaScores;
+ }
+
+ public List<FeatureCriteriaScore> getCriteriaScores() {
+ return criteriaScores;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/RuleBasedScorecardRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/RuleBasedScorecardRepository.java
new file mode 100644
index 0000000..2839bed
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/RuleBasedScorecardRepository.java
@@ -0,0 +1,27 @@
+/**
+ * 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.creditscorecard.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface RuleBasedScorecardRepository
+ extends JpaRepository<RuleBasedScorecard, Long>, JpaSpecificationExecutor<RuleBasedScorecard> {}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/ScorecardFeatureCriteriaRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/ScorecardFeatureCriteriaRepository.java
new file mode 100644
index 0000000..84e69f2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/ScorecardFeatureCriteriaRepository.java
@@ -0,0 +1,28 @@
+/**
+ * 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.creditscorecard.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ScorecardFeatureCriteriaRepository
+ extends JpaRepository<org.apache.fineract.portfolio.creditscorecard.domain.FeatureCriteria, Long>,
+ JpaSpecificationExecutor<org.apache.fineract.portfolio.creditscorecard.domain.FeatureCriteria> {}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/StatScorecard.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/StatScorecard.java
new file mode 100644
index 0000000..8a5928e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/StatScorecard.java
@@ -0,0 +1,147 @@
+/**
+ * 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.creditscorecard.domain;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+
+@Entity
+@Table(name = "m_stat_scorecard")
+public class StatScorecard extends AbstractPersistableCustom {
+
+ @Embedded
+ private MLScorecardFields scorecardFields;
+
+ @Column(name = "method")
+ private String method;
+
+ @Column(name = "color")
+ private String color;
+
+ @Column(name = "prediction")
+ private BigDecimal prediction;
+
+ @Column(name = "wilki_s_lambda")
+ private BigDecimal wilkisLambda;
+
+ @Column(name = "pillai_s_trace")
+ private BigDecimal pillaisTrace;
+
+ @Column(name = "hotelling_lawley_trace")
+ private BigDecimal hotellingLawley;
+
+ @Column(name = "roy_s_greatest_roots")
+ private BigDecimal roysGreatestRoots;
+
+ public StatScorecard() {
+ //
+ }
+
+ public StatScorecard(String method, String color, BigDecimal prediction, BigDecimal wilkisLambda, BigDecimal pillaisTrace,
+ BigDecimal hotellingLawley, BigDecimal roysGreatestRoots) {
+ this.method = method;
+ this.color = color;
+ this.prediction = prediction;
+ this.wilkisLambda = wilkisLambda;
+ this.pillaisTrace = pillaisTrace;
+ this.hotellingLawley = hotellingLawley;
+ this.roysGreatestRoots = roysGreatestRoots;
+ }
+
+ public StatScorecard(final MLScorecardFields mlScorecardFields) {
+ this.scorecardFields = mlScorecardFields;
+ }
+
+ public void setPredictionResponse(String method, String color, BigDecimal prediction, BigDecimal wilkisLambda, BigDecimal pillaisTrace,
+ BigDecimal hotellingLawley, BigDecimal roysGreatestRoots) {
+ this.method = method;
+ this.color = color;
+ this.prediction = prediction;
+ if (this.method.equalsIgnoreCase("manova")) {
+ this.wilkisLambda = wilkisLambda;
+ this.pillaisTrace = pillaisTrace;
+ this.hotellingLawley = hotellingLawley;
+ this.roysGreatestRoots = roysGreatestRoots;
+ }
+ }
+
+ public MLScorecardFields getScorecardFields() {
+ return scorecardFields;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+
+ public String getColor() {
+ return color;
+ }
+
+ public BigDecimal getPrediction() {
+ return prediction;
+ }
+
+ public BigDecimal getWilkisLambda() {
+ return wilkisLambda;
+ }
+
+ public BigDecimal getPillaisTrace() {
+ return pillaisTrace;
+ }
+
+ public BigDecimal getHotellingLawley() {
+ return hotellingLawley;
+ }
+
+ public BigDecimal getRoysGreatestRoots() {
+ return roysGreatestRoots;
+ }
+
+ @Override
+ public String toString() {
+ return "StatScorecard{" + "scorecardFields=" + scorecardFields + ", method='" + method + '\'' + ", color='" + color + '\''
+ + ", prediction=" + prediction + ", wilkisLambda=" + wilkisLambda + ", pillaisTrace=" + pillaisTrace + ", hotellingLawley="
+ + hotellingLawley + ", roysGreatestRoots=" + roysGreatestRoots + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof StatScorecard)) {
+ return false;
+ }
+ StatScorecard that = (StatScorecard) o;
+ return Objects.equals(scorecardFields, that.scorecardFields) && Objects.equals(method, that.method)
+ && Objects.equals(color, that.color) && Objects.equals(prediction, that.prediction)
+ && Objects.equals(wilkisLambda, that.wilkisLambda) && Objects.equals(pillaisTrace, that.pillaisTrace)
+ && Objects.equals(hotellingLawley, that.hotellingLawley) && Objects.equals(roysGreatestRoots, that.roysGreatestRoots);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(scorecardFields, method, color, prediction, wilkisLambda, pillaisTrace, hotellingLawley, roysGreatestRoots);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/StatScorecardRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/StatScorecardRepository.java
new file mode 100644
index 0000000..516bf2d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/domain/StatScorecardRepository.java
@@ -0,0 +1,26 @@
+/**
+ * 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.creditscorecard.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface StatScorecardRepository extends JpaRepository<StatScorecard, Long>, JpaSpecificationExecutor<StatScorecard> {}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/exception/FeatureCannotBeDeletedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/exception/FeatureCannotBeDeletedException.java
new file mode 100644
index 0000000..158dd27
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/exception/FeatureCannotBeDeletedException.java
@@ -0,0 +1,31 @@
+/**
+ * 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.creditscorecard.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class FeatureCannotBeDeletedException extends AbstractPlatformDomainRuleException {
+
+ public FeatureCannotBeDeletedException(final String globalisationMessageCode, final String defaultUserMessage,
+ final Object... defaultUserMessageArgs) {
+
+ super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/exception/FeatureNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/exception/FeatureNotFoundException.java
new file mode 100644
index 0000000..d960d31
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/exception/FeatureNotFoundException.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.creditscorecard.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+import org.springframework.dao.EmptyResultDataAccessException;
+
+public class FeatureNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+ public FeatureNotFoundException(final Long id) {
+ super("error.msg.scorecard.feature.id.invalid", "Scorecard Feature with identifier " + id + " does not exist", id);
+ }
+
+ public FeatureNotFoundException(Long id, EmptyResultDataAccessException e) {
+ super("error.msg.scorecard.feature.id.invalid", "Scorecard Feature with identifier " + id + " does not exist", id, e);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/handler/CreateCreditScorecardFeatureDefinitionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/handler/CreateCreditScorecardFeatureDefinitionCommandHandler.java
new file mode 100644
index 0000000..08ec7a8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/handler/CreateCreditScorecardFeatureDefinitionCommandHandler.java
@@ -0,0 +1,56 @@
+/**
+ * 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.creditscorecard.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
+import org.apache.fineract.portfolio.creditscorecard.provider.ScorecardServiceProvider;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CREDIT_SCORECARD_FEATURE", action = "CREATE")
+public class CreateCreditScorecardFeatureDefinitionCommandHandler implements NewCommandSourceHandler {
+
+ private final ScorecardServiceProvider serviceProvider;
+
+ @Autowired
+ public CreateCreditScorecardFeatureDefinitionCommandHandler(final ScorecardServiceProvider serviceProvider) {
+ this.serviceProvider = serviceProvider;
+ }
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+ final String serviceName = "CreditScorecardWritePlatformService";
+ final CreditScorecardWritePlatformService scorecardWritePlatformService = (CreditScorecardWritePlatformService) this.serviceProvider
+ .getScorecardService(serviceName);
+ if (scorecardWritePlatformService == null) {
+ throw new PlatformServiceUnavailableException("err.msg.credit.scorecard.service.implementation.missing",
+ ScorecardServiceProvider.SERVICE_MISSING + serviceName, serviceName);
+ }
+
+ return scorecardWritePlatformService.createScoringFeature(command);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/handler/DeleteCreditScorecardFeatureDefinitionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/handler/DeleteCreditScorecardFeatureDefinitionCommandHandler.java
new file mode 100644
index 0000000..7241749
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/handler/DeleteCreditScorecardFeatureDefinitionCommandHandler.java
@@ -0,0 +1,56 @@
+/**
+ * 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.creditscorecard.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
+import org.apache.fineract.portfolio.creditscorecard.provider.ScorecardServiceProvider;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CREDIT_SCORECARD_FEATURE", action = "DELETE")
+public class DeleteCreditScorecardFeatureDefinitionCommandHandler implements NewCommandSourceHandler {
+
+ private final ScorecardServiceProvider serviceProvider;
+
+ @Autowired
+ public DeleteCreditScorecardFeatureDefinitionCommandHandler(final ScorecardServiceProvider serviceProvider) {
+ this.serviceProvider = serviceProvider;
+ }
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+ final String serviceName = "CreditScorecardWritePlatformService";
+ final CreditScorecardWritePlatformService scorecardWritePlatformService = (CreditScorecardWritePlatformService) this.serviceProvider
+ .getScorecardService(serviceName);
+ if (scorecardWritePlatformService == null) {
+ throw new PlatformServiceUnavailableException("err.msg.credit.scorecard.service.implementation.missing",
+ ScorecardServiceProvider.SERVICE_MISSING + serviceName, serviceName);
+ }
+
+ return scorecardWritePlatformService.deleteScoringFeature(command.entityId());
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/handler/UpdateCreditScorecardFeatureDefinitionCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/handler/UpdateCreditScorecardFeatureDefinitionCommandHandler.java
new file mode 100644
index 0000000..5d8daa2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/handler/UpdateCreditScorecardFeatureDefinitionCommandHandler.java
@@ -0,0 +1,56 @@
+/**
+ * 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.creditscorecard.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
+import org.apache.fineract.portfolio.creditscorecard.provider.ScorecardServiceProvider;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CREDIT_SCORECARD_FEATURE", action = "UPDATE")
+public class UpdateCreditScorecardFeatureDefinitionCommandHandler implements NewCommandSourceHandler {
+
+ private final ScorecardServiceProvider serviceProvider;
+
+ @Autowired
+ public UpdateCreditScorecardFeatureDefinitionCommandHandler(final ScorecardServiceProvider serviceProvider) {
+ this.serviceProvider = serviceProvider;
+ }
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+ final String serviceName = "CreditScorecardWritePlatformService";
+ final CreditScorecardWritePlatformService scorecardWritePlatformService = (CreditScorecardWritePlatformService) this.serviceProvider
+ .getScorecardService(serviceName);
+ if (scorecardWritePlatformService == null) {
+ throw new PlatformServiceUnavailableException("err.msg.credit.scorecard.service.implementation.missing",
+ ScorecardServiceProvider.SERVICE_MISSING + serviceName, serviceName);
+ }
+
+ return scorecardWritePlatformService.updateScoringFeature(command.entityId(), command);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/provider/ScorecardServiceProvider.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/provider/ScorecardServiceProvider.java
new file mode 100644
index 0000000..9ab2cb9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/provider/ScorecardServiceProvider.java
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.creditscorecard.provider;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.fineract.portfolio.creditscorecard.annotation.ScorecardService;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Component
+@Scope("singleton")
+public class ScorecardServiceProvider {
+
+ public static final String SERVICE_MISSING = "There is no ScorecardService registered in the ScorecardServiceProvider for this report name: ";
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ScorecardServiceProvider.class);
+
+ private final Map<String, CreditScorecardService> scorecardServices;
+
+ @SuppressWarnings("unchecked")
+ @Autowired
+ public ScorecardServiceProvider(List<CreditScorecardService> scorecardServices) {
+
+ var mapBuilder = ImmutableMap.<String, CreditScorecardService>builder();
+ for (CreditScorecardService s : scorecardServices) {
+ Class<? extends CreditScorecardService> serviceClass = s.getClass();
+
+ if (!serviceClass.isAnnotationPresent(ScorecardService.class)) {
+ serviceClass = (Class<? extends CreditScorecardService>) serviceClass.getGenericSuperclass();
+ }
+
+ final String name = serviceClass.getAnnotation(ScorecardService.class).name();
+ mapBuilder.put(name, s);
+
+ LOGGER.warn("Registered credit scorecard service '{}' for name '{}'", s, name);
+
+ }
+ this.scorecardServices = mapBuilder.build();
+ }
+
+ public CreditScorecardService getScorecardService(final String serviceName) {
+ return scorecardServices.getOrDefault(serviceName, null);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/serialization/CreditScorecardApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/serialization/CreditScorecardApiJsonHelper.java
new file mode 100644
index 0000000..f3f118c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/serialization/CreditScorecardApiJsonHelper.java
@@ -0,0 +1,350 @@
+/**
+ * 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.creditscorecard.serialization;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.creditscorecard.domain.FeatureCategory;
+import org.apache.fineract.portfolio.creditscorecard.domain.FeatureDataType;
+import org.apache.fineract.portfolio.creditscorecard.domain.FeatureValueType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CreditScorecardApiJsonHelper {
+
+ private final Set<String> supportedParameters = new HashSet<>(
+ Arrays.asList("name", "valueType", "dataType", "category", "active", "locale"));
+
+ private final FromJsonHelper fromApiJsonHelper;
+
+ @Autowired
+ public CreditScorecardApiJsonHelper(final FromJsonHelper fromApiJsonHelper) {
+ this.fromApiJsonHelper = fromApiJsonHelper;
+ }
+
+ public void validateFeatureForCreate(final String json) {
+ if (StringUtils.isBlank(json)) {
+ throw new InvalidJsonException();
+ }
+
+ final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+ this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
+
+ final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("credit_scorecard_feature");
+
+ final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+ final String name = this.fromApiJsonHelper.extractStringNamed("name", element);
+ baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(100);
+
+ final Integer valueType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("valueType", element);
+ baseDataValidator.reset().parameter("valueType").value(valueType).notNull();
+ if (valueType != null) {
+ baseDataValidator.reset().parameter("valueType").value(valueType).isOneOfTheseValues(FeatureValueType.validValues());
+ }
+
+ final Integer dataType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("dataType", element);
+ baseDataValidator.reset().parameter("dataType").value(dataType).notNull();
+ if (valueType != null) {
+ baseDataValidator.reset().parameter("dataType").value(dataType).isOneOfTheseValues(FeatureDataType.validValues());
+ }
+
+ final Integer category = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("category", element);
+ baseDataValidator.reset().parameter("category").value(category).notNull();
+ if (valueType != null) {
+ baseDataValidator.reset().parameter("category").value(category).isOneOfTheseValues(FeatureCategory.validValues());
+ }
+
+ if (this.fromApiJsonHelper.parameterExists("active", element)) {
+ final Boolean active = this.fromApiJsonHelper.extractBooleanNamed("active", element);
+ baseDataValidator.reset().parameter("active").value(active).notNull();
+ }
+
+ }
+
+ public void validateFeatureForUpdate(final String json) {
+ if (StringUtils.isBlank(json)) {
+ throw new InvalidJsonException();
+ }
+
+ final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+ this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
+
+ final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("credit_scorecard_feature");
+
+ final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+ final String name = this.fromApiJsonHelper.extractStringNamed("name", element);
+ baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(100);
+
+ final Integer valueType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("valueType", element);
+ baseDataValidator.reset().parameter("valueType").value(valueType).notNull();
+ if (valueType != null) {
+ baseDataValidator.reset().parameter("valueType").value(valueType).isOneOfTheseValues(FeatureValueType.validValues());
+ }
+
+ final Integer dataType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("dataType", element);
+ baseDataValidator.reset().parameter("dataType").value(dataType).notNull();
+ if (valueType != null) {
+ baseDataValidator.reset().parameter("dataType").value(dataType).isOneOfTheseValues(FeatureDataType.validValues());
+ }
+
+ final Integer category = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("category", element);
+ baseDataValidator.reset().parameter("category").value(category).notNull();
+ if (valueType != null) {
+ baseDataValidator.reset().parameter("category").value(category).isOneOfTheseValues(FeatureCategory.validValues());
+ }
+
+ if (this.fromApiJsonHelper.parameterExists("active", element)) {
+ final Boolean active = this.fromApiJsonHelper.extractBooleanNamed("active", element);
+ baseDataValidator.reset().parameter("active").value(active).notNull();
+ }
+
+ }
+
+ public void validateScorecardJson(final JsonElement element) {
+
+ final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("creditScorecard");
+
+ final String scorecardParameterName = "scorecard";
+ final JsonObject topLevelJsonElement = element.getAsJsonObject();
+
+ if (topLevelJsonElement.get(scorecardParameterName).isJsonObject()) {
+ final Type arrayObjectParameterTypeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+ final Set<String> supportedParameters = new HashSet<>(Arrays.asList("scoringMethod", "scoringModel", "mlScorecard",
+ "statScorecard", "ruleBasedScorecard", "locale", "dateFormat"));
+
+ final JsonObject scorecardElement = topLevelJsonElement.getAsJsonObject(scorecardParameterName);
+ this.fromApiJsonHelper.checkForUnsupportedParameters(arrayObjectParameterTypeOfMap,
+ this.fromApiJsonHelper.toJson(scorecardElement), supportedParameters);
+
+ final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(scorecardElement);
+ final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(scorecardElement);
+
+ final String scoringMethod = this.fromApiJsonHelper.extractStringNamed("scoringMethod", scorecardElement);
+
+ if (scoringMethod != null) {
+ baseDataValidator.reset().parameter("scoringMethod").value(scoringMethod).notNull().notExceedingLengthOf(100);
+
+ final String scoringModel = this.fromApiJsonHelper.extractStringNamed("scoringModel", scorecardElement);
+ baseDataValidator.reset().parameter("scoringModel").value(scoringModel).notNull().notExceedingLengthOf(100);
+
+ if (!dataValidationErrors.isEmpty()) {
+ throw new PlatformApiDataValidationException(dataValidationErrors);
+ }
+
+ switch (scoringMethod) {
+ case "ml":
+ this.validateMLScorecardJson(scorecardElement);
+ break;
+
+ case "stat":
+ this.validateStatScorecardJson(scorecardElement);
+ break;
+
+ case "ruleBased":
+ this.validateRuleBasedScorecardJson(scorecardElement);
+ break;
+ }
+ }
+ }
+ }
+
+ private void validateRuleBasedScorecardJson(final JsonElement element) {
+
+ final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("ruleBasedScorecard");
+
+ final String rbScorecardParameterName = "ruleBasedScorecard";
+ if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(rbScorecardParameterName, element)) {
+ final JsonObject topLevelJsonElement = element.getAsJsonObject();
+
+ if (topLevelJsonElement.get(rbScorecardParameterName).isJsonObject()) {
+ final Type arrayObjectParameterTypeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+ final Set<String> supportedParameters = new HashSet<>(Arrays.asList("criteriaScores", "scorecardScore", "scorecardColor"));
+
+ final JsonObject rbScorecardElement = topLevelJsonElement.getAsJsonObject(rbScorecardParameterName);
+ this.fromApiJsonHelper.checkForUnsupportedParameters(arrayObjectParameterTypeOfMap,
+ this.fromApiJsonHelper.toJson(rbScorecardElement), supportedParameters);
+
+ final String criteriaScoresParameterName = "criteriaScores";
+ if (rbScorecardElement.get(criteriaScoresParameterName).isJsonArray()) {
+ final Type criteriaScoreArrayObjectParameterTypeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+ final Set<String> criteriaScoreSupportedParameters = new HashSet<>(
+ Arrays.asList("featureId", "value", "score", "color"));
+
+ final JsonArray array = rbScorecardElement.getAsJsonArray(criteriaScoresParameterName);
+ for (int i = 1; i <= array.size(); i++) {
+
+ final JsonObject criteriaScoreElement = array.get(i - 1).getAsJsonObject();
+ this.fromApiJsonHelper.checkForUnsupportedParameters(criteriaScoreArrayObjectParameterTypeOfMap,
+ this.fromApiJsonHelper.toJson(criteriaScoreElement), criteriaScoreSupportedParameters);
+
+ final Long featureId = this.fromApiJsonHelper.extractLongNamed("featureId", criteriaScoreElement);
+ baseDataValidator.reset().parameter(criteriaScoresParameterName).parameterAtIndexArray("featureId", i)
+ .value(featureId).notNull().integerGreaterThanZero();
+
+ final String feature = this.fromApiJsonHelper.extractStringNamed("feature", criteriaScoreElement);
+ baseDataValidator.reset().parameter(criteriaScoresParameterName).parameterAtIndexArray("feature", i).value(feature)
+ .ignoreIfNull().notExceedingLengthOf(100);
+
+ final String value = this.fromApiJsonHelper.extractStringNamed("value", criteriaScoreElement);
+ baseDataValidator.reset().parameter(criteriaScoresParameterName).parameterAtIndexArray("value", i).value(value)
+ .notNull().notExceedingLengthOf(100);
+
+ if (this.fromApiJsonHelper.parameterExists("score", criteriaScoreElement)) {
+ final BigDecimal score = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("score", criteriaScoreElement);
+ baseDataValidator.reset().parameter(criteriaScoresParameterName).parameterAtIndexArray("score", i).value(score)
+ .ignoreIfNull().positiveAmount();
+ }
+
+ if (this.fromApiJsonHelper.parameterExists("color", criteriaScoreElement)) {
+ final String color = this.fromApiJsonHelper.extractStringNamed("color", criteriaScoreElement);
+ baseDataValidator.reset().parameter(criteriaScoresParameterName).parameterAtIndexArray("color", i).value(color)
+ .ignoreIfNull().notExceedingLengthOf(100);
+ }
+ }
+ }
+ }
+ }
+
+ if (!dataValidationErrors.isEmpty()) {
+ throw new PlatformApiDataValidationException(dataValidationErrors);
+ }
+
+ }
+
+ private void validateStatScorecardJson(final JsonElement element) {
+
+ final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("statScorecard");
+
+ final String statScorecardParameterName = "statScorecard";
+ if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(statScorecardParameterName, element)) {
+ final JsonObject topLevelJsonElement = element.getAsJsonObject();
+
+ if (topLevelJsonElement.get(statScorecardParameterName).isJsonObject()) {
+ final Type arrayObjectParameterTypeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+ final Set<String> supportedParameters = new HashSet<>(
+ Arrays.asList("age", "sex", "job", "housing", "creditAmount", "duration", "purpose", "locale", "dateFormat"));
+
+ final JsonObject statScorecardElement = topLevelJsonElement.getAsJsonObject(statScorecardParameterName);
+ this.fromApiJsonHelper.checkForUnsupportedParameters(arrayObjectParameterTypeOfMap,
+ this.fromApiJsonHelper.toJson(statScorecardElement), supportedParameters);
+
+ final Integer age = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("age", statScorecardElement);
+ baseDataValidator.reset().parameter("age").value(age).ignoreIfNull().integerGreaterThanZero();
+
+ final String sex = this.fromApiJsonHelper.extractStringNamed("sex", statScorecardElement);
+ baseDataValidator.reset().parameter("sex").value(sex).ignoreIfNull().notExceedingLengthOf(100);
+
+ final String job = this.fromApiJsonHelper.extractStringNamed("job", statScorecardElement);
+ baseDataValidator.reset().parameter("job").value(job).ignoreIfNull().notExceedingLengthOf(100);
+
+ final String housing = this.fromApiJsonHelper.extractStringNamed("housing", statScorecardElement);
+ baseDataValidator.reset().parameter("housing").value(housing).ignoreIfNull().notExceedingLengthOf(100);
+
+ final BigDecimal creditAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("creditAmount",
+ statScorecardElement);
+ baseDataValidator.reset().parameter("creditAmount").value(creditAmount).notNull().positiveAmount();
+
+ final Integer duration = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("duration", statScorecardElement);
+ baseDataValidator.reset().parameter("duration").value(duration).ignoreIfNull().integerGreaterThanZero();
+
+ final String purpose = this.fromApiJsonHelper.extractStringNamed("purpose", statScorecardElement);
+ baseDataValidator.reset().parameter("purpose").value(purpose).ignoreIfNull().notExceedingLengthOf(100);
+
+ }
+ }
+
+ if (!dataValidationErrors.isEmpty()) {
+ throw new PlatformApiDataValidationException(dataValidationErrors);
+ }
+
+ }
+
+ private void validateMLScorecardJson(final JsonElement element) {
+
+ final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("mlScorecard");
+
+ final String mlScorecardParameterName = "mlScorecard";
+ if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(mlScorecardParameterName, element)) {
+ final JsonObject topLevelJsonElement = element.getAsJsonObject();
+
+ if (topLevelJsonElement.get(mlScorecardParameterName).isJsonObject()) {
+ final Type arrayObjectParameterTypeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+ final Set<String> supportedParameters = new HashSet<>(
+ Arrays.asList("age", "sex", "job", "housing", "creditAmount", "duration", "purpose", "locale", "dateFormat"));
+
+ final JsonObject mlScorecardElement = topLevelJsonElement.getAsJsonObject(mlScorecardParameterName);
+ this.fromApiJsonHelper.checkForUnsupportedParameters(arrayObjectParameterTypeOfMap,
+ this.fromApiJsonHelper.toJson(mlScorecardElement), supportedParameters);
+
+ final Integer age = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("age", mlScorecardElement);
+ baseDataValidator.reset().parameter("age").value(age).ignoreIfNull().integerGreaterThanZero();
+
+ final String sex = this.fromApiJsonHelper.extractStringNamed("sex", mlScorecardElement);
+ baseDataValidator.reset().parameter("sex").value(sex).ignoreIfNull().notExceedingLengthOf(100);
+
+ final String job = this.fromApiJsonHelper.extractStringNamed("job", mlScorecardElement);
+ baseDataValidator.reset().parameter("job").value(job).ignoreIfNull().notExceedingLengthOf(100);
+
+ final String housing = this.fromApiJsonHelper.extractStringNamed("housing", mlScorecardElement);
+ baseDataValidator.reset().parameter("housing").value(housing).ignoreIfNull().notExceedingLengthOf(100);
+
+ final BigDecimal creditAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("creditAmount", mlScorecardElement);
+ baseDataValidator.reset().parameter("creditAmount").value(creditAmount).notNull().positiveAmount();
+
+ final Integer duration = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("duration", mlScorecardElement);
+ baseDataValidator.reset().parameter("duration").value(duration).ignoreIfNull().integerGreaterThanZero();
+
+ final String purpose = this.fromApiJsonHelper.extractStringNamed("purpose", mlScorecardElement);
+ baseDataValidator.reset().parameter("purpose").value(purpose).ignoreIfNull().notExceedingLengthOf(100);
+
+ }
+ }
+
+ if (!dataValidationErrors.isEmpty()) {
+ throw new PlatformApiDataValidationException(dataValidationErrors);
+ }
+
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardAssembler.java
new file mode 100644
index 0000000..0274866
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardAssembler.java
@@ -0,0 +1,34 @@
+/**
+ * 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.creditscorecard.service;
+
+import com.google.gson.JsonElement;
+import java.util.List;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.portfolio.creditscorecard.domain.CreditScorecard;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductScorecardFeature;
+
+public interface CreditScorecardAssembler extends CreditScorecardService {
+
+ CreditScorecard assembleFrom(JsonElement element);
+
+ List<LoanProductScorecardFeature> assembleListOfProductScoringFeatures(JsonCommand command, LoanProduct loanProduct);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardEnumerations.java
new file mode 100644
index 0000000..c42b6c9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardEnumerations.java
@@ -0,0 +1,137 @@
+/**
+ * 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.creditscorecard.service;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.creditscorecard.domain.FeatureCategory;
+import org.apache.fineract.portfolio.creditscorecard.domain.FeatureDataType;
+import org.apache.fineract.portfolio.creditscorecard.domain.FeatureValueType;
+
+public final class CreditScorecardEnumerations {
+
+ public static final String FEATURE_VALUE_TYPE = "valueType";
+ public static final String FEATURE_DATA_TYPE = "dataType";
+ public static final String FEATURE_CATEGORY = "category";
+
+ private CreditScorecardEnumerations() {
+ //
+ }
+
+ public static EnumOptionData scorecardEnumueration(final String typeName, final int id) {
+ switch (typeName) {
+ case FEATURE_VALUE_TYPE:
+ return featureValueType(id);
+ case FEATURE_DATA_TYPE:
+ return featureDataType(id);
+ case FEATURE_CATEGORY:
+ return featureCategory(id);
+ }
+ return null;
+ }
+
+ public static EnumOptionData featureValueType(final Integer id) {
+ return featureValueType(FeatureValueType.fromInt(id));
+ }
+
+ public static EnumOptionData featureValueType(final FeatureValueType valueType) {
+ EnumOptionData optionData = null;
+ switch (valueType) {
+ case BINARY:
+ optionData = new EnumOptionData(FeatureValueType.BINARY.getValue().longValue(), FeatureValueType.BINARY.getCode(),
+ "Binary");
+ break;
+ case NOMINAL:
+ optionData = new EnumOptionData(FeatureValueType.NOMINAL.getValue().longValue(), FeatureValueType.NOMINAL.getCode(),
+ "Nominal");
+ break;
+ case INTERVAL:
+ optionData = new EnumOptionData(FeatureValueType.INTERVAL.getValue().longValue(), FeatureValueType.INTERVAL.getCode(),
+ "Interval");
+ break;
+ case RATIO:
+ optionData = new EnumOptionData(FeatureValueType.RATIO.getValue().longValue(), FeatureValueType.RATIO.getCode(), "Ratio");
+ break;
+ default:
+ optionData = new EnumOptionData(FeatureValueType.INVALID.getValue().longValue(), FeatureValueType.INVALID.getCode(),
+ "Invalid");
+ break;
+ }
+ return optionData;
+ }
+
+ public static EnumOptionData featureDataType(final Integer id) {
+ return featureDataType(FeatureDataType.fromInt(id));
+ }
+
+ public static EnumOptionData featureDataType(final FeatureDataType valueType) {
+ EnumOptionData optionData = null;
+ switch (valueType) {
+ case NUMERIC:
+ optionData = new EnumOptionData(FeatureDataType.NUMERIC.getValue().longValue(), FeatureDataType.NUMERIC.getCode(),
+ "Numeric");
+ break;
+ case STRING:
+ optionData = new EnumOptionData(FeatureDataType.STRING.getValue().longValue(), FeatureDataType.STRING.getCode(), "String");
+ break;
+ case DATE:
+ optionData = new EnumOptionData(FeatureDataType.DATE.getValue().longValue(), FeatureDataType.DATE.getCode(), "Date");
+ break;
+ default:
+ optionData = new EnumOptionData(FeatureValueType.INVALID.getValue().longValue(), FeatureValueType.INVALID.getCode(),
+ "Invalid");
+ break;
+ }
+ return optionData;
+ }
+
+ public static EnumOptionData featureCategory(final Integer id) {
+ return featureCategory(FeatureCategory.fromInt(id));
+ }
+
+ public static EnumOptionData featureCategory(final FeatureCategory category) {
+ EnumOptionData optionData = null;
+ switch (category) {
+ case INDIVIDUAL:
+ optionData = new EnumOptionData(FeatureCategory.INDIVIDUAL.getValue().longValue(), FeatureCategory.INDIVIDUAL.getCode(),
+ "Individual");
+ break;
+ case ORGANISATION:
+ optionData = new EnumOptionData(FeatureCategory.ORGANISATION.getValue().longValue(), FeatureCategory.ORGANISATION.getCode(),
+ "Organisation");
+ break;
+ case COUNTRY:
+ optionData = new EnumOptionData(FeatureCategory.COUNTRY.getValue().longValue(), FeatureCategory.COUNTRY.getCode(),
+ "Country");
+ break;
+ case CREDIT_HISTORY:
+ optionData = new EnumOptionData(FeatureCategory.CREDIT_HISTORY.getValue().longValue(),
+ FeatureCategory.CREDIT_HISTORY.getCode(), "Credit History");
+ break;
+ case LOAN:
+ optionData = new EnumOptionData(FeatureCategory.LOAN.getValue().longValue(), FeatureCategory.LOAN.getCode(), "Loan");
+ break;
+ default:
+ optionData = new EnumOptionData(FeatureValueType.INVALID.getValue().longValue(), FeatureValueType.INVALID.getCode(),
+ "Invalid");
+ break;
+ }
+ return optionData;
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardReadPlatformService.java
new file mode 100644
index 0000000..9c16889
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardReadPlatformService.java
@@ -0,0 +1,42 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.creditscorecard.service;
+
+import java.util.Collection;
+import java.util.List;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardData;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardFeatureData;
+import org.apache.fineract.portfolio.creditscorecard.domain.CreditScorecardFeature;
+
+public interface CreditScorecardReadPlatformService extends CreditScorecardService {
+
+ CreditScorecardFeatureData retrieveNewScorecardFeatureDetails();
+
+ Collection<CreditScorecardFeatureData> retrieveLoanProductFeatures(Long productId);
+
+ CreditScorecardData retrieveCreditScorecard(Long scorecardId);
+
+ CreditScorecardData loanScorecardTemplate();
+
+ CreditScorecardData loanScorecardTemplate(CreditScorecardData scorecard);
+
+ CreditScorecardFeature findOneFeatureWithNotFoundDetection(Long id);
+
+ List<CreditScorecardFeature> findAllFeaturesWithNotFoundDetection();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardService.java
new file mode 100644
index 0000000..2efad9f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardService.java
@@ -0,0 +1,23 @@
+/**
+ * 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.creditscorecard.service;
+
+public interface CreditScorecardService {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardWritePlatformService.java
new file mode 100644
index 0000000..38c0012
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/creditscorecard/service/CreditScorecardWritePlatformService.java
@@ -0,0 +1,36 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.creditscorecard.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.creditscorecard.domain.CreditScorecard;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+
+public interface CreditScorecardWritePlatformService extends CreditScorecardService {
+
+ CommandProcessingResult createScoringFeature(JsonCommand command);
+
+ CommandProcessingResult deleteScoringFeature(Long entityId);
+
+ CommandProcessingResult updateScoringFeature(Long entityId, JsonCommand command);
+
+ CreditScorecard assessCreditRisk(Loan loan);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
index 0bac357..2db0407 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
@@ -136,5 +136,8 @@
String lastApplication = "lastApplication";
String fixedPrincipalPercentagePerInstallmentParamName = "fixedPrincipalPercentagePerInstallment";
+ String recalculationCompoundingFrequencyDate = "recalculationCompoundingFrequencyDate";
+
+ String scorecard = "scorecard";
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
index 702cb56..f7171ec 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
@@ -100,6 +100,10 @@
import org.apache.fineract.portfolio.collateral.service.CollateralReadPlatformService;
import org.apache.fineract.portfolio.collateralmanagement.data.LoanCollateralResponseData;
import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralManagementReadPlatformService;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardData;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardFeatureData;
+import org.apache.fineract.portfolio.creditscorecard.provider.ScorecardServiceProvider;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardReadPlatformService;
import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
import org.apache.fineract.portfolio.fund.data.FundData;
import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
@@ -259,6 +263,7 @@
private final DefaultToApiJsonSerializer<GlimRepaymentTemplate> glimTemplateToApiJsonSerializer;
private final GLIMAccountInfoReadPlatformService glimAccountInfoReadPlatformService;
private final LoanCollateralManagementReadPlatformService loanCollateralManagementReadPlatformService;
+ private final ScorecardServiceProvider scorecardServiceProvider;
public LoansApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService,
final LoanProductReadPlatformService loanProductReadPlatformService,
@@ -284,7 +289,8 @@
final ConfigurationDomainService configurationDomainService,
final DefaultToApiJsonSerializer<GlimRepaymentTemplate> glimTemplateToApiJsonSerializer,
final GLIMAccountInfoReadPlatformService glimAccountInfoReadPlatformService,
- final LoanCollateralManagementReadPlatformService loanCollateralManagementReadPlatformService) {
+ final LoanCollateralManagementReadPlatformService loanCollateralManagementReadPlatformService,
+ final ScorecardServiceProvider scorecardServiceProvider) {
this.context = context;
this.loanReadPlatformService = loanReadPlatformService;
this.loanProductReadPlatformService = loanProductReadPlatformService;
@@ -317,6 +323,7 @@
this.glimTemplateToApiJsonSerializer = glimTemplateToApiJsonSerializer;
this.glimAccountInfoReadPlatformService = glimAccountInfoReadPlatformService;
this.loanCollateralManagementReadPlatformService = loanCollateralManagementReadPlatformService;
+ this.scorecardServiceProvider = scorecardServiceProvider;
}
/*
@@ -454,10 +461,20 @@
accountLinkingOptions = getaccountLinkingOptions(newLoanAccount, clientId, groupId);
}
+ CreditScorecardData scorecard = null;
+
+ final String serviceName = "CreditScorecardReadPlatformService";
+ final CreditScorecardReadPlatformService scorecardService = (CreditScorecardReadPlatformService) scorecardServiceProvider
+ .getScorecardService(serviceName);
+
+ if (scorecardService != null) {
+ scorecard = scorecardService.loanScorecardTemplate();
+ }
+
// add product options, allowed loan officers and calendar options
// (calendar options will be null in individual loan)
newLoanAccount = LoanAccountData.associationsAndTemplate(newLoanAccount, productOptions, allowedLoanOfficers, calendarOptions,
- accountLinkingOptions, isRatesEnabled);
+ accountLinkingOptions, isRatesEnabled, scorecard);
}
final List<DatatableData> datatableTemplates = this.entityDatatableChecksReadService
.retrieveTemplates(StatusEnum.CREATE.getCode().longValue(), EntityTables.LOAN.getName(), productId);
@@ -499,6 +516,19 @@
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
LoanAccountData loanBasicDetails = this.loanReadPlatformService.retrieveOne(loanId);
+
+ CreditScorecardData scorecard = null;
+
+ final String serviceName = "CreditScorecardReadPlatformService";
+ final CreditScorecardReadPlatformService scorecardService = (CreditScorecardReadPlatformService) scorecardServiceProvider
+ .getScorecardService(serviceName);
+
+ if (scorecardService != null) {
+ scorecard = scorecardService.retrieveCreditScorecard(loanBasicDetails.getScorecardId());
+ }
+
+ loanBasicDetails = LoanAccountData.populateScorecardDetails(loanBasicDetails, scorecard);
+
if (loanBasicDetails.isInterestRecalculationEnabled()) {
Collection<CalendarData> interestRecalculationCalendarDatas = this.calendarReadPlatformService.retrieveCalendarsByEntity(
loanBasicDetails.getInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue(),
@@ -749,14 +779,24 @@
rates = this.rateReadService.retrieveLoanRates(loanId);
}
+ final Collection<CreditScorecardFeatureData> scorecardFeatures = null;
+
+ final Collection<CreditScorecardFeatureData> scorecardFeatureOptions = null;
+
+ CreditScorecardData scorecardWithTemplate = null;
+
+ if (scorecardService != null) {
+ scorecardWithTemplate = scorecardService.loanScorecardTemplate(scorecard);
+ }
+
final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments,
charges, loanCollateralManagementData, guarantors, meeting, productOptions, loanTermFrequencyTypeOptions,
repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions,
repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions,
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations,
- overdueCharges, paidInAdvanceTemplate, interestRatesPeriods, clientActiveLoanOptions, rates, isRatesEnabled,
- collectionData);
+ overdueCharges, paidInAdvanceTemplate, interestRatesPeriods, clientActiveLoanOptions, rates, isRatesEnabled, collectionData,
+ scorecardFeatures, scorecardFeatureOptions, scorecardWithTemplate);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
mandatoryResponseParameters);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
index 4bc706a..7a62839 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
@@ -38,6 +38,8 @@
import org.apache.fineract.portfolio.calendar.data.CalendarData;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardData;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardFeatureData;
import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
import org.apache.fineract.portfolio.fund.data.FundData;
import org.apache.fineract.portfolio.group.data.GroupGeneralData;
@@ -189,6 +191,7 @@
private final Long closureLoanId;
private final String closureLoanAccountNo;
private final BigDecimal topupAmount;
+ private final CreditScorecardData scorecard;
private LoanProductData product;
@@ -239,6 +242,8 @@
private LocalDate expectedDisbursementDate;
private final CollectionData delinquent;
+ private final Collection<CreditScorecardFeatureData> scorecardFeatures;
+ private final Collection<CreditScorecardFeatureData> scorecardFeatureOptions;
public static LoanAccountData importInstanceIndividual(EnumOptionData loanTypeEnumOption, Long clientId, Long productId,
Long loanOfficerId, LocalDate submittedOnDate, Long fundId, BigDecimal principal, Integer numberOfRepayments,
@@ -255,7 +260,7 @@
nominalInterestRate, expectedDisbursementDate, amortizationEnumOption, interestMethodEnum,
interestCalculationPeriodTypeEnum, inArrearsTolerance, transactionProcessingStrategyId, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, repaymentsStartingFromDate, rowIndex, externalId,
- null, charges, linkAccountId, locale, dateFormat, loanCollateralManagementData);
+ null, charges, linkAccountId, locale, dateFormat, loanCollateralManagementData, null, null);
}
public static LoanAccountData importInstanceGroup(EnumOptionData loanTypeEnumOption, Long groupIdforGroupLoan, Long productId,
@@ -272,7 +277,7 @@
nominalInterestRate, null, amortizationEnumOption, interestMethodEnum, interestCalculationPeriodEnum, arrearsTolerance,
transactionProcessingStrategyId, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
interestChargedFromDate, repaymentsStartingFromDate, rowIndex, externalId, null, null, linkAccountId, locale, dateFormat,
- null);
+ null, null, null);
}
private LoanAccountData(EnumOptionData loanType, Long clientId, Long productId, Long loanOfficerId, LocalDate submittedOnDate,
@@ -283,7 +288,8 @@
Integer graceOnPrincipalPayment, Integer graceOnInterestPayment, Integer graceOnInterestCharged,
LocalDate interestChargedFromDate, LocalDate repaymentsStartingFromDate, Integer rowIndex, String externalId, Long groupId,
Collection<LoanChargeData> charges, String linkAccountId, String locale, String dateFormat,
- Collection<LoanCollateralManagementData> loanCollateralManagementData) {
+ Collection<LoanCollateralManagementData> loanCollateralManagementData, CreditScorecardData scorecard,
+ Collection<CreditScorecardFeatureData> scorecardFeatures) {
this.dateFormat = dateFormat;
this.locale = locale;
this.rowIndex = rowIndex;
@@ -426,6 +432,41 @@
this.isRatesEnabled = false;
this.fixedPrincipalPercentagePerInstallment = null;
this.delinquent = null;
+
+ this.scorecard = scorecard;
+
+ this.scorecardFeatures = scorecardFeatures;
+ this.scorecardFeatureOptions = null;
+
+ }
+
+ public static LoanAccountData populateScorecardDetails(LoanAccountData acc, CreditScorecardData scorecard) {
+ return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName,
+ acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
+ acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
+ acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.netDisbursalAmount,
+ acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments,
+ acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
+ acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod,
+ acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate,
+ acc.interestRateDifferential, acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalcualtion,
+ acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods,
+ acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary,
+ acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
+ acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
+ acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
+ acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
+ acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
+ acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
+ acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
+ acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
+ acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
+ acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
+ acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
+ acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
+ acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
+ acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
+ acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.scorecardFeatures, acc.scorecardFeatureOptions, scorecard);
}
public Integer getRowIndex() {
@@ -593,6 +634,11 @@
final BigDecimal fixedPrincipalPercentagePerInstallment = null;
final CollectionData delinquent = CollectionData.template();
+ final Collection<CreditScorecardFeatureData> scorecardFeatures = null;
+ final Collection<CreditScorecardFeatureData> scorecardFeatureOptions = null;
+
+ final CreditScorecardData scorecard = null;
+
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal,
@@ -613,7 +659,8 @@
interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup,
closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment, delinquent);
+ fixedPrincipalPercentagePerInstallment, delinquent, scorecardFeatures, scorecardFeatureOptions, scorecard);
+
}
/**
@@ -739,6 +786,11 @@
final BigDecimal fixedPrincipalPercentagePerInstallment = null;
final CollectionData delinquent = CollectionData.template();
+ final Collection<CreditScorecardFeatureData> scorecardFeatures = null;
+ final Collection<CreditScorecardFeatureData> scorecardFeatureOptions = null;
+
+ final CreditScorecardData scorecard = null;
+
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal,
@@ -759,7 +811,8 @@
originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup,
closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment, delinquent);
+ fixedPrincipalPercentagePerInstallment, delinquent, scorecardFeatures, scorecardFeatureOptions, scorecard);
+
}
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
@@ -790,7 +843,8 @@
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
- acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
+ acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.scorecardFeatures, acc.scorecardFeatureOptions,
+ acc.scorecard);
}
/**
@@ -918,6 +972,11 @@
final BigDecimal fixedPrincipalPercentagePerInstallment = null;
final CollectionData delinquent = CollectionData.template();
+ final Collection<CreditScorecardFeatureData> scorecardFeatures = null;
+ final Collection<CreditScorecardFeatureData> scorecardFeatureOptions = null;
+
+ final CreditScorecardData scorecard = null;
+
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal,
@@ -938,7 +997,8 @@
originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup,
closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment, delinquent);
+ fixedPrincipalPercentagePerInstallment, delinquent, scorecardFeatures, scorecardFeatureOptions, scorecard);
+
}
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
@@ -968,7 +1028,9 @@
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
- acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
+ acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.scorecardFeatures, acc.scorecardFeatureOptions,
+ acc.scorecard);
+
}
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
@@ -980,7 +1042,8 @@
final Collection<EnumOptionData> interestTypeOptions, final Collection<EnumOptionData> interestCalculationPeriodTypeOptions,
final Collection<FundData> fundOptions, final Collection<ChargeData> chargeOptions,
final Collection<CodeValueData> loanPurposeOptions, final Collection<CodeValueData> loanCollateralOptions,
- final Integer loanCycleNumber, final Collection<LoanAccountSummaryData> clientActiveLoanOptions) {
+ final Integer loanCycleNumber, final Collection<LoanAccountSummaryData> clientActiveLoanOptions,
+ final Collection<CreditScorecardFeatureData> scorecardFeatureOptions) {
final Long id = null;
final String accountNo = null;
@@ -1111,6 +1174,10 @@
final Boolean isRatesEnabled = false;
final CollectionData delinquent = CollectionData.template();
+ final Collection<CreditScorecardFeatureData> scorecardFeatures = product.scorecardFeatures();
+ // final Collection<CreditScorecardFeatureData> scorecardFeatureOptions = scorecardFeatureOptions;
+ final CreditScorecardData scorecard = null;
+
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, product.getId(), product.getName(), product.getDescription(), product.isLinkedToFloatingInterestRates(),
product.getFundId(), product.getFundName(), loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -1136,7 +1203,7 @@
product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(),
product.getMaximumGapBetweenInstallments(), subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId,
closureLoanAccountNo, topupAmount, product.isEqualAmortization(), rates, isRatesEnabled,
- product.getFixedPrincipalPercentagePerInstallment(), delinquent);
+ product.getFixedPrincipalPercentagePerInstallment(), delinquent, scorecardFeatures, scorecardFeatureOptions, scorecard);
}
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -1179,6 +1246,8 @@
}
}
final CollectionData delinquent = CollectionData.template();
+ final Collection<CreditScorecardFeatureData> scorecardFeatures = product.scorecardFeatures();
+ final Collection<CreditScorecardFeatureData> scorecardFeatureOptions = null;
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName,
acc.clientOfficeId, acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(),
@@ -1206,7 +1275,8 @@
product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(),
product.getMaximumGapBetweenInstallments(), acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, product.isEqualAmortization(), acc.rates, acc.isRatesEnabled,
- product.getFixedPrincipalPercentagePerInstallment(), delinquent);
+ product.getFixedPrincipalPercentagePerInstallment(), delinquent, scorecardFeatures, scorecardFeatureOptions, acc.scorecard);
+
}
/*
@@ -1237,7 +1307,8 @@
final LoanInterestRecalculationData interestRecalculationData, final Boolean createStandingInstructionAtDisbursement,
final Boolean isVariableInstallmentsAllowed, Integer minimumGap, Integer maximumGap, final EnumOptionData subStatus,
final boolean canUseForTopup, final boolean isTopup, final Long closureLoanId, final String closureLoanAccountNo,
- final BigDecimal topupAmount, final boolean isEqualAmortization, final BigDecimal fixedPrincipalPercentagePerInstallment) {
+ final BigDecimal topupAmount, final boolean isEqualAmortization, final BigDecimal fixedPrincipalPercentagePerInstallment,
+ final CreditScorecardData scorecard) {
final LoanScheduleData repaymentSchedule = null;
final Collection<LoanTransactionData> transactions = null;
@@ -1278,6 +1349,9 @@
final Boolean isRatesEnabled = false;
final CollectionData delinquent = CollectionData.template();
+ final Collection<CreditScorecardFeatureData> scorecardFeatures = null;
+ final Collection<CreditScorecardFeatureData> scorecardFeatureOptions = null;
+
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal,
@@ -1297,7 +1371,8 @@
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount,
- isEqualAmortization, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquent);
+ isEqualAmortization, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquent, scorecardFeatures,
+ scorecardFeatureOptions, scorecard);
}
/*
@@ -1321,7 +1396,9 @@
final Collection<LoanTermVariationsData> emiAmountVariations, final Collection<ChargeData> overdueCharges,
final PaidInAdvanceData paidInAdvance, Collection<InterestRatePeriodData> interestRatesPeriods,
final Collection<LoanAccountSummaryData> clientActiveLoanOptions, final List<RateData> rates, final Boolean isRatesEnabled,
- final CollectionData delinquent) {
+ final CollectionData delinquent, final Collection<CreditScorecardFeatureData> scorecardFeatures,
+ final Collection<CreditScorecardFeatureData> scorecardFeatureOptions, final CreditScorecardData scorecard) {
+
LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
if (acc.product != null) {
loanProductConfigurableAttributes = acc.product.getloanProductConfigurableAttributes();
@@ -1351,12 +1428,13 @@
acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, clientActiveLoanOptions, acc.isTopup, acc.closureLoanId,
acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, rates, isRatesEnabled,
- acc.fixedPrincipalPercentagePerInstallment, delinquent);
+ acc.fixedPrincipalPercentagePerInstallment, delinquent, scorecardFeatures, scorecardFeatureOptions, scorecard);
}
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
final Collection<StaffData> allowedLoanOfficers, final Collection<CalendarData> calendarOptions,
- final Collection<PortfolioAccountData> accountLinkingOptions, final Boolean isRatesEnabled) {
+ final Collection<PortfolioAccountData> accountLinkingOptions, final Boolean isRatesEnabled,
+ final CreditScorecardData scorecard) {
return associationsAndTemplate(acc, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
acc.meeting, productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
@@ -1364,7 +1442,8 @@
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges, acc.paidInAdvance,
- acc.interestRatesPeriods, acc.clientActiveLoanOptions, acc.rates, isRatesEnabled, acc.delinquent);
+ acc.interestRatesPeriods, acc.clientActiveLoanOptions, acc.rates, isRatesEnabled, acc.delinquent, acc.scorecardFeatures,
+ acc.scorecardFeatureOptions, scorecard);
}
public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) {
@@ -1394,7 +1473,8 @@
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
- acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
+ acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.scorecardFeatures, acc.scorecardFeatureOptions,
+ acc.scorecard);
}
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
@@ -1460,7 +1540,9 @@
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
- acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
+ acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.scorecardFeatures, acc.scorecardFeatureOptions,
+ acc.scorecard);
+
}
public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData,
@@ -1494,7 +1576,8 @@
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
- acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
+ acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.scorecardFeatures, acc.scorecardFeatureOptions,
+ acc.scorecard);
}
public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
@@ -1523,11 +1606,11 @@
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
- acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
+ acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.scorecardFeatures, acc.scorecardFeatureOptions,
+ acc.scorecard);
}
public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
-
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName,
acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
@@ -1553,7 +1636,8 @@
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
- acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
+ acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.scorecardFeatures, acc.scorecardFeatureOptions,
+ acc.scorecard);
}
private LoanAccountData(final Long id, //
@@ -1606,7 +1690,9 @@
final Integer minimumGap, final Integer maximumGap, final EnumOptionData subStatus, final Boolean canUseForTopup,
final Collection<LoanAccountSummaryData> clientActiveLoanOptions, final boolean isTopup, final Long closureLoanId,
final String closureLoanAccountNo, final BigDecimal topupAmount, final boolean isEqualAmortization, final List<RateData> rates,
- final Boolean isRatesEnabled, final BigDecimal fixedPrincipalPercentagePerInstallment, final CollectionData delinquent) {
+ final Boolean isRatesEnabled, final BigDecimal fixedPrincipalPercentagePerInstallment, final CollectionData delinquent,
+ final Collection<CreditScorecardFeatureData> scorecardFeatures,
+ final Collection<CreditScorecardFeatureData> scorecardFeatureOptions, final CreditScorecardData scorecard) {
this.id = id;
this.accountNo = accountNo;
@@ -1794,6 +1880,16 @@
this.rates = rates;
this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment;
this.delinquent = delinquent;
+
+ this.scorecardFeatures = scorecardFeatures;
+ if (CollectionUtils.isEmpty(scorecardFeatureOptions)) {
+ this.scorecardFeatureOptions = null;
+ } else {
+ this.scorecardFeatureOptions = scorecardFeatureOptions;
+ }
+
+ this.scorecard = scorecard;
+
}
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
@@ -1817,6 +1913,10 @@
return this.charges;
}
+ public Collection<CreditScorecardFeatureData> getScorecardFeatures() {
+ return this.scorecardFeatures;
+ }
+
public Long groupOfficeId() {
return this.group == null ? null : this.group.officeId();
}
@@ -1953,4 +2053,11 @@
return this.status.value();
}
+ public Long getScorecardId() {
+ if (scorecard != null) {
+ return scorecard.getId();
+ } else {
+ return null;
+ }
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 4ac8c25..3fe845e 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -100,6 +100,7 @@
import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
import org.apache.fineract.portfolio.common.domain.NthDayType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.creditscorecard.domain.CreditScorecard;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.fund.domain.Fund;
@@ -420,6 +421,10 @@
@Column(name = "fixed_principal_percentage_per_installment", scale = 2, precision = 5, nullable = true)
private BigDecimal fixedPrincipalPercentagePerInstallment;
+ @OneToOne(cascade = CascadeType.ALL)
+ @JoinColumn(name = "loan_credit_scorecard_id", referencedColumnName = "id")
+ private CreditScorecard scorecard;
+
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
@@ -427,14 +432,15 @@
final Set<LoanCollateralManagement> collateral, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
- final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment) {
+ final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
+ final CreditScorecard scorecard) {
final LoanStatus status = null;
final Group group = null;
final Boolean syncDisbursementWithMeeting = null;
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
- interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment);
+ interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment, scorecard);
}
public static Loan newGroupLoanApplication(final String accountNo, final Group group, final Integer loanType,
@@ -444,13 +450,14 @@
final Set<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
- final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment) {
+ final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
+ final CreditScorecard scorecard) {
final LoanStatus status = null;
final Client client = null;
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
- interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment);
+ interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment, scorecard);
}
public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group,
@@ -460,12 +467,13 @@
final Set<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
- final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment) {
+ final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
+ final CreditScorecard scorecard) {
final LoanStatus status = null;
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
- interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment);
+ interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment, scorecard);
}
protected Loan() {
@@ -479,7 +487,7 @@
final BigDecimal fixedEmiAmount, final List<LoanDisbursementDetails> disbursementDetails,
final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement,
final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> rates,
- final BigDecimal fixedPrincipalPercentagePerInstallment) {
+ final BigDecimal fixedPrincipalPercentagePerInstallment, final CreditScorecard scorecard) {
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings();
@@ -543,6 +551,7 @@
// Add net get net disbursal amount from charges and principal
this.netDisbursalAmount = this.approvedPrincipal.subtract(deriveSumTotalOfChargesDueAtDisbursement());
+ this.scorecard = scorecard;
}
public Integer getNumberOfRepayments() {
@@ -4633,7 +4642,7 @@
break;
}
return dueRepaymentPeriodDate.minusDays(1);// get 2n-1 range date from
- // startDate
+ // startDate
}
public void applyHolidayToRepaymentScheduleDates(final Holiday holiday, final LoanUtilService loanUtilService) {
@@ -6841,4 +6850,12 @@
public void adjustNetDisbursalAmount(BigDecimal adjustedAmount) {
this.netDisbursalAmount = adjustedAmount.subtract(this.deriveSumTotalOfChargesDueAtDisbursement());
}
+
+ public CreditScorecard getScorecard() {
+ return scorecard;
+ }
+
+ public boolean hasScorecard() {
+ return this.scorecard != null;
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
index a3e0f14..711373d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
@@ -1,3 +1,4 @@
+
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -43,6 +44,7 @@
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement;
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagementRepositoryWrapper;
+import org.apache.fineract.portfolio.creditscorecard.serialization.CreditScorecardApiJsonHelper;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
@@ -99,14 +101,17 @@
private final FromJsonHelper fromApiJsonHelper;
private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper;
private final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper;
+ private final CreditScorecardApiJsonHelper scorecardApiJsonHelper;
@Autowired
public LoanApplicationCommandFromApiJsonHelper(final FromJsonHelper fromApiJsonHelper,
final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper,
- final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper) {
+ final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper,
+ final CreditScorecardApiJsonHelper scorecardApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
this.apiJsonHelper = apiJsonHelper;
this.clientCollateralManagementRepositoryWrapper = clientCollateralManagementRepositoryWrapper;
+ this.scorecardApiJsonHelper = scorecardApiJsonHelper;
}
public void validateForCreate(final String json, final boolean isMeetingMandatoryForJLGLoans, final LoanProduct loanProduct) {
@@ -439,6 +444,12 @@
* accounts. (loanType.isJLG() || loanType.isGLIM())
*/
+ // Scorecard
+ final String scorecardParameterName = "scorecard";
+ if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(scorecardParameterName, element)) {
+ this.scorecardApiJsonHelper.validateScorecardJson(element);
+ }
+
if (!StringUtils.isBlank(loanTypeStr)) {
final AccountType loanType = AccountType.fromName(loanTypeStr);
@@ -861,6 +872,13 @@
baseDataValidator.reset().parameter(linkAccountIdParameterName).value(linkAccountId).ignoreIfNull().longGreaterThanZero();
}
+ // Scorecard
+ final String scorecardParameterName = "scorecard";
+ if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(scorecardParameterName, element)) {
+ atLeastOneParameterPassedForUpdate = true;
+ this.scorecardApiJsonHelper.validateScorecardJson(element);
+ }
+
// charges
final String chargesParameterName = "charges";
if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(chargesParameterName, element)) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
index fe97760..5e8bc40 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
@@ -50,6 +50,7 @@
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
@@ -88,6 +89,8 @@
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BusinessEvents;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.creditscorecard.provider.ScorecardServiceProvider;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardWritePlatformService;
import org.apache.fineract.portfolio.fund.domain.Fund;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
@@ -199,6 +202,8 @@
private final LoanCollateralManagementRepository loanCollateralManagementRepository;
private final ClientCollateralManagementRepository clientCollateralManagementRepository;
+ private final ScorecardServiceProvider scorecardServiceProvider;
+
@Autowired
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
final LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator,
@@ -226,7 +231,8 @@
final LoanRepository loanRepository, final GSIMReadPlatformService gsimReadPlatformService, final RateAssembler rateAssembler,
final LoanProductReadPlatformService loanProductReadPlatformService,
final LoanCollateralManagementRepository loanCollateralManagementRepository,
- final ClientCollateralManagementRepository clientCollateralManagementRepository) {
+ final ClientCollateralManagementRepository clientCollateralManagementRepository,
+ final ScorecardServiceProvider scorecardServiceProvider) {
this.context = context;
this.fromJsonHelper = fromJsonHelper;
this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
@@ -269,6 +275,7 @@
this.gsimReadPlatformService = gsimReadPlatformService;
this.loanCollateralManagementRepository = loanCollateralManagementRepository;
this.clientCollateralManagementRepository = clientCollateralManagementRepository;
+ this.scorecardServiceProvider = scorecardServiceProvider;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -530,6 +537,17 @@
}
}
+ if (newLoanApplication.hasScorecard()) {
+ final String serviceName = "CreditScorecardWritePlatformService";
+ final CreditScorecardWritePlatformService scorecardService = (CreditScorecardWritePlatformService) this.scorecardServiceProvider
+ .getScorecardService(serviceName);
+ if (scorecardService == null) {
+ throw new PlatformServiceUnavailableException("err.msg.credit.scorecard.service.implementation.missing",
+ ScorecardServiceProvider.SERVICE_MISSING + serviceName, serviceName);
+ }
+ scorecardService.assessCreditRisk(newLoanApplication);
+ }
+
// Save linked account information
SavingsAccount savingsAccount;
final boolean backdatedTxnsAllowedTill = false;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
index 60f31d1..4b197ab 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.loanaccount.service;
import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.ZoneId;
@@ -35,6 +36,7 @@
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
@@ -52,6 +54,9 @@
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManagementDomain;
import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralAssembler;
+import org.apache.fineract.portfolio.creditscorecard.domain.CreditScorecard;
+import org.apache.fineract.portfolio.creditscorecard.provider.ScorecardServiceProvider;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardAssembler;
import org.apache.fineract.portfolio.fund.domain.Fund;
import org.apache.fineract.portfolio.fund.domain.FundRepository;
import org.apache.fineract.portfolio.fund.exception.FundNotFoundException;
@@ -115,6 +120,7 @@
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanUtilService loanUtilService;
private final RateAssembler rateAssembler;
+ private final ScorecardServiceProvider scorecardServiceProvider;
@Autowired
public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanRepositoryWrapper loanRepository,
@@ -126,7 +132,8 @@
final LoanCollateralAssembler collateralAssembler, final LoanSummaryWrapper loanSummaryWrapper,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
- final WorkingDaysRepositoryWrapper workingDaysRepository, final LoanUtilService loanUtilService, RateAssembler rateAssembler) {
+ final WorkingDaysRepositoryWrapper workingDaysRepository, final LoanUtilService loanUtilService, RateAssembler rateAssembler,
+ final ScorecardServiceProvider scorecardServiceProvider) {
this.fromApiJsonHelper = fromApiJsonHelper;
this.loanRepository = loanRepository;
this.loanProductRepository = loanProductRepository;
@@ -146,6 +153,7 @@
this.workingDaysRepository = workingDaysRepository;
this.loanUtilService = loanUtilService;
this.rateAssembler = rateAssembler;
+ this.scorecardServiceProvider = scorecardServiceProvider;
}
public Loan assembleFrom(final Long accountId) {
@@ -270,6 +278,23 @@
BigDecimal fixedPrincipalPercentagePerInstallment = fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element);
+ final JsonObject scorecardElement = this.fromApiJsonHelper.extractJsonObjectNamed("scorecard", element);
+ CreditScorecard scorecard = null;
+
+ if (scorecardElement != null) {
+ final String scoringMethod = scorecardElement.get("scoringMethod").getAsString();
+ if (scoringMethod != null) {
+ final String serviceName = "CreditScorecardAssembler";
+ final CreditScorecardAssembler scorecardAssembler = (CreditScorecardAssembler) this.scorecardServiceProvider
+ .getScorecardService(serviceName);
+ if (scorecardAssembler == null) {
+ throw new PlatformServiceUnavailableException("err.msg.credit.scorecard.service.implementation.missing",
+ ScorecardServiceProvider.SERVICE_MISSING + serviceName, serviceName);
+ }
+ scorecard = scorecardAssembler.assembleFrom(scorecardElement);
+ }
+ }
+
Loan loanApplication = null;
Client client = null;
Group group = null;
@@ -308,21 +333,21 @@
fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, null,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
- fixedPrincipalPercentagePerInstallment);
+ fixedPrincipalPercentagePerInstallment, scorecard);
} else if (group != null) {
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, null,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
- fixedPrincipalPercentagePerInstallment);
+ fixedPrincipalPercentagePerInstallment, scorecard);
} else if (client != null) {
loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund,
loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral,
fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement,
- isFloatingInterestRate, interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment);
+ isFloatingInterestRate, interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment, scorecard);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 0d8e305..3939908 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -73,6 +73,10 @@
import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardData;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardFeatureData;
+import org.apache.fineract.portfolio.creditscorecard.provider.ScorecardServiceProvider;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardReadPlatformService;
import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.fund.data.FundData;
@@ -150,7 +154,7 @@
private final CalendarReadPlatformService calendarReadPlatformService;
private final StaffReadPlatformService staffReadPlatformService;
private final PaginationHelper paginationHelper;
- private final LoanMapper loaanLoanMapper;
+ private final LoanMapper loanMapper;
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
@@ -161,6 +165,7 @@
private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
private final ColumnValidator columnValidator;
private final DatabaseSpecificSQLGenerator sqlGenerator;
+ private final ScorecardServiceProvider scorecardServiceProvider;
@Autowired
public LoanReadPlatformServiceImpl(final PlatformSecurityContext context,
@@ -175,7 +180,8 @@
final FloatingRatesReadPlatformService floatingRatesReadPlatformService, final LoanUtilService loanUtilService,
final ConfigurationDomainService configurationDomainService,
final AccountDetailsReadPlatformService accountDetailsReadPlatformService, final LoanRepositoryWrapper loanRepositoryWrapper,
- final ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
+ final ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper,
+ final ScorecardServiceProvider scorecardServiceProvider) {
this.context = context;
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.applicationCurrencyRepository = applicationCurrencyRepository;
@@ -197,9 +203,10 @@
this.configurationDomainService = configurationDomainService;
this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
this.columnValidator = columnValidator;
- this.loaanLoanMapper = new LoanMapper(sqlGenerator);
+ this.loanMapper = new LoanMapper(sqlGenerator);
this.sqlGenerator = sqlGenerator;
this.paginationHelper = paginationHelper;
+ this.scorecardServiceProvider = scorecardServiceProvider;
}
@Override
@@ -301,7 +308,7 @@
final StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
- sqlBuilder.append(this.loaanLoanMapper.loanSchema());
+ sqlBuilder.append(this.loanMapper.loanSchema());
// TODO - for time being this will data scope list of loans returned to
// only loans that have a client associated.
@@ -365,7 +372,7 @@
}
final Object[] objectArray = extraCriterias.toArray();
final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
- return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), finalObjectArray, this.loaanLoanMapper);
+ return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), finalObjectArray, this.loanMapper);
}
@Override
@@ -671,7 +678,11 @@
+ " lpvi.minimum_gap as minimuminstallmentgap, lpvi.maximum_gap as maximuminstallmentgap, "
+ " lp.can_use_for_topup as canUseForTopup, " + " l.is_topup as isTopup, " + " topup.closure_loan_id as closureLoanId, "
+ " l.total_recovered_derived as totalRecovered" + ", topuploan.account_no as closureLoanAccountNo, "
- + " topup.topup_amount as topupAmount " + " from m_loan l" //
+ + " topup.topup_amount as topupAmount, "
+ + " csc.id as scorecardId, csc.scorecard_scoring_method as scoringMethod, csc.scorecard_scoring_model as scoringModel "
+
+ + " from m_loan l" //
+
+ " join m_product_loan lp on lp.id = l.product_id" //
+ " left join m_loan_recalculation_details lir on lir.loan_id = l.id " + " join m_currency rc on rc."
+ sqlGenerator.escape("code") + " = l.currency_code" //
@@ -690,7 +701,8 @@
+ " left join ref_loan_transaction_processing_strategy lps on lps.id = l.loan_transaction_strategy_id"
+ " left join m_product_loan_variable_installment_config lpvi on lpvi.loan_product_id = l.product_id"
+ " left join m_loan_topup as topup on l.id = topup.loan_id"
- + " left join m_loan as topuploan on topuploan.id = topup.closure_loan_id";
+ + " left join m_loan as topuploan on topuploan.id = topup.closure_loan_id"
+ + " left join m_credit_scorecard as csc on csc.id = l.loan_credit_scorecard_id";
}
@@ -995,6 +1007,11 @@
final String closureLoanAccountNo = rs.getString("closureLoanAccountNo");
final BigDecimal topupAmount = rs.getBigDecimal("topupAmount");
+ final Long scorecardId = rs.getLong("scorecardId");
+ final String scoringMethod = rs.getString("scoringMethod");
+ final String scoringModel = rs.getString("scoringModel");
+ final CreditScorecardData loanScorecardDetails = CreditScorecardData.instance(scorecardId, scoringMethod, scoringModel);
+
return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName,
clientOfficeId, groupData, loanType, loanProductId, loanProductName, loanProductDescription,
isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -1009,7 +1026,7 @@
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData,
createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap, maximumGap, loanSubStatus,
canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization,
- fixedPrincipalPercentagePerInstallment);
+ fixedPrincipalPercentagePerInstallment, loanScorecardDetails);
}
}
@@ -1458,10 +1475,21 @@
activeLoanOptions = this.accountDetailsReadPlatformService.retrieveGroupActiveLoanAccountSummary(groupId);
}
+ Collection<CreditScorecardFeatureData> scorecardFeatureOptions = null;
+
+ final String serviceName = "CreditScorecardReadPlatformService";
+ final CreditScorecardReadPlatformService scorecardService = (CreditScorecardReadPlatformService) scorecardServiceProvider
+ .getScorecardService(serviceName);
+
+ if (scorecardService != null) {
+ scorecardFeatureOptions = scorecardService.retrieveLoanProductFeatures(productId);
+ }
+
return LoanAccountData.loanProductWithTemplateDefaults(loanProduct, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDaysOfWeekTypeOptions, repaymentStrategyOptions,
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
- fundOptions, chargeOptions, loanPurposeOptions, loanCollateralOptions, loanCycleCounter, activeLoanOptions);
+ fundOptions, chargeOptions, loanPurposeOptions, loanCollateralOptions, loanCycleCounter, activeLoanOptions,
+ scorecardFeatureOptions);
}
@Override
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
index 0799ebf..12f7228 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
@@ -33,6 +33,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
@@ -65,6 +66,10 @@
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardFeatureData;
+import org.apache.fineract.portfolio.creditscorecard.domain.CreditScorecardFeature;
+import org.apache.fineract.portfolio.creditscorecard.provider.ScorecardServiceProvider;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardReadPlatformService;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.fund.data.FundData;
@@ -130,6 +135,7 @@
private final FloatingRatesReadPlatformService floatingRateReadPlatformService;
private final RateReadService rateReadService;
private final ConfigurationDomainService configurationDomainService;
+ private final ScorecardServiceProvider scorecardServiceProvider;
@Autowired
public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService,
@@ -145,7 +151,7 @@
final DropdownReadPlatformService commonDropdownReadPlatformService,
PaymentTypeReadPlatformService paymentTypeReadPlatformService,
final FloatingRatesReadPlatformService floatingRateReadPlatformService, final RateReadService rateReadService,
- final ConfigurationDomainService configurationDomainService) {
+ final ConfigurationDomainService configurationDomainService, final ScorecardServiceProvider scorecardServiceProvider) {
this.context = context;
this.loanProductReadPlatformService = readPlatformService;
this.chargeReadPlatformService = chargeReadPlatformService;
@@ -164,6 +170,7 @@
this.floatingRateReadPlatformService = floatingRateReadPlatformService;
this.rateReadService = rateReadService;
this.configurationDomainService = configurationDomainService;
+ this.scorecardServiceProvider = scorecardServiceProvider;
}
@POST
@@ -366,13 +373,24 @@
.retrivePreCloseInterestCalculationStrategyOptions();
final List<FloatingRateData> floatingRateOptions = this.floatingRateReadPlatformService.retrieveLookupActive();
+ Collection<CreditScorecardFeatureData> scorecardFeatureOptions = null;
+
+ final String serviceName = "CreditScorecardReadPlatformService";
+ final CreditScorecardReadPlatformService scorecardService = (CreditScorecardReadPlatformService) scorecardServiceProvider
+ .getScorecardService(serviceName);
+
+ if (scorecardService != null) {
+ scorecardFeatureOptions = scorecardService.findAllFeaturesWithNotFoundDetection().stream().map(CreditScorecardFeature::toData)
+ .collect(Collectors.toList());
+ }
+
return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions,
fundOptions, transactionProcessingStrategyOptions, rateOptions, accountOptions, accountingRuleTypeOptions,
loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions,
interestRecalculationCompoundingTypeOptions, rescheduleStrategyTypeOptions, interestRecalculationFrequencyTypeOptions,
preCloseInterestCalculationStrategyOptions, floatingRateOptions, interestRecalculationNthDayTypeOptions,
- interestRecalculationDayOfWeekTypeOptions, isRatesEnabled);
+ interestRecalculationDayOfWeekTypeOptions, isRatesEnabled, scorecardFeatureOptions);
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
index 40077a8..1b42056 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
@@ -39,6 +39,7 @@
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardFeatureData;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
import org.apache.fineract.portfolio.fund.data.FundData;
import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
@@ -198,6 +199,9 @@
private final boolean isEqualAmortization;
private final BigDecimal fixedPrincipalPercentagePerInstallment;
+ private final Collection<CreditScorecardFeatureData> scorecardFeatures;
+ private final Collection<CreditScorecardFeatureData> scorecardFeatureOptions;
+
/**
* Used when returning lookup information about loan product for dropdowns.
*/
@@ -282,6 +286,9 @@
final Collection<RateData> rateOptions = null;
final Collection<RateData> rates = null;
final boolean isRatesEnabled = false;
+
+ final Collection<CreditScorecardFeatureData> scorecardFeatures = null;
+
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
@@ -298,7 +305,7 @@
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment);
+ fixedPrincipalPercentagePerInstallment, scorecardFeatures);
}
@@ -385,6 +392,8 @@
final Collection<RateData> rates = null;
final boolean isRatesEnabled = false;
+ final Collection<CreditScorecardFeatureData> scorecardFeatures = null;
+
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
@@ -401,7 +410,7 @@
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment);
+ fixedPrincipalPercentagePerInstallment, scorecardFeatures);
}
@@ -495,6 +504,8 @@
final Collection<RateData> rates = null;
final boolean isRatesEnabled = false;
+ final Collection<CreditScorecardFeatureData> scorecardFeatures = null;
+
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
@@ -511,7 +522,7 @@
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment);
+ fixedPrincipalPercentagePerInstallment, scorecardFeatures);
}
@@ -599,6 +610,8 @@
final Collection<RateData> rates = null;
final boolean isRatesEnabled = false;
+ final Collection<CreditScorecardFeatureData> scorecardFeatures = null;
+
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
@@ -615,7 +628,7 @@
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment);
+ fixedPrincipalPercentagePerInstallment, scorecardFeatures);
}
@@ -660,7 +673,7 @@
final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments,
final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization,
Collection<RateData> rateOptions, Collection<RateData> rates, final boolean isRatesEnabled,
- final BigDecimal fixedPrincipalPercentagePerInstallment) {
+ final BigDecimal fixedPrincipalPercentagePerInstallment, final Collection<CreditScorecardFeatureData> scorecardFeatures) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -775,6 +788,9 @@
this.canUseForTopup = canUseForTopup;
this.isEqualAmortization = isEqualAmortization;
+ this.scorecardFeatures = scorecardFeatures;
+ this.scorecardFeatureOptions = null;
+
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -790,7 +806,8 @@
final List<EnumOptionData> rescheduleStrategyTypeOptions, final List<EnumOptionData> interestRecalculationFrequencyTypeOptions,
final List<EnumOptionData> preCloseInterestCalculationStrategyOptions, final List<FloatingRateData> floatingRateOptions,
final List<EnumOptionData> interestRecalculationNthDayTypeOptions,
- final List<EnumOptionData> interestRecalculationDayOfWeekTypeOptions, final boolean isRatesEnabled) {
+ final List<EnumOptionData> interestRecalculationDayOfWeekTypeOptions, final boolean isRatesEnabled,
+ final Collection<CreditScorecardFeatureData> scorecardFeatureOptions) {
this.id = productData.id;
this.name = productData.name;
this.shortName = productData.shortName;
@@ -921,6 +938,9 @@
this.isEqualAmortization = productData.isEqualAmortization;
this.rates = productData.rates;
this.isRatesEnabled = isRatesEnabled;
+
+ this.scorecardFeatures = productData.scorecardFeatures;
+ this.scorecardFeatureOptions = scorecardFeatureOptions;
}
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
@@ -1343,4 +1363,8 @@
public BigDecimal getFixedPrincipalPercentagePerInstallment() {
return fixedPrincipalPercentagePerInstallment;
}
+
+ public Collection<CreditScorecardFeatureData> scorecardFeatures() {
+ return this.scorecardFeatures;
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
index 4413a4f..1e5ffe1 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
@@ -202,9 +202,13 @@
@Column(name = "over_applied_number", nullable = true)
private Integer overAppliedNumber;
+ @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
+ @JoinColumn(name = "product_loan_id", referencedColumnName = "id", nullable = false)
+ private List<LoanProductScorecardFeature> scorecardFeatures;
+
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate,
- final List<Rate> productRates) {
+ final List<Rate> productRates, List<LoanProductScorecardFeature> scorecardFeatures) {
final String name = command.stringValueOfParameterNamed("name");
final String shortName = command.stringValueOfParameterNamed(LoanProductConstants.SHORT_NAME);
@@ -392,7 +396,7 @@
defaultDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed,
minimumGapBetweenInstallments, maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, productRates, fixedPrincipalPercentagePerInstallment, disallowExpectedDisbursements,
- allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber);
+ allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber, scorecardFeatures);
}
@@ -629,7 +633,7 @@
final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization,
final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean disallowExpectedDisbursements,
final boolean allowApprovedDisbursedAmountsOverApplied, final String overAppliedCalculationType,
- final Integer overAppliedNumber) {
+ final Integer overAppliedNumber, List<LoanProductScorecardFeature> scorecardFeatures) {
this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim();
@@ -716,6 +720,11 @@
if (rates != null) {
this.rates = rates;
}
+
+ if (scorecardFeatures != null) {
+ this.scorecardFeatures = scorecardFeatures;
+ }
+
validateLoanProductPreSave();
}
@@ -939,6 +948,14 @@
}
}
+ final String scorecardFeaturesParamName = "scorecardFeatures";
+ if (command.hasParameter(scorecardFeaturesParamName)) {
+ final JsonArray jsonArray = command.arrayOfParameterNamed(scorecardFeaturesParamName);
+ if (jsonArray != null) {
+ actualChanges.put(scorecardFeaturesParamName, command.jsonFragment(scorecardFeaturesParamName));
+ }
+ }
+
final String includeInBorrowerCycleParamName = "includeInBorrowerCycle";
if (command.isChangeInBooleanParameterNamed(includeInBorrowerCycleParamName, this.includeInBorrowerCycle)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(includeInBorrowerCycleParamName);
@@ -1608,4 +1625,28 @@
this.loanProducTrancheDetails = loanProducTrancheDetails;
}
+ public boolean updateScorecardFeatures(List<LoanProductScorecardFeature> newScorecardFeatures) {
+ if (newScorecardFeatures == null) {
+ return false;
+ }
+
+ boolean updated = false;
+ if (this.scorecardFeatures != null) {
+ final Set<LoanProductScorecardFeature> currentSetOfFeatures = new HashSet<>(this.scorecardFeatures);
+ final Set<LoanProductScorecardFeature> newSetOfFeatures = new HashSet<>(newScorecardFeatures);
+
+ if (!currentSetOfFeatures.equals(newSetOfFeatures)) {
+ updated = true;
+ this.scorecardFeatures = newScorecardFeatures;
+ }
+ } else {
+ updated = true;
+ this.scorecardFeatures = newScorecardFeatures;
+ }
+ return updated;
+ }
+
+ public List<LoanProductScorecardFeature> getScorecardFeatures() {
+ return scorecardFeatures;
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java
index 143802d..1165164 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java
@@ -28,4 +28,7 @@
@Query("select loanProduct from LoanProduct loanProduct, IN(loanProduct.charges) charge where charge.id = :chargeId")
List<LoanProduct> retrieveLoanProductsByChargeId(@Param("chargeId") Long chargeId);
+
+ @Query("select loanProduct from LoanProduct loanProduct, IN(loanProduct.scorecardFeatures) feature where feature.id = :featureId")
+ List<LoanProduct> retrieveLoanProductsByScorecardFeatureId(@Param("featureId") Long featureId);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductScorecardFeature.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductScorecardFeature.java
new file mode 100644
index 0000000..7fa641c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductScorecardFeature.java
@@ -0,0 +1,95 @@
+/**
+ * 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.loanproduct.domain;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Objects;
+import javax.persistence.CascadeType;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+import org.apache.fineract.portfolio.creditscorecard.domain.CreditScorecardFeature;
+import org.apache.fineract.portfolio.creditscorecard.domain.FeatureConfiguration;
+import org.apache.fineract.portfolio.creditscorecard.domain.FeatureCriteria;
+
+@Entity
+@Table(name = "m_product_loan_scorecard_feature")
+public class LoanProductScorecardFeature extends AbstractPersistableCustom {
+
+ @ManyToOne(optional = false)
+ @JoinColumn(name = "scorecard_feature_id", referencedColumnName = "id", nullable = false)
+ private CreditScorecardFeature scorecardFeature;
+
+ @Embedded
+ private FeatureConfiguration configuration;
+
+ @OneToMany(cascade = { CascadeType.PERSIST, CascadeType.DETACH }, orphanRemoval = true, fetch = FetchType.EAGER)
+ @JoinColumn(name = "product_loan_scorecard_feature_id", referencedColumnName = "id")
+ private List<FeatureCriteria> featureCriteria;
+
+ public LoanProductScorecardFeature() {
+ //
+ }
+
+ public LoanProductScorecardFeature(final CreditScorecardFeature scorecardFeature, final BigDecimal weightage, final Integer greenMin,
+ final Integer greenMax, final Integer amberMin, final Integer amberMax, final Integer redMin, final Integer redMax) {
+ this.scorecardFeature = scorecardFeature;
+ this.configuration = FeatureConfiguration.from(weightage, greenMin, greenMax, amberMin, amberMax, redMin, redMax);
+ }
+
+ public FeatureConfiguration getFeatureConfiguration() {
+ return configuration;
+ }
+
+ public CreditScorecardFeature getScorecardFeature() {
+ return scorecardFeature;
+ }
+
+ public List<FeatureCriteria> getFeatureCriteria() {
+ return featureCriteria;
+ }
+
+ public void setFeatureCriteria(List<FeatureCriteria> featureCriteria) {
+ this.featureCriteria = featureCriteria;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof LoanProductScorecardFeature)) {
+ return false;
+ }
+ LoanProductScorecardFeature that = (LoanProductScorecardFeature) o;
+ return Objects.equals(configuration, that.configuration) && Objects.equals(scorecardFeature, that.scorecardFeature)
+ && Objects.equals(featureCriteria, that.featureCriteria);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(configuration, scorecardFeature, featureCriteria);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductScorecardFeatureRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductScorecardFeatureRepository.java
new file mode 100644
index 0000000..939b3a6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductScorecardFeatureRepository.java
@@ -0,0 +1,25 @@
+/**
+ * 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.loanproduct.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface LoanProductScorecardFeatureRepository
+ extends JpaRepository<LoanProductScorecardFeature, Long>, JpaSpecificationExecutor<LoanProductScorecardFeature> {}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductScorecardFeatureRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductScorecardFeatureRepositoryWrapper.java
new file mode 100644
index 0000000..ee2a940
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductScorecardFeatureRepositoryWrapper.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanproduct.domain;
+
+import java.util.List;
+import org.apache.fineract.portfolio.creditscorecard.domain.FeatureNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * <p>
+ * Wrapper for {@link LoanProductScorecardFeatureRepository} that adds NULL checking and Error handling capabilities
+ * </p>
+ */
+@Service
+public class LoanProductScorecardFeatureRepositoryWrapper {
+
+ private final LoanProductScorecardFeatureRepository repository;
+
+ @Autowired
+ public LoanProductScorecardFeatureRepositoryWrapper(final LoanProductScorecardFeatureRepository repository) {
+ this.repository = repository;
+ }
+
+ @Transactional(readOnly = true)
+ public LoanProductScorecardFeature findOneWithNotFoundDetection(final Long id) {
+ return this.repository.findById(id).orElseThrow(() -> new FeatureNotFoundException(id));
+ }
+
+ public LoanProductScorecardFeature saveAndFlush(final LoanProductScorecardFeature scorecardFeature) {
+ return this.repository.saveAndFlush(scorecardFeature);
+ }
+
+ @Transactional
+ public LoanProductScorecardFeature save(final LoanProductScorecardFeature scorecardFeature) {
+ return this.repository.save(scorecardFeature);
+ }
+
+ public List<LoanProductScorecardFeature> save(List<LoanProductScorecardFeature> scorecardFeatures) {
+ return this.repository.saveAll(scorecardFeatures);
+ }
+
+ public void flush() {
+ this.repository.flush();
+ }
+
+ public void delete(final Long featureId) {
+ this.repository.deleteById(featureId);
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index d428672..e926151 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -71,7 +71,7 @@
"graceOnInterestPayment", "graceOnInterestCharged", "charges", "accountingRule", "includeInBorrowerCycle", "startDate",
"closeDate", "externalId", "isLinkedToFloatingInterestRates", "floatingRatesId", "interestRateDifferential",
"minDifferentialLendingRate", "defaultDifferentialLendingRate", "maxDifferentialLendingRate",
- "isFloatingInterestRateCalculationAllowed", "syncExpectedWithDisbursementDate",
+ "isFloatingInterestRateCalculationAllowed", "syncExpectedWithDisbursementDate", "scorecardFeatures",
LoanProductAccountingParams.FEES_RECEIVABLE.getValue(), LoanProductAccountingParams.FUND_SOURCE.getValue(),
LoanProductAccountingParams.INCOME_FROM_FEES.getValue(), LoanProductAccountingParams.INCOME_FROM_PENALTIES.getValue(),
LoanProductAccountingParams.INTEREST_ON_LOANS.getValue(), LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(),
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
index ac60427..6371dfd 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
@@ -36,6 +36,9 @@
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
import org.apache.fineract.portfolio.common.service.CommonEnumerations;
+import org.apache.fineract.portfolio.creditscorecard.data.CreditScorecardFeatureData;
+import org.apache.fineract.portfolio.creditscorecard.provider.ScorecardServiceProvider;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductGuaranteeData;
@@ -60,18 +63,20 @@
private final RateReadService rateReadService;
private final DatabaseSpecificSQLGenerator sqlGenerator;
private final FineractEntityAccessUtil fineractEntityAccessUtil;
+ private final ScorecardServiceProvider scorecardServiceProvider;
@Autowired
public LoanProductReadPlatformServiceImpl(final PlatformSecurityContext context,
final ChargeReadPlatformService chargeReadPlatformService, final JdbcTemplate jdbcTemplate,
final FineractEntityAccessUtil fineractEntityAccessUtil, final RateReadService rateReadService,
- DatabaseSpecificSQLGenerator sqlGenerator) {
+ DatabaseSpecificSQLGenerator sqlGenerator, final ScorecardServiceProvider scorecardServiceProvider) {
this.context = context;
this.chargeReadPlatformService = chargeReadPlatformService;
this.jdbcTemplate = jdbcTemplate;
this.fineractEntityAccessUtil = fineractEntityAccessUtil;
this.rateReadService = rateReadService;
this.sqlGenerator = sqlGenerator;
+ this.scorecardServiceProvider = scorecardServiceProvider;
}
@Override
@@ -82,7 +87,18 @@
final Collection<RateData> rates = this.rateReadService.retrieveProductLoanRates(loanProductId);
final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas = retrieveLoanProductBorrowerCycleVariations(
loanProductId);
- final LoanProductMapper rm = new LoanProductMapper(charges, borrowerCycleVariationDatas, rates);
+
+ final String serviceName = "CreditScorecardReadPlatformService";
+ final CreditScorecardReadPlatformService scorecardService = (CreditScorecardReadPlatformService) scorecardServiceProvider
+ .getScorecardService(serviceName);
+
+ Collection<CreditScorecardFeatureData> scorecardFeatures = null;
+
+ if (scorecardService != null) {
+ scorecardFeatures = scorecardService.retrieveLoanProductFeatures(loanProductId);
+ }
+
+ final LoanProductMapper rm = new LoanProductMapper(charges, borrowerCycleVariationDatas, rates, scorecardFeatures);
final String sql = "select " + rm.loanProductSchema() + " where lp.id = ?";
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanProductId }); // NOSONAR
@@ -104,7 +120,7 @@
this.context.authenticatedUser();
- final LoanProductMapper rm = new LoanProductMapper(null, null, null);
+ final LoanProductMapper rm = new LoanProductMapper(null, null, null, null);
String sql = "select " + rm.loanProductSchema();
@@ -183,11 +199,15 @@
private final Collection<RateData> rates;
+ private final Collection<CreditScorecardFeatureData> scorecardFeatures;
+
LoanProductMapper(final Collection<ChargeData> charges,
- final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas, final Collection<RateData> rates) {
+ final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas, final Collection<RateData> rates,
+ final Collection<CreditScorecardFeatureData> scorecardFeatures) {
this.charges = charges;
this.borrowerCycleVariationDatas = borrowerCycleVariationDatas;
this.rates = rates;
+ this.scorecardFeatures = scorecardFeatures;
}
public String loanProductSchema() {
@@ -482,7 +502,7 @@
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap,
maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, this.rates,
- isRatesEnabled, fixedPrincipalPercentagePerInstallment);
+ isRatesEnabled, fixedPrincipalPercentagePerInstallment, this.scorecardFeatures);
}
}
@@ -558,7 +578,7 @@
public Collection<LoanProductData> retrieveAllLoanProductsForCurrency(String currencyCode) {
this.context.authenticatedUser();
- final LoanProductMapper rm = new LoanProductMapper(null, null, null);
+ final LoanProductMapper rm = new LoanProductMapper(null, null, null, null);
String sql = "select " + rm.loanProductSchema() + " where lp.currency_code= ? ";
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
index 9b0108b..7bf183a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
@@ -32,6 +32,7 @@
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessType;
import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAccessUtil;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
@@ -41,6 +42,8 @@
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BusinessEntity;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BusinessEvents;
import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.creditscorecard.provider.ScorecardServiceProvider;
+import org.apache.fineract.portfolio.creditscorecard.service.CreditScorecardAssembler;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper;
import org.apache.fineract.portfolio.fund.domain.Fund;
@@ -53,6 +56,7 @@
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductScorecardFeature;
import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductCannotBeModifiedDueToNonClosedLoansException;
@@ -86,6 +90,7 @@
private final FloatingRateRepositoryWrapper floatingRateRepository;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final BusinessEventNotifierService businessEventNotifierService;
+ private final ScorecardServiceProvider scorecardServiceProvider;
@Autowired
public LoanProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -95,7 +100,8 @@
final ChargeRepositoryWrapper chargeRepository, final RateRepositoryWrapper rateRepository,
final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
final FineractEntityAccessUtil fineractEntityAccessUtil, final FloatingRateRepositoryWrapper floatingRateRepository,
- final LoanRepositoryWrapper loanRepositoryWrapper, final BusinessEventNotifierService businessEventNotifierService) {
+ final LoanRepositoryWrapper loanRepositoryWrapper, final BusinessEventNotifierService businessEventNotifierService,
+ final ScorecardServiceProvider scorecardServiceProvider) {
this.context = context;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanProductRepository = loanProductRepository;
@@ -109,6 +115,7 @@
this.floatingRateRepository = floatingRateRepository;
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.businessEventNotifierService = businessEventNotifierService;
+ this.scorecardServiceProvider = scorecardServiceProvider;
}
@Transactional
@@ -132,13 +139,29 @@
final List<Charge> charges = assembleListOfProductCharges(command, currencyCode);
final List<Rate> rates = assembleListOfProductRates(command);
+ List<LoanProductScorecardFeature> scorecardFeatures = null;
+ if (command.parameterExists("scorecardFeatures")) {
+ final JsonArray featuresArray = command.arrayOfParameterNamed("scorecardFeatures");
+ if (!featuresArray.isEmpty()) {
+
+ final String serviceName = "CreditScorecardAssembler";
+ final CreditScorecardAssembler scorecardService = (CreditScorecardAssembler) this.scorecardServiceProvider
+ .getScorecardService(serviceName);
+ if (scorecardService == null) {
+ throw new PlatformServiceUnavailableException("err.msg.credit.scorecard.service.implementation.missing",
+ ScorecardServiceProvider.SERVICE_MISSING + serviceName, serviceName);
+ }
+ scorecardFeatures = scorecardService.assembleListOfProductScoringFeatures(command, null);
+ }
+ }
+
FloatingRate floatingRate = null;
if (command.parameterExists("floatingRatesId")) {
floatingRate = this.floatingRateRepository
.findOneWithNotFoundDetection(command.longValueOfParameterNamed("floatingRatesId"));
}
final LoanProduct loanproduct = LoanProduct.assembleFromJson(fund, loanTransactionProcessingStrategy, charges, command,
- this.aprCalculator, floatingRate, rates);
+ this.aprCalculator, floatingRate, rates, scorecardFeatures);
loanproduct.updateLoanProductInRelatedClasses();
this.loanProductRepository.saveAndFlush(loanproduct);
@@ -251,6 +274,29 @@
}
}
+ if (changes.containsKey("scorecardFeatures")) {
+
+ List<LoanProductScorecardFeature> scorecardFeatures = null;
+ if (command.parameterExists("scorecardFeatures")) {
+ final JsonArray featuresArray = command.arrayOfParameterNamed("scorecardFeatures");
+ if (!featuresArray.isEmpty()) {
+ final String serviceName = "CreditScorecardAssembler";
+ final CreditScorecardAssembler scorecardService = (CreditScorecardAssembler) this.scorecardServiceProvider
+ .getScorecardService(serviceName);
+ if (scorecardService == null) {
+ throw new PlatformServiceUnavailableException("err.msg.credit.scorecard.service.implementation.missing",
+ ScorecardServiceProvider.SERVICE_MISSING + serviceName, serviceName);
+ }
+ scorecardFeatures = scorecardService.assembleListOfProductScoringFeatures(command, product);
+ }
+ }
+
+ final boolean updated = product.updateScorecardFeatures(scorecardFeatures);
+ if (!updated) {
+ changes.remove("scorecardFeatures");
+ }
+ }
+
if (!changes.isEmpty()) {
product.validateLoanProductPreSave();
this.loanProductRepository.saveAndFlush(product);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java
index f52a7d9..9bda3c7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java
@@ -22,6 +22,7 @@
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
+import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@@ -40,19 +41,19 @@
@Table(name = "m_note")
public class Note extends AbstractAuditableCustom {
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_id", nullable = true)
private Client client;
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "group_id", nullable = true)
private Group group;
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "loan_id", nullable = true)
private Loan loan;
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "loan_transaction_id", nullable = true)
private LoanTransaction loanTransaction;
@@ -62,15 +63,15 @@
@Column(name = "note_type_enum")
private Integer noteTypeId;
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "savings_account_id", nullable = true)
private SavingsAccount savingsAccount;
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "savings_account_transaction_id", nullable = true)
private SavingsAccountTransaction savingsTransaction;
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "share_account_id", nullable = true)
private ShareAccount shareAccount;
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 7dc8189..e02cfba 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -30,4 +30,5 @@
<include file="parts/0008_loan_charge_add_external_id.xml" relativeToChangelogFile="true"/>
<include file="parts/0009_hold_reason_savings_account.xml" relativeToChangelogFile="true"/>
<include file="parts/0010_lien_allowed_on_savings_account_products.xml" relativeToChangelogFile="true"/>
+ <include file="parts/0011_credit_scorecard.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0011_credit_scorecard.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0011_credit_scorecard.xml
new file mode 100644
index 0000000..819d8b3
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0011_credit_scorecard.xml
@@ -0,0 +1,273 @@
+<?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="xurror" id="1">
+ <createTable tableName="m_credit_scorecard_feature">
+ <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="value_type_enum" type="SMALLINT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="data_type_enum" type="SMALLINT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="category_enum" type="SMALLINT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="is_active" type="boolean">
+ <constraints nullable="false"/>
+ </column>
+ <column defaultValueBoolean="false" name="is_deleted" type="boolean">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
+ </changeSet>
+
+ <changeSet author="xurror" id="2">
+ <insert tableName="m_permission">
+ <column name="grouping" value="portfolio"/>
+ <column name="code" value="READ_CREDIT_SCORECARD_FEATURE"/>
+ <column name="entity_name" value="CREDIT_SCORECARD_FEATURE"/>
+ <column name="action_name" value="READ"/>
+ <column name="can_maker_checker" valueBoolean="false"/>
+ </insert>
+ <insert tableName="m_permission">
+ <column name="grouping" value="portfolio"/>
+ <column name="code" value="CREATE_CREDIT_SCORECARD_FEATURE"/>
+ <column name="entity_name" value="CREDIT_SCORECARD_FEATURE"/>
+ <column name="action_name" value="CREATE"/>
+ <column name="can_maker_checker" valueBoolean="false"/>
+ </insert>
+ <insert tableName="m_permission">
+ <column name="grouping" value="portfolio"/>
+ <column name="code" value="UPDATE_CREDIT_SCORECARD_FEATURE"/>
+ <column name="entity_name" value="CREDIT_SCORECARD_FEATURE"/>
+ <column name="action_name" value="UPDATE"/>
+ <column name="can_maker_checker" valueBoolean="false"/>
+ </insert>
+ <insert tableName="m_permission">
+ <column name="grouping" value="portfolio"/>
+ <column name="code" value="DELETE_CREDIT_SCORECARD_FEATURE"/>
+ <column name="entity_name" value="CREDIT_SCORECARD_FEATURE"/>
+ <column name="action_name" value="DELETE"/>
+ <column name="can_maker_checker" valueBoolean="false"/>
+ </insert>
+ </changeSet>
+
+ <changeSet author="xurror" id="3">
+ <createTable tableName="m_product_loan_scorecard_feature">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column name="product_loan_id" type="BIGINT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="scorecard_feature_id" type="BIGINT">
+ <constraints nullable="false"/>
+ </column>
+ <column defaultValueNumeric="0.000000" name="weightage" type="DECIMAL(6, 5)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="green_min" type="INT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="green_max" type="INT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="amber_min" type="INT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="amber_max" type="INT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="red_min" type="INT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="red_max" type="INT">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
+ </changeSet>
+
+ <changeSet author="xurror" id="4">
+ <addForeignKeyConstraint baseColumnNames="product_loan_id" baseTableName="m_product_loan_scorecard_feature"
+ constraintName="m_product_loan_scorecard_feature_ibfk_1" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_product_loan" validate="true"/>
+
+ <addForeignKeyConstraint baseColumnNames="scorecard_feature_id" baseTableName="m_product_loan_scorecard_feature"
+ constraintName="m_product_loan_scorecard_feature_ibfk_2" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_credit_scorecard_feature" validate="true"/>
+ </changeSet>
+
+ <changeSet author="xurror" id="5">
+ <createTable tableName="m_scorecard_feature_criteria">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column name="name" type="VARCHAR(20)">
+ <constraints nullable="false"/>
+ </column>
+ <column defaultValueNumeric="0.000000" name="score" type="DECIMAL(6, 5)">
+ <constraints nullable="false"/>
+ </column>
+ <column defaultValueComputed="NULL" name="product_loan_scorecard_feature_id" type="BIGINT"/>
+ </createTable>
+ </changeSet>
+
+ <changeSet author="xurror" id="6">
+ <addForeignKeyConstraint baseColumnNames="product_loan_scorecard_feature_id"
+ baseTableName="m_scorecard_feature_criteria"
+ constraintName="m_scorecard_feature_criteria_ibfk_1" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_product_loan_scorecard_feature" validate="true"/>
+ </changeSet>
+
+ <changeSet author="xurror" id="7">
+ <createTable tableName="m_rule_based_scorecard">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column name="overall_score" type="DECIMAL(6, 5)"/>
+ <column name="overall_color" type="VARCHAR(20)"/>
+ </createTable>
+ </changeSet>
+
+ <changeSet author="xurror" id="8">
+ <createTable tableName="m_scorecard_feature_criteria_score">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column name="value" type="VARCHAR(20)"/>
+ <column name="score" type="DECIMAL(6, 5)"/>
+ <column name="color" type="VARCHAR(20)"/>
+ <column defaultValueComputed="NULL" name="rule_based_scorecard_id" type="BIGINT"/>
+ <column defaultValueComputed="NULL" name="product_loan_scorecard_feature_id" type="BIGINT"/>
+ </createTable>
+ </changeSet>
+
+ <changeSet author="xurror" id="9">
+ <addForeignKeyConstraint baseColumnNames="rule_based_scorecard_id" baseTableName="m_scorecard_feature_criteria_score"
+ constraintName="m_scorecard_feature_criteria_score_ibfk_1" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_rule_based_scorecard" validate="true"/>
+
+ <addForeignKeyConstraint baseColumnNames="product_loan_scorecard_feature_id" baseTableName="m_scorecard_feature_criteria_score"
+ constraintName="m_scorecard_feature_criteria_score_ibfk_2" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_product_loan_scorecard_feature" validate="true"/>
+ </changeSet>
+
+ <changeSet author="xurror" id="10">
+ <createTable tableName="m_stat_scorecard">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column defaultValueComputed="NULL" name="age" type="INT"/>
+ <column defaultValueComputed="NULL" name="sex" type="VARCHAR(50)"/>
+ <column defaultValueComputed="NULL" name="job" type="VARCHAR(50)"/>
+ <column defaultValueComputed="NULL" name="housing" type="VARCHAR(50)"/>
+ <column defaultValueComputed="NULL" name="credit_amount" type="DECIMAL(19, 6)"/>
+ <column defaultValueComputed="NULL" name="duration" type="INT"/>
+ <column defaultValueComputed="NULL" name="purpose" type="VARCHAR(50)"/>
+ <column defaultValueComputed="NULL" name="method" type="VARCHAR(50)"/>
+ <column defaultValueComputed="NULL" name="color" type="VARCHAR(50)"/>
+ <column defaultValueComputed="NULL" name="prediction" type="DECIMAL(19, 6)"/>
+ <column defaultValueComputed="NULL" name="wilki_s_lambda" type="DECIMAL(19, 6)"/>
+ <column defaultValueComputed="NULL" name="pillai_s_trace" type="DECIMAL(19, 6)"/>
+ <column defaultValueComputed="NULL" name="hotelling_lawley_trace" type="DECIMAL(19, 6)"/>
+ <column defaultValueComputed="NULL" name="roy_s_greatest_roots" type="DECIMAL(19, 6)"/>
+ </createTable>
+ </changeSet>
+
+ <changeSet author="xurror" id="11">
+ <createTable tableName="m_ml_scorecard">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column defaultValueComputed="NULL" name="age" type="INT"/>
+ <column defaultValueComputed="NULL" name="sex" type="VARCHAR(50)"/>
+ <column defaultValueComputed="NULL" name="job" type="VARCHAR(50)"/>
+ <column defaultValueComputed="NULL" name="housing" type="VARCHAR(50)"/>
+ <column defaultValueComputed="NULL" name="credit_amount" type="DECIMAL(19, 6)"/>
+ <column defaultValueComputed="NULL" name="duration" type="INT"/>
+ <column defaultValueComputed="NULL" name="purpose" type="VARCHAR(50)"/>
+ <column defaultValueComputed="NULL" name="predicted_risk" type="VARCHAR(50)"/>
+ <column defaultValueComputed="NULL" name="accuracy" type="DECIMAL(19, 6)"/>
+ <column defaultValueComputed="NULL" name="actual_risk" type="VARCHAR(50)"/>
+ <column defaultValueComputed="NULL" name="prediction_request_id" type="INT"/>
+ </createTable>
+ </changeSet>
+
+ <changeSet author="xurror" id="12">
+ <createTable tableName="m_credit_scorecard">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column name="scorecard_scoring_method" type="VARCHAR(100)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="scorecard_scoring_model" type="VARCHAR(100)">
+ <constraints nullable="false"/>
+ </column>
+ <column defaultValueComputed="NULL" name="rule_based_scorecard_id" type="BIGINT"/>
+ <column defaultValueComputed="NULL" name="stat_scorecard_id" type="BIGINT"/>
+ <column defaultValueComputed="NULL" name="ml_scorecard_id" type="BIGINT"/>
+ </createTable>
+ </changeSet>
+
+ <changeSet author="xurror" id="13">
+ <addForeignKeyConstraint baseColumnNames="rule_based_scorecard_id" baseTableName="m_credit_scorecard"
+ constraintName="m_credit_scorecard_ibfk_1" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_rule_based_scorecard" validate="true"/>
+
+ <addForeignKeyConstraint baseColumnNames="stat_scorecard_id" baseTableName="m_credit_scorecard"
+ constraintName="m_credit_scorecard_ibfk_2" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_stat_scorecard" validate="true"/>
+
+ <addForeignKeyConstraint baseColumnNames="ml_scorecard_id" baseTableName="m_credit_scorecard"
+ constraintName="m_credit_scorecard_ibfk_3" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_ml_scorecard" validate="true"/>
+ </changeSet>
+
+ <changeSet author="xurror" id="14">
+ <addColumn tableName="m_loan">
+ <column defaultValueComputed="NULL" name="loan_credit_scorecard_id" type="BIGINT"/>
+ </addColumn>
+
+ <addForeignKeyConstraint baseColumnNames="loan_credit_scorecard_id"
+ baseTableName="m_loan"
+ constraintName="FK_credit_scorecard_m_loan_m_credit_scorecard" deferrable="false"
+ initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
+ referencedColumnNames="id" referencedTableName="m_credit_scorecard" validate="true"/>
+ </changeSet>
+</databaseChangeLog>