Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3871,6 +3871,15 @@ public CommandWrapperBuilder updateExternalAssetOwnerLoanProductAttribute(final
return this;
}

public CommandWrapperBuilder deleteExternalAssetOwnerLoanProductAttribute(final Long loanProductId, final Long attributeId) {
this.actionName = ACTION_DELETE;
this.entityName = ENTITY_EXTERNAL_ASSET_OWNER_LOAN_PRODUCT_ATTRIBUTE;
this.productId = loanProductId;
this.entityId = attributeId;
this.href = "/external-asset-owners/loan-product/" + loanProductId + "/attributes/" + attributeId;
return this;
}

public CommandWrapperBuilder intermediarySaleLoanToExternalAssetOwner(final Long loanId) {
this.actionName = ACTION_INTERMEDIARYSALE;
this.entityName = ENTITY_LOAN;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
Expand Down Expand Up @@ -127,4 +128,22 @@ public CommandProcessingResult updateLoanProductAttribute(
return commandsSourceWritePlatformService.logCommandSource(request);
}

@DELETE
@Path("/{loanProductId}/attributes/{id}")
@Produces({ MediaType.APPLICATION_JSON })
@Operation(tags = {
"External Asset Owner Loan Product Attributes" }, summary = "Delete a Loan Product Attribute", operationId = "deleteExternalAssetOwnerLoanProductAttribute", description = "Deletes a loan product attribute with a given loan product id and attribute id, so that the loan product falls back to the default behaviour of the attribute", parameters = {
@Parameter(name = "loanProductId", description = "loanProductId"),
@Parameter(name = "attributeId", description = "attributeId") })
@AlternativeOperationId("deleteLoanProductAttribute")
public CommandProcessingResult deleteLoanProductAttribute(
@PathParam("loanProductId") @Parameter(description = "loanProductId") final Long loanProductId,
@PathParam("id") @Parameter(description = "attributeId") final Long attributeId) {
platformUserRightsContext.isAuthenticated();
final CommandWrapperBuilder builder = new CommandWrapperBuilder();
CommandWrapper request = builder.deleteExternalAssetOwnerLoanProductAttribute(loanProductId, attributeId).build();

return commandsSourceWritePlatformService.logCommandSource(request);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ public static final class PostExternalAssetOwnerLoanProductAttributeRequest {

private PostExternalAssetOwnerLoanProductAttributeRequest() {}

@Schema(example = "SETTLEMENT_MODEL")
@Schema(example = "SETTLEMENT_MODEL", description = "Attribute key, one of the keys returned by the loan product attributes template endpoint, e.g. SETTLEMENT_MODEL or EXCLUDED_TRANSACTION_TYPES.")
public String attributeKey;

@Schema(example = "DELAYED_SETTLEMENT")
@Schema(example = "DELAYED_SETTLEMENT", description = "Attribute value. For multi value attributes such as EXCLUDED_TRANSACTION_TYPES this is a comma separated list, e.g. BUY_DOWN_FEE,BUY_DOWN_FEE_ADJUSTMENT.")
public String attributeValue;
}

Expand All @@ -42,10 +42,10 @@ public static final class PutExternalAssetOwnerLoanProductAttributeRequest {

private PutExternalAssetOwnerLoanProductAttributeRequest() {}

@Schema(example = "SETTLEMENT_MODEL")
@Schema(example = "SETTLEMENT_MODEL", description = "Attribute key, one of the keys returned by the loan product attributes template endpoint, e.g. SETTLEMENT_MODEL or EXCLUDED_TRANSACTION_TYPES.")
public String attributeKey;

@Schema(example = "DELAYED_SETTLEMENT_DISABLED")
@Schema(example = "DELAYED_SETTLEMENT_DISABLED", description = "Attribute value. For multi value attributes such as EXCLUDED_TRANSACTION_TYPES this is a comma separated list, e.g. BUY_DOWN_FEE,BUY_DOWN_FEE_ADJUSTMENT.")
public String attributeValue;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* 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.investor.data.attribute;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;

public enum CapitalizedIncomeAmortizationStrategyExternalAssetOwnerLoanProductAttribute implements ExternalAssetOwnerLoanProductAttribute {

DEFERRED, //
IMMEDIATE; //

private final String attributeKey;

CapitalizedIncomeAmortizationStrategyExternalAssetOwnerLoanProductAttribute() {
this.attributeKey = "CAPITALIZED_INCOME_AMORTIZATION_STRATEGY";
}

@Override
public String getAttributeKey() {
return attributeKey;
}

@Override
public String getAttributeValue() {
return name();
}

@Override
public List<String> getAttributeValues() {
return Arrays.stream(values()).map(Enum::name).toList();
}

@Override
public boolean validate(String attributeValue) {
return this.getAttributeValue().equals(attributeValue.toUpperCase(Locale.ROOT));
}

@Override
public boolean isMultiValue() {
return false;
}

@Override
public String normalize(String token) {
return token.trim().toUpperCase(Locale.ROOT);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* 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.investor.data.attribute;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;

/**
* Multi value attribute holding the loan transaction types that must be kept out of external asset owner accounting and
* reporting. The value is a comma separated list of {@link LoanTransactionType} names.
* <p>
* This cannot be modelled as an enum the way {@link SettlementModelExternalAssetOwnerLoanProductAttribute} is, because
* the allowed values are the constants of another enum and any combination of them is a valid attribute value.
*/
public class ExcludedTransactionTypesExternalAssetOwnerLoanProductAttribute implements ExternalAssetOwnerLoanProductAttribute {

public static final String ATTRIBUTE_KEY = "EXCLUDED_TRANSACTION_TYPES";

private static final String SEPARATOR = ",";

@Override
public String getAttributeKey() {
return ATTRIBUTE_KEY;
}

@Override
public String getAttributeValue() {
return null;
}

@Override
public List<String> getAttributeValues() {
return Arrays.stream(LoanTransactionType.values()).filter(type -> !LoanTransactionType.INVALID.equals(type)).map(Enum::name)
.toList();
}

@Override
public boolean validate(String attributeValue) {
if (attributeValue == null || attributeValue.isBlank()) {
return false;
}
List<String> allowedValues = getAttributeValues();
Set<String> seenValues = new LinkedHashSet<>();
for (String token : attributeValue.split(SEPARATOR, -1)) {
String normalizedToken = normalizeToken(token);
if (!allowedValues.contains(normalizedToken) || !seenValues.add(normalizedToken)) {
return false;
}
}
return true;
}

@Override
public boolean isMultiValue() {
return true;
}

@Override
public String normalize(String attributeValue) {
List<String> normalizedTokens = new ArrayList<>();
for (String token : attributeValue.split(SEPARATOR, -1)) {
normalizedTokens.add(normalizeToken(token));
}
return String.join(SEPARATOR, normalizedTokens);
}

private String normalizeToken(String token) {
return token.trim().toUpperCase(Locale.ROOT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,13 @@ public interface ExternalAssetOwnerLoanProductAttribute {
boolean validate(String attributeValue);

boolean isMultiValue();

/**
* Converts an already validated attribute value into its canonical, storable form. Implementations that accept
* loosely formatted input (different casing, padding around separators) return the normalised value here, so that
* the persisted value is always canonical.
*/
default String normalize(String attributeValue) {
return attributeValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class ExternalAssetOwnerLoanProductAttributes extends AbstractAuditableWi
@Column(name = "attribute_key", nullable = false)
private String attributeKey;

@Column(name = "attribute_value", nullable = false)
@Column(name = "attribute_value", nullable = false, length = 2000)
private String attributeValue;

}
Original file line number Diff line number Diff line change
@@ -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.investor.service;

import lombok.RequiredArgsConstructor;
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.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
@CommandType(entity = "EXTERNAL_ASSET_OWNER_LOAN_PRODUCT_ATTRIBUTE", action = "DELETE")
public class DeleteExternalAssetOwnerLoanProductAttributeHandler implements NewCommandSourceHandler {

private final ExternalAssetOwnerLoanProductAttributesWriteService externalAssetOwnerLoanProductAttributesWriteService;

@Override
public CommandProcessingResult processCommand(JsonCommand command) {
return externalAssetOwnerLoanProductAttributesWriteService.deleteExternalAssetOwnerLoanProductAttribute(command);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* 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.investor.service;

import static org.reflections.scanners.Scanners.SubTypes;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.apache.fineract.investor.data.attribute.ExternalAssetOwnerLoanProductAttribute;
import org.reflections.Reflections;
import org.springframework.stereotype.Component;

/**
* Discovers every {@link ExternalAssetOwnerLoanProductAttribute} implementation on the classpath and exposes them as
* ready to use instances. Enum implementations contribute one instance per constant, non enum implementations are
* instantiated through their no-arg constructor.
*/
@Component
public final class ExternalAssetOwnerLoanProductAttributeProvider {

private static final String INVESTOR_PATH = "org.apache.fineract.investor";

private final List<ExternalAssetOwnerLoanProductAttribute> attributes;

public ExternalAssetOwnerLoanProductAttributeProvider() {
Set<Class<?>> implementingClasses = new Reflections(INVESTOR_PATH)
.get(SubTypes.of(ExternalAssetOwnerLoanProductAttribute.class).asClass());
List<ExternalAssetOwnerLoanProductAttribute> resolved = new ArrayList<>();
for (Class<?> implementingClass : implementingClasses) {
if (implementingClass.isEnum()) {
Arrays.stream(implementingClass.getEnumConstants()).map(ExternalAssetOwnerLoanProductAttribute.class::cast)
.forEach(resolved::add);
} else {
resolved.add(createAttribute(implementingClass));
}
}
this.attributes = List.copyOf(resolved);
}

/**
* @return every discovered attribute instance; enum implementations are expanded to their constants.
*/
public List<ExternalAssetOwnerLoanProductAttribute> retrieveAll() {
return attributes;
}

private static ExternalAssetOwnerLoanProductAttribute createAttribute(final Class<?> implementingClass) {
try {
return (ExternalAssetOwnerLoanProductAttribute) implementingClass.getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException | ClassCastException exception) {
throw new IllegalStateException("Unable to create external asset owner loan product attribute: " + implementingClass.getName(),
exception);
}
}
}
Loading
Loading