FINERACT-2421: External Asset Owner create api

This commit is contained in:
Jose Alberto Hernandez
2026-02-12 10:58:24 +01:00
committed by Adam Saghy
parent 6875645825
commit 955222ee82
21 changed files with 489 additions and 1 deletions
@@ -20,6 +20,7 @@ package org.apache.fineract.investor.api;
import static org.apache.fineract.infrastructure.core.service.CommandParameterUtil.BUY_BACK_COMMAND_VALUE;
import static org.apache.fineract.infrastructure.core.service.CommandParameterUtil.CANCEL_COMMAND_VALUE;
import static org.apache.fineract.infrastructure.core.service.CommandParameterUtil.CREATE_COMMAND_VALUE;
import static org.apache.fineract.infrastructure.core.service.CommandParameterUtil.INTERMEDIARY_SALE_COMMAND_VALUE;
import static org.apache.fineract.infrastructure.core.service.CommandParameterUtil.SALE_COMMAND_VALUE;
@@ -38,6 +39,7 @@ import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.batch.command.CommandHandlerRegistry;
@@ -55,6 +57,7 @@ import org.apache.fineract.investor.config.InvestorModuleIsEnabledCondition;
import org.apache.fineract.investor.data.ExternalOwnerJournalEntryData;
import org.apache.fineract.investor.data.ExternalOwnerTransferJournalEntryData;
import org.apache.fineract.investor.data.ExternalTransferData;
import org.apache.fineract.investor.data.ExternalTransferOwnerData;
import org.apache.fineract.investor.data.request.ExternalAssetOwnerRequest;
import org.apache.fineract.investor.service.ExternalAssetOwnersReadService;
import org.apache.fineract.investor.service.search.domain.ExternalAssetOwnerSearchRequest;
@@ -85,7 +88,8 @@ public class ExternalAssetOwnersApiResource {
(id, json) -> new CommandWrapperBuilder().withJson(json).intermediarySaleLoanToExternalAssetOwner(id).build(),
SALE_COMMAND_VALUE, (id, json) -> new CommandWrapperBuilder().withJson(json).saleLoanToExternalAssetOwner(id).build(),
BUY_BACK_COMMAND_VALUE,
(id, json) -> new CommandWrapperBuilder().withJson(json).buybackLoanToExternalAssetOwner(id).build()));
(id, json) -> new CommandWrapperBuilder().withJson(json).buybackLoanToExternalAssetOwner(id).build(),
CREATE_COMMAND_VALUE, (id, json) -> new CommandWrapperBuilder().withJson(json).createExternalAssetOwner().build()));
@POST
@Path("/transfers/loans/{loanId}")
@@ -215,4 +219,26 @@ public class ExternalAssetOwnersApiResource {
platformUserRightsContext.isAuthenticated();
return delegate.searchInvestorData(request);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Create an External Asset Owner using the External Id")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = ExternalAssetOwnersApiResourceSwagger.PostExternalAssetOwnerRequest.class)))
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ExternalAssetOwnersApiResourceSwagger.PostExternalAssetOwnerResponse.class)))
@ApiResponse(responseCode = "400", description = "Bad requests due invalid json data")
public CommandProcessingResult createExternalAssetOwner(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
platformUserRightsContext.isAuthenticated();
final CommandWrapper commandRequest = COMMAND_HANDLER_REGISTRY.execute(CREATE_COMMAND_VALUE, null, apiRequestBodyAsJson,
new UnrecognizedQueryParamException(COMMAND_PARAM, CREATE_COMMAND_VALUE));
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}
@GET
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Get all External Asset Owner with details")
public List<ExternalTransferOwnerData> retrieveExternalAssetOwners() {
platformUserRightsContext.isAuthenticated();
return externalAssetOwnersReadService.retrieveAllExternalOwners();
}
}
@@ -68,4 +68,22 @@ final class ExternalAssetOwnersApiResourceSwagger {
}
}
@Schema(description = "PostExternalAssetOwnerRequest")
public static final class PostExternalAssetOwnerRequest {
private PostExternalAssetOwnerRequest() {}
@Schema(example = "36efeb06-d835-48a1-99eb-09bd1d348c1e", description = "External Asset Owner External Id")
public String ownerExternalId;
}
@Schema(description = "PostExternalAssetOwnerResponse")
public static final class PostExternalAssetOwnerResponse {
private PostExternalAssetOwnerResponse() {}
@Schema(example = "1", description = "External Asset Owner Id")
public Long resourceId;
}
}
@@ -24,4 +24,5 @@ import lombok.Data;
public class ExternalTransferOwnerData {
private final String externalId;
private Long id;
}
@@ -0,0 +1,32 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when a GL Closure for a given date and Office combination is already present
*/
public class ExternalAssetOwnerDuplicateException extends AbstractPlatformDomainRuleException {
public ExternalAssetOwnerDuplicateException(final String externalId) {
super("error.msg.provided.external.id.already.exists", "Provided external id already exists with Id " + externalId);
}
}
@@ -0,0 +1,51 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.investor.serialization;
import com.google.gson.JsonElement;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.validator.ParseAndValidator;
import org.apache.fineract.investor.data.ExternalTransferRequestParameters;
import org.apache.fineract.portfolio.common.service.Validator;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class ExternalAssetOwnerValidator extends ParseAndValidator {
private final FromJsonHelper fromApiJsonHelper;
public void validateForCreate(JsonCommand command) {
final String json = command.json();
validateRequestBody(json);
validateForSupportedParameters(json, List.of(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID), fromApiJsonHelper);
final JsonElement element = this.fromApiJsonHelper.parse(json);
Validator.validateOrThrow("externalAssetOwner", baseDataValidator -> {
final String ownerExternalId = this.fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID,
element);
baseDataValidator.reset().parameter(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID).value(ownerExternalId).notNull();
});
}
}
@@ -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", action = "CREATE")
public class CreateExternalAssetOwnerHandler implements NewCommandSourceHandler {
private final ExternalAssetOwnersWriteService externalAssetOwnersWriteService;
@Override
public CommandProcessingResult processCommand(JsonCommand command) {
return externalAssetOwnersWriteService.createExternalAssetOwner(command);
}
}
@@ -18,10 +18,12 @@
*/
package org.apache.fineract.investor.service;
import java.util.List;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.investor.data.ExternalOwnerJournalEntryData;
import org.apache.fineract.investor.data.ExternalOwnerTransferJournalEntryData;
import org.apache.fineract.investor.data.ExternalTransferData;
import org.apache.fineract.investor.data.ExternalTransferOwnerData;
import org.springframework.data.domain.Page;
public interface ExternalAssetOwnersReadService {
@@ -40,4 +42,7 @@ public interface ExternalAssetOwnersReadService {
ExternalTransferData retrieveFirstTransferByExternalId(ExternalId externalTransferId);
Long retrieveLastTransferIdByExternalId(ExternalId externalTransferId);
List<ExternalTransferOwnerData> retrieveAllExternalOwners();
}
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.investor.service;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.journalentry.JournalEntryMapper;
@@ -26,8 +27,10 @@ import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.investor.data.ExternalOwnerJournalEntryData;
import org.apache.fineract.investor.data.ExternalOwnerTransferJournalEntryData;
import org.apache.fineract.investor.data.ExternalTransferData;
import org.apache.fineract.investor.data.ExternalTransferOwnerData;
import org.apache.fineract.investor.domain.ExternalAssetOwnerJournalEntryMapping;
import org.apache.fineract.investor.domain.ExternalAssetOwnerJournalEntryMappingRepository;
import org.apache.fineract.investor.domain.ExternalAssetOwnerRepository;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMapping;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMappingRepository;
@@ -52,6 +55,7 @@ public class ExternalAssetOwnersReadServiceImpl implements ExternalAssetOwnersRe
private final ExternalAssetOwnerJournalEntryMappingRepository externalAssetOwnerJournalEntryMappingRepository;
private final ExternalAssetOwnersTransferMapper mapper;
private final JournalEntryMapper journalEntryMapper;
private final ExternalAssetOwnerRepository externalAssetOwnerRepository;
@Override
public Page<ExternalTransferData> retrieveTransferData(Long loanId, String externalLoanId, String externalTransferId, Integer offset,
@@ -142,4 +146,9 @@ public class ExternalAssetOwnersReadServiceImpl implements ExternalAssetOwnersRe
return PageRequest.of(offset, limit, Sort.by("id"));
}
@Override
public List<ExternalTransferOwnerData> retrieveAllExternalOwners() {
return mapper.mapOwners(externalAssetOwnerRepository.findAll());
}
}
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.investor.service;
import java.util.List;
import org.apache.fineract.accounting.journalentry.JournalEntryMapper;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
import org.apache.fineract.investor.data.ExternalTransferData;
@@ -49,6 +50,8 @@ public interface ExternalAssetOwnersTransferMapper {
ExternalTransferOwnerData mapOwner(ExternalAssetOwner source);
List<ExternalTransferOwnerData> mapOwners(List<ExternalAssetOwner> source);
@Mapping(target = "detailsId", source = "id")
ExternalTransferDataDetails mapDetails(ExternalAssetOwnerTransferDetails details);
@@ -31,4 +31,5 @@ public interface ExternalAssetOwnersWriteService {
CommandProcessingResult cancelTransactionById(JsonCommand command);
CommandProcessingResult createExternalAssetOwner(JsonCommand command);
}
@@ -59,7 +59,9 @@ import org.apache.fineract.investor.domain.ExternalAssetOwner;
import org.apache.fineract.investor.domain.ExternalAssetOwnerRepository;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
import org.apache.fineract.investor.exception.ExternalAssetOwnerDuplicateException;
import org.apache.fineract.investor.exception.ExternalAssetOwnerInitiateTransferException;
import org.apache.fineract.investor.serialization.ExternalAssetOwnerValidator;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
@@ -82,6 +84,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
private final DelayedSettlementAttributeService delayedSettlementAttributeService;
private final ConfigurationDomainService configurationDomainService;
private final ExternalAssetOwnersReadService externalAssetOwnersReadService;
private final ExternalAssetOwnerValidator externalAssetOwnerValidator;
@Override
@Transactional
@@ -601,4 +604,18 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
return configurationDomainService.getAllowedLoanStatusesOfDelayedSettlementForExternalAssetTransfer().stream()
.map(LoanStatus::valueOf).collect(Collectors.toList());
}
@Override
public CommandProcessingResult createExternalAssetOwner(JsonCommand command) {
externalAssetOwnerValidator.validateForCreate(command);
String ownerExternalId = command.stringValueOfParameterNamed(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID);
Optional<ExternalAssetOwner> optExternalId = externalAssetOwnerRepository
.findByExternalId(ExternalIdFactory.produce(ownerExternalId));
if (!optExternalId.isEmpty()) {
throw new ExternalAssetOwnerDuplicateException(ownerExternalId);
}
final ExternalAssetOwner externalAssetOwner = createAndGetAssetOwner(ownerExternalId);
return new CommandProcessingResultBuilder().withEntityId(externalAssetOwner.getId()).build();
}
}
@@ -43,4 +43,5 @@
<include relativeToChangelogFile="true" file="parts/0019_add_configurable_allowed_loan_statuses.xml"/>
<include relativeToChangelogFile="true" file="parts/0020_add_previous_owner_reference.xml"/>
<include relativeToChangelogFile="true" file="parts/0021_external_owner_reference_in_journal_entry_aggregation.xml"/>
<include relativeToChangelogFile="true" file="parts/0022_add_external_asser_owner_create_permission.xml"/>
</databaseChangeLog>
@@ -0,0 +1,34 @@
<?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 id="1" author="fineract">
<insert tableName="m_permission">
<column name="grouping" value="investor"/>
<column name="code" value="CREATE_EXTERNAL_ASSET_OWNER"/>
<column name="entity_name" value="EXTERNAL_ASSET_OWNER"/>
<column name="action_name" value="CREATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
</databaseChangeLog>