Merge branch 'feature/bugfix' into dev-latest
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> # Conflicts: # fineract-provider/src/main/resources/db/changelog/tenant/final-changelog-tenant.xml
This commit is contained in:
+111
@@ -756,6 +756,14 @@ public class CommandWrapperBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder deleteFund(final Long fundId) {
|
||||
this.actionName = "DELETE";
|
||||
this.entityName = "FUND";
|
||||
this.entityId = fundId;
|
||||
this.href = "/funds/" + fundId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createReport() {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "REPORT";
|
||||
@@ -884,6 +892,70 @@ public class CommandWrapperBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createPaymentType() {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "PAYMENTTYPE";
|
||||
this.entityId = null;
|
||||
this.href = "/paymenttypes/template";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updatePaymentType(final Long paymentTypeId) {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "PAYMENTTYPE";
|
||||
this.entityId = paymentTypeId;
|
||||
this.href = "/paymenttypes/" + paymentTypeId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder deletePaymentType(final Long paymentTypeId) {
|
||||
this.actionName = "DELETE";
|
||||
this.entityName = "PAYMENTTYPE";
|
||||
this.entityId = paymentTypeId;
|
||||
this.href = "/paymenttypes/" + paymentTypeId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createSponsorBank() {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "SPONSORBANK";
|
||||
this.entityId = null;
|
||||
this.href = "/sponsorbanks";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateSponsorBank(final Long sponsorBankId) {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "SPONSORBANK";
|
||||
this.entityId = sponsorBankId;
|
||||
this.href = "/sponsorbanks/" + sponsorBankId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateSponsorDdGlMapping() {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "SPONSORDDGLMAPPING";
|
||||
this.entityId = null;
|
||||
this.href = "/sponsordd/gl-mapping";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateInstitutionProfile() {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "INSTITUTIONPROFILE";
|
||||
this.entityId = null;
|
||||
this.href = "/institutionprofile";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateBranchProfile(final Long officeId) {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "BRANCHPROFILE";
|
||||
this.entityId = officeId;
|
||||
this.href = "/branchprofile/" + officeId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createLoanProduct() {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "LOANPRODUCT";
|
||||
@@ -3390,6 +3462,13 @@ public class CommandWrapperBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateCurrency() {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "CURRENCY";
|
||||
this.href = "/currencies";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateExternalServiceProperties(final String externalServiceName) {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "EXTERNALSERVICES";
|
||||
@@ -4730,4 +4809,36 @@ public class CommandWrapperBuilder {
|
||||
this.href = "/loans/" + loanId + "/originators/" + originatorId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createSurvey() {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "SURVEY";
|
||||
this.entityId = null;
|
||||
this.href = "/surveys";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateSurvey(final Long surveyId) {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "SURVEY";
|
||||
this.entityId = surveyId;
|
||||
this.href = "/surveys/" + surveyId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder activateSurvey(final Long surveyId) {
|
||||
this.actionName = "ACTIVATE";
|
||||
this.entityName = "SURVEY";
|
||||
this.entityId = surveyId;
|
||||
this.href = "/surveys/" + surveyId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder deactivateSurvey(final Long surveyId) {
|
||||
this.actionName = "DEACTIVATE";
|
||||
this.entityName = "SURVEY";
|
||||
this.entityId = surveyId;
|
||||
this.href = "/surveys/" + surveyId;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-11
@@ -27,13 +27,14 @@ import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.command.core.CommandPipeline;
|
||||
import org.apache.fineract.organisation.monetary.command.CurrencyUpdateCommand;
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyConfigurationData;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyUpdateRequest;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyUpdateResponse;
|
||||
import org.apache.fineract.organisation.monetary.service.OrganisationCurrencyReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -44,7 +45,8 @@ import org.springframework.stereotype.Component;
|
||||
public class CurrenciesApiResource {
|
||||
|
||||
private final OrganisationCurrencyReadPlatformService readPlatformService;
|
||||
private final CommandPipeline commandPipeline;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final DefaultToApiJsonSerializer<CurrencyUpdateRequest> toApiJsonSerializer;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@@ -65,13 +67,12 @@ public class CurrenciesApiResource {
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Update Currency Configuration", description = "Updates the list of currencies permitted for use.")
|
||||
public CurrencyUpdateResponse updateCurrencies(@Valid CurrencyUpdateRequest request) {
|
||||
final var command = new CurrencyUpdateCommand();
|
||||
public String updateCurrencies(@Valid final CurrencyUpdateRequest request) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCurrency()
|
||||
.withJson(toApiJsonSerializer.serialize(request)).build();
|
||||
|
||||
command.setPayload(request);
|
||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
final Supplier<CurrencyUpdateResponse> response = commandPipeline.send(command);
|
||||
|
||||
return response.get();
|
||||
return toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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.organisation.monetary.handler;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyUpdateRequest;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyUpdateResponse;
|
||||
import org.apache.fineract.organisation.monetary.service.CurrencyWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "CURRENCY", action = "UPDATE")
|
||||
public class UpdateCurrencyCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final CurrencyWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public UpdateCurrencyCommandHandler(final CurrencyWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final String[] currencyCodes = command.arrayValueOfParameterNamed("currencies");
|
||||
final List<String> currencies = currencyCodes == null ? List.of() : Arrays.asList(currencyCodes);
|
||||
|
||||
final CurrencyUpdateRequest request = CurrencyUpdateRequest.builder().currencies(currencies).build();
|
||||
|
||||
final CurrencyUpdateResponse response = this.writePlatformService.updateAllowedCurrencies(request);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.with(Map.of("currencies", response.getCurrencies())) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+19
-29
@@ -31,20 +31,15 @@ import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.command.core.CommandPipeline;
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.portfolio.paymenttype.command.PaymentTypeCreateCommand;
|
||||
import org.apache.fineract.portfolio.paymenttype.command.PaymentTypeDeleteCommand;
|
||||
import org.apache.fineract.portfolio.paymenttype.command.PaymentTypeUpdateCommand;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeCreateRequest;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeCreateResponse;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeDeleteRequest;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeDeleteResponse;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeUpdateRequest;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeUpdateResponse;
|
||||
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -55,7 +50,7 @@ import org.springframework.stereotype.Component;
|
||||
public class PaymentTypeApiResource {
|
||||
|
||||
private final PaymentTypeReadService readPlatformService;
|
||||
private final CommandPipeline commandPipeline;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final DefaultToApiJsonSerializer<PaymentTypeData> jsonSerializer;
|
||||
|
||||
@GET
|
||||
@@ -79,14 +74,13 @@ public class PaymentTypeApiResource {
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Create a Payment Type", description = "Creates a new Payment type")
|
||||
public PaymentTypeCreateResponse createPaymentType(PaymentTypeCreateRequest request) {
|
||||
final var command = new PaymentTypeCreateCommand();
|
||||
public String createPaymentType(final PaymentTypeCreateRequest request) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createPaymentType()
|
||||
.withJson(jsonSerializer.serialize(request)).build();
|
||||
|
||||
command.setPayload(request);
|
||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
final Supplier<PaymentTypeCreateResponse> response = commandPipeline.send(command);
|
||||
|
||||
return response.get();
|
||||
return jsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@@ -94,18 +88,16 @@ public class PaymentTypeApiResource {
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Update a Payment Type", description = "Updates a Payment Type")
|
||||
public PaymentTypeUpdateResponse updatePaymentType(@PathParam("paymentTypeId") final Long paymentTypeId,
|
||||
final PaymentTypeUpdateRequest request) {
|
||||
public String updatePaymentType(@PathParam("paymentTypeId") final Long paymentTypeId, final PaymentTypeUpdateRequest request) {
|
||||
|
||||
request.setId(paymentTypeId);
|
||||
|
||||
final var command = new PaymentTypeUpdateCommand();
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updatePaymentType(paymentTypeId)
|
||||
.withJson(jsonSerializer.serialize(request)).build();
|
||||
|
||||
command.setPayload(request);
|
||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
final Supplier<PaymentTypeUpdateResponse> response = commandPipeline.send(command);
|
||||
|
||||
return response.get();
|
||||
return jsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@@ -113,14 +105,12 @@ public class PaymentTypeApiResource {
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Delete a Payment Type", description = "Deletes payment type")
|
||||
public PaymentTypeDeleteResponse deleteCode(@PathParam("paymentTypeId") final Long paymentTypeId) {
|
||||
public String deleteCode(@PathParam("paymentTypeId") final Long paymentTypeId) {
|
||||
|
||||
final var command = new PaymentTypeDeleteCommand();
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().deletePaymentType(paymentTypeId).build();
|
||||
|
||||
command.setPayload(PaymentTypeDeleteRequest.builder().id(paymentTypeId).build());
|
||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
final Supplier<PaymentTypeDeleteResponse> response = commandPipeline.send(command);
|
||||
|
||||
return response.get();
|
||||
return jsonSerializer.serialize(result);
|
||||
}
|
||||
}
|
||||
|
||||
+34
-17
@@ -18,33 +18,50 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.paymenttype.handler;
|
||||
|
||||
import io.github.resilience4j.retry.annotation.Retry;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.core.CommandHandler;
|
||||
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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeCreateRequest;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeCreateResponse;
|
||||
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeWriteService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PaymentTypeCreateCommandHandler implements CommandHandler<PaymentTypeCreateRequest, PaymentTypeCreateResponse> {
|
||||
@Service
|
||||
@CommandType(entity = "PAYMENTTYPE", action = "CREATE")
|
||||
public class PaymentTypeCreateCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final PaymentTypeWriteService paymentTypeWriteService;
|
||||
|
||||
@Retry(name = "commandPaymentTypeCreate", fallbackMethod = "fallback")
|
||||
@Override
|
||||
@Transactional
|
||||
public PaymentTypeCreateResponse handle(Command<PaymentTypeCreateRequest> command) {
|
||||
return paymentTypeWriteService.createPaymentType(command.getPayload());
|
||||
@Autowired
|
||||
public PaymentTypeCreateCommandHandler(final PaymentTypeWriteService paymentTypeWriteService) {
|
||||
this.paymentTypeWriteService = paymentTypeWriteService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public PaymentTypeCreateResponse fallback(Command<PaymentTypeCreateRequest> command, Throwable t) {
|
||||
return CommandHandler.super.fallback(command, t);
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final Boolean isSystemDefined = command.booleanObjectValueOfParameterNamed("isSystemDefined");
|
||||
|
||||
final PaymentTypeCreateRequest request = PaymentTypeCreateRequest.builder()
|
||||
.name(command.stringValueOfParameterNamed("name"))
|
||||
.description(command.stringValueOfParameterNamed("description"))
|
||||
.isCashPayment(command.booleanObjectValueOfParameterNamed("isCashPayment"))
|
||||
.position(command.longValueOfParameterNamed("position"))
|
||||
.codeName(command.stringValueOfParameterNamed("codeName"))
|
||||
.isSystemDefined(isSystemDefined != null ? isSystemDefined : Boolean.FALSE)
|
||||
.build();
|
||||
|
||||
final PaymentTypeCreateResponse response = this.paymentTypeWriteService.createPaymentType(request);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(response.getResourceId()) //
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+25
-17
@@ -18,33 +18,41 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.paymenttype.handler;
|
||||
|
||||
import io.github.resilience4j.retry.annotation.Retry;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.core.CommandHandler;
|
||||
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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeDeleteRequest;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeDeleteResponse;
|
||||
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeWriteService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PaymentTypeDeleteCommandHandler implements CommandHandler<PaymentTypeDeleteRequest, PaymentTypeDeleteResponse> {
|
||||
@Service
|
||||
@CommandType(entity = "PAYMENTTYPE", action = "DELETE")
|
||||
public class PaymentTypeDeleteCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final PaymentTypeWriteService paymentTypeWriteService;
|
||||
|
||||
@Retry(name = "commandPaymentTypeDelete", fallbackMethod = "fallback")
|
||||
@Override
|
||||
@Transactional
|
||||
public PaymentTypeDeleteResponse handle(Command<PaymentTypeDeleteRequest> command) {
|
||||
return paymentTypeWriteService.deletePaymentType(command.getPayload());
|
||||
@Autowired
|
||||
public PaymentTypeDeleteCommandHandler(final PaymentTypeWriteService paymentTypeWriteService) {
|
||||
this.paymentTypeWriteService = paymentTypeWriteService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public PaymentTypeDeleteResponse fallback(Command<PaymentTypeDeleteRequest> command, Throwable t) {
|
||||
return CommandHandler.super.fallback(command, t);
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final PaymentTypeDeleteRequest request = PaymentTypeDeleteRequest.builder().id(command.entityId()).build();
|
||||
|
||||
final PaymentTypeDeleteResponse response = this.paymentTypeWriteService.deletePaymentType(request);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(response.getResourceId()) //
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+35
-17
@@ -18,33 +18,51 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.paymenttype.handler;
|
||||
|
||||
import io.github.resilience4j.retry.annotation.Retry;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.core.CommandHandler;
|
||||
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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeUpdateRequest;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeUpdateResponse;
|
||||
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeWriteService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PaymentTypeUpdateCommandHandler implements CommandHandler<PaymentTypeUpdateRequest, PaymentTypeUpdateResponse> {
|
||||
@Service
|
||||
@CommandType(entity = "PAYMENTTYPE", action = "UPDATE")
|
||||
public class PaymentTypeUpdateCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final PaymentTypeWriteService paymentTypeWriteService;
|
||||
|
||||
@Retry(name = "commandPaymentTypeUpdate", fallbackMethod = "fallback")
|
||||
@Override
|
||||
@Transactional
|
||||
public PaymentTypeUpdateResponse handle(Command<PaymentTypeUpdateRequest> command) {
|
||||
return paymentTypeWriteService.updatePaymentType(command.getPayload());
|
||||
@Autowired
|
||||
public PaymentTypeUpdateCommandHandler(final PaymentTypeWriteService paymentTypeWriteService) {
|
||||
this.paymentTypeWriteService = paymentTypeWriteService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public PaymentTypeUpdateResponse fallback(Command<PaymentTypeUpdateRequest> command, Throwable t) {
|
||||
return CommandHandler.super.fallback(command, t);
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final Boolean isSystemDefined = command.booleanObjectValueOfParameterNamed("isSystemDefined");
|
||||
|
||||
final PaymentTypeUpdateRequest request = PaymentTypeUpdateRequest.builder()
|
||||
.id(command.entityId())
|
||||
.name(command.stringValueOfParameterNamed("name"))
|
||||
.description(command.stringValueOfParameterNamed("description"))
|
||||
.isCashPayment(command.booleanObjectValueOfParameterNamed("isCashPayment"))
|
||||
.position(command.longValueOfParameterNamed("position"))
|
||||
.codeName(command.stringValueOfParameterNamed("codeName"))
|
||||
.isSystemDefined(isSystemDefined != null ? isSystemDefined : Boolean.FALSE)
|
||||
.build();
|
||||
|
||||
final PaymentTypeUpdateResponse response = this.paymentTypeWriteService.updatePaymentType(request);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(response.getResourceId()) //
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -35,6 +35,7 @@ import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO;
|
||||
import org.apache.fineract.cob.service.LoanCOBCatchUpService;
|
||||
import org.apache.fineract.infrastructure.core.exception.JobIsNotFoundOrNotEnabledException;
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/loans")
|
||||
@@ -44,6 +45,7 @@ import org.springframework.stereotype.Component;
|
||||
public class LoanCOBCatchUpApiResource {
|
||||
|
||||
private final Optional<LoanCOBCatchUpService> loanCOBCatchUpServiceOp;
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
@GET
|
||||
@Path("oldest-cob-closed")
|
||||
@@ -64,6 +66,7 @@ public class LoanCOBCatchUpApiResource {
|
||||
@ApiResponse(responseCode = "202", description = "Catch Up has been started")
|
||||
@ApiResponse(responseCode = "400", description = "Catch Up is already running")
|
||||
public Response executeLoanCOBCatchUp() {
|
||||
this.context.authenticatedUser().validateHasPermissionTo("EXECUTE_LOANCOBCATCHUP");
|
||||
return loanCOBCatchUpServiceOp.map(loanCOBCatchUpService -> {
|
||||
if (loanCOBCatchUpService.isCatchUpRunning().isCatchUpRunning()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).build();
|
||||
|
||||
+44
-5
@@ -222,7 +222,10 @@ public class SecurityConfig {
|
||||
// currency
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/currencies"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_CURRENCY")
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/currencies"))
|
||||
// the actual currency-update endpoint is PUT (CurrenciesApiResource has no @POST method) -
|
||||
// this matcher previously targeted POST, which meant PUT fell through to the generic
|
||||
// fullyAuthenticated() catch-all with no permission check at all.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/currencies"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_CURRENCY")
|
||||
// notes: read
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/clients/*/notes"))
|
||||
@@ -355,11 +358,18 @@ public class SecurityConfig {
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/import/*"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_IMPORT")
|
||||
// payment type
|
||||
// NOTE: POST previously required "UPDATE_PAYMENTTYPE" (wrong authority for create), with a
|
||||
// second, unreachable matcher for the same POST pattern requiring "DELETE_PAYMENTTYPE" - and
|
||||
// no PUT/DELETE matcher existed at all, so those requests fell through to the generic
|
||||
// fullyAuthenticated() catch-all below with no permission check. Corrected to one matcher
|
||||
// per method, each requiring its own correct permission code.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/paymenttypes/*"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_PAYMENTTYPE")
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/paymenttypes"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_PAYMENTTYPE")
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/paymenttypes/*"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_PAYMENTTYPE")
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/paymenttypes"))
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/paymenttypes/*"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_PAYMENTTYPE")
|
||||
// mix: taxonomy
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/mixtaxonomy/*"))
|
||||
@@ -390,12 +400,41 @@ public class SecurityConfig {
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/mixreport/*"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_MIX_REPORT")
|
||||
// working days
|
||||
// NOTE: previously "READ_WORKING_DAYS"/"UPDATE_WORKING_DAYS" (with an underscore between
|
||||
// WORKING and DAYS), which did not match the actual seeded permission codes
|
||||
// (READ_WORKINGDAYS / UPDATE_WORKINGDAYS) - corrected to the real codes.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/workingdays"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_WORKING_DAYS")
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_WORKINGDAYS")
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/workingdays/template"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_WORKING_DAYS")
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_WORKINGDAYS")
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/workingdays"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_WORKING_DAYS")
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_WORKINGDAYS")
|
||||
// sponsor banks
|
||||
// NOTE: no matcher previously existed at all for this path - POST/PUT both fell
|
||||
// through to the generic fullyAuthenticated() catch-all below with no permission
|
||||
// check whatsoever.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/sponsorbanks"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_SPONSORBANK")
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/sponsorbanks/*"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_SPONSORBANK")
|
||||
// sponsor DD GST/GL settings
|
||||
// NOTE: no matcher previously existed at all for this path either. gl-mapping is a
|
||||
// genuine upsert (one call both creates and updates the single settings row), so it is
|
||||
// covered by one UPDATE_SPONSORDDGLMAPPING matcher rather than a separate CREATE one.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/sponsordd/gl-mapping"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_SPONSORDDGLMAPPING")
|
||||
// bank profile (institution profile)
|
||||
// NOTE: no matcher previously existed at all for this path - the PUT relied solely on
|
||||
// the in-resource validateHasPermissionTo() check, which this change replaces with the
|
||||
// command-source pipeline. Added here for the same defense-in-depth already applied to
|
||||
// Currency/Payment Type/Working Days.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/institutionprofile"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_INSTITUTIONPROFILE")
|
||||
// branch profile
|
||||
// NOTE: no matcher previously existed at all for this path either, same rationale as
|
||||
// institution profile above.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/branchprofile/*"))
|
||||
.hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_BRANCHPROFILE")
|
||||
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/twofactor/validate")).fullyAuthenticated()
|
||||
.requestMatchers(API_MATCHER.matcher("/api/*/twofactor")).fullyAuthenticated()
|
||||
|
||||
+12
-15
@@ -27,15 +27,15 @@ import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.time.Instant;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.command.core.CommandPipeline;
|
||||
import org.apache.fineract.organisation.workingdays.command.WorkingDaysUpdateCommand;
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.organisation.workingdays.data.WorkingDaysData;
|
||||
import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequest;
|
||||
import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequestValidator;
|
||||
import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateResponse;
|
||||
import org.apache.fineract.organisation.workingdays.service.WorkingDaysReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -50,7 +50,8 @@ public class WorkingDaysApiResource {
|
||||
|
||||
private final WorkingDaysReadPlatformService workingDaysReadPlatformService;
|
||||
private final WorkingDaysUpdateRequestValidator workingDaysUpdateRequestValidator;
|
||||
private final CommandPipeline commandPipeline;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final DefaultToApiJsonSerializer<WorkingDaysUpdateRequest> toApiJsonSerializer;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@@ -65,17 +66,13 @@ public class WorkingDaysApiResource {
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Update a Working Day", description = "Mandatory Fields\n"
|
||||
+ "recurrence,repaymentRescheduleType,extendTermForDailyRepayments,locale")
|
||||
public WorkingDaysUpdateResponse update(@Valid WorkingDaysUpdateRequest request) {
|
||||
public String update(@Valid final WorkingDaysUpdateRequest request) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateWorkingDays()
|
||||
.withJson(toApiJsonSerializer.serialize(request)).build();
|
||||
|
||||
final var command = new WorkingDaysUpdateCommand();
|
||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
command.setCommandId(System.currentTimeMillis());
|
||||
command.setCreatedAt(Instant.now());
|
||||
command.setPayload(request);
|
||||
|
||||
final Supplier<WorkingDaysUpdateResponse> response = commandPipeline.send(command);
|
||||
|
||||
return response.get();
|
||||
return toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
|
||||
+29
-21
@@ -18,38 +18,46 @@
|
||||
*/
|
||||
package org.apache.fineract.organisation.workingdays.handler;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.core.CommandHandler;
|
||||
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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequest;
|
||||
import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateResponse;
|
||||
import org.apache.fineract.organisation.workingdays.service.WorkingDaysWritePlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class UpdateWorkingDaysCommandHandler implements CommandHandler<WorkingDaysUpdateRequest, WorkingDaysUpdateResponse> {
|
||||
@Service
|
||||
@CommandType(entity = "WORKINGDAYS", action = "UPDATE")
|
||||
public class UpdateWorkingDaysCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final WorkingDaysWritePlatformService workingDaysWritePlatformService;
|
||||
|
||||
@Autowired
|
||||
public UpdateWorkingDaysCommandHandler(final WorkingDaysWritePlatformService workingDaysWritePlatformService) {
|
||||
this.workingDaysWritePlatformService = workingDaysWritePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public WorkingDaysUpdateResponse handle(Command<WorkingDaysUpdateRequest> command) {
|
||||
WorkingDaysUpdateRequest request = command.getPayload();
|
||||
Map<String, Object> changes = this.workingDaysWritePlatformService.updateWorkingDays(request);
|
||||
if (changes == null) {
|
||||
changes = Collections.emptyMap();
|
||||
}
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return WorkingDaysUpdateResponse.builder().resourceId((Long) changes.get("resourceId")).changes(changes)
|
||||
.recurrence(request.getRecurrence()).repaymentRescheduleType(request.getRepaymentRescheduleType())
|
||||
.extendTermForDailyRepayments(request.getExtendTermForDailyRepayments())
|
||||
.extendTermForRepaymentsOnHolidays(request.getExtendTermForRepaymentsOnHolidays()).build();
|
||||
final WorkingDaysUpdateRequest request = WorkingDaysUpdateRequest.builder()
|
||||
.recurrence(command.stringValueOfParameterNamed("recurrence"))
|
||||
.repaymentRescheduleType(command.integerValueSansLocaleOfParameterNamed("repaymentRescheduleType"))
|
||||
.extendTermForDailyRepayments(command.booleanObjectValueOfParameterNamed("extendTermForDailyRepayments"))
|
||||
.extendTermForRepaymentsOnHolidays(command.booleanObjectValueOfParameterNamed("extendTermForRepaymentsOnHolidays"))
|
||||
.build();
|
||||
|
||||
final Map<String, Object> changes = this.workingDaysWritePlatformService.updateWorkingDays(request);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
@@ -26,6 +26,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
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;
|
||||
@@ -114,4 +115,16 @@ public class FundsApiResource {
|
||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{fundId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Delete a Fund", description = "Deletes a fund outright. Fails if the fund is still referenced by any loan or loan product.")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FundsApiResourceSwagger.DeleteFundsFundIdResponse.class)))
|
||||
public String deleteFund(@PathParam("fundId") @Parameter(description = "fundId") final Long fundId) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteFund(fundId).build();
|
||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -55,4 +55,13 @@ final class FundsApiResourceSwagger {
|
||||
public Long resourceId;
|
||||
public PutFundsFundIdRequest changes;
|
||||
}
|
||||
|
||||
@Schema(description = "DeleteFundsFundIdResponse")
|
||||
public static final class DeleteFundsFundIdResponse {
|
||||
|
||||
private DeleteFundsFundIdResponse() {}
|
||||
|
||||
@Schema(example = "1")
|
||||
public Long resourceId;
|
||||
}
|
||||
}
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.fund.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.portfolio.fund.service.FundWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "FUND", action = "DELETE")
|
||||
public class DeleteFundCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final FundWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public DeleteFundCommandHandler(final FundWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return this.writePlatformService.deleteFund(command.entityId(), command);
|
||||
}
|
||||
}
|
||||
+2
@@ -26,4 +26,6 @@ public interface FundWritePlatformService {
|
||||
CommandProcessingResult createFund(JsonCommand command);
|
||||
|
||||
CommandProcessingResult updateFund(Long fundId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult deleteFund(Long fundId, JsonCommand command);
|
||||
}
|
||||
|
||||
+41
@@ -35,6 +35,7 @@ import org.apache.fineract.portfolio.fund.exception.FundNotFoundException;
|
||||
import org.apache.fineract.portfolio.fund.serialization.FundCommandFromApiJsonDeserializer;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -45,6 +46,7 @@ public class FundWritePlatformServiceJpaRepositoryImpl implements FundWritePlatf
|
||||
private final PlatformSecurityContext context;
|
||||
private final FundCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
private final FundRepository fundRepository;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -99,6 +101,45 @@ public class FundWritePlatformServiceJpaRepositoryImpl implements FundWritePlatf
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@CacheEvict(value = "funds", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat('fn')")
|
||||
public CommandProcessingResult deleteFund(final Long fundId, final JsonCommand command) {
|
||||
|
||||
this.context.authenticatedUser();
|
||||
|
||||
final Fund fund = this.fundRepository.findById(fundId).orElseThrow(() -> new FundNotFoundException(fundId));
|
||||
|
||||
requireNoneReference(fundId, "m_loan", "fund_id", "loan", "loans",
|
||||
"Reassign or remove the loans using this fund before deleting it.");
|
||||
requireNoneReference(fundId, "m_product_loan", "fund_id", "loan product", "loan products",
|
||||
"Remove this fund from the loan products using it before deleting it.");
|
||||
|
||||
try {
|
||||
this.fundRepository.delete(fund);
|
||||
this.fundRepository.flush();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.fund.cannot.delete.has.references",
|
||||
"This fund cannot be deleted: other records still reference it.");
|
||||
} catch (final PersistenceException dve) {
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.fund.cannot.delete.has.references",
|
||||
"This fund cannot be deleted: other records still reference it.");
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(fundId).build();
|
||||
}
|
||||
|
||||
private void requireNoneReference(final Long fundId, final String table, final String column, final String singular,
|
||||
final String plural, final String remedy) {
|
||||
final Long count = this.jdbcTemplate.queryForObject("SELECT COUNT(*) FROM " + table + " WHERE " + column + " = ?", Long.class,
|
||||
fundId);
|
||||
if (count != null && count > 0) {
|
||||
final String noun = count == 1 ? singular : plural;
|
||||
throw new PlatformDataIntegrityException("error.msg.fund.cannot.delete.still.referenced",
|
||||
"This fund cannot be deleted: it is still referenced by " + count + " " + noun + ". " + remedy, "fundId", fundId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Guaranteed to throw an exception no matter what the data integrity issue is.
|
||||
*/
|
||||
|
||||
+2
-2
@@ -42,7 +42,7 @@ public class FundConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(FundWritePlatformService.class)
|
||||
public FundWritePlatformService fundWritePlatformService(PlatformSecurityContext context,
|
||||
FundCommandFromApiJsonDeserializer fromApiJsonDeserializer, FundRepository fundRepository) {
|
||||
return new FundWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, fundRepository);
|
||||
FundCommandFromApiJsonDeserializer fromApiJsonDeserializer, FundRepository fundRepository, JdbcTemplate jdbcTemplate) {
|
||||
return new FundWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, fundRepository, jdbcTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-63
@@ -19,6 +19,7 @@
|
||||
package org.apache.fineract.portfolio.orgsetup.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.PUT;
|
||||
@@ -27,17 +28,18 @@ import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.time.Instant;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -57,6 +59,7 @@ public class BranchProfileApiResource {
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final PlatformSecurityContext context;
|
||||
private final FromJsonHelper fromJsonHelper;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@GET
|
||||
@@ -84,68 +87,23 @@ public class BranchProfileApiResource {
|
||||
@PUT
|
||||
@Path("{officeId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
public String update(@PathParam("officeId") final Long officeId, final String body) {
|
||||
this.context.authenticatedUser().validateHasPermissionTo("UPDATE_BRANCHPROFILE");
|
||||
|
||||
final Integer officeExists = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM m_office WHERE id = ?",
|
||||
Integer.class, officeId);
|
||||
if (officeExists == null || officeExists == 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.branchprofile.office.not.found",
|
||||
"Office " + officeId + " was not found.");
|
||||
}
|
||||
|
||||
public CommandProcessingResult update(@PathParam("officeId") final Long officeId, final String body) {
|
||||
// The write itself (office-exists check, branch-code uniqueness check, m_office_profile
|
||||
// upsert) now lives in BranchProfileWritePlatformService, invoked via
|
||||
// UpdateBranchProfileCommandHandler so it goes through the generic Maker-Checker gate. The
|
||||
// permission check that used to happen here explicitly is now enforced by SecurityConfig's
|
||||
// matcher plus the command-source pipeline itself, the same as every other endpoint already
|
||||
// migrated to this pipeline. A "locale" key is added if the body does not already carry one -
|
||||
// JsonCommand's BigDecimal extractor (for latitude/longitude) requires it, and the frontend's
|
||||
// payload does not currently send one.
|
||||
final var root = this.fromJsonHelper.parse(body);
|
||||
final String branchCode = this.fromJsonHelper.extractStringNamed("branchCode", root);
|
||||
final String branchType = this.fromJsonHelper.extractStringNamed("branchType", root);
|
||||
final String address = this.fromJsonHelper.extractStringNamed("address", root);
|
||||
final String pincode = this.fromJsonHelper.extractStringNamed("pincode", root);
|
||||
final String contactPhone = this.fromJsonHelper.extractStringNamed("contactPhone", root);
|
||||
final String contactEmail = this.fromJsonHelper.extractStringNamed("contactEmail", root);
|
||||
final String workingHoursStart = this.fromJsonHelper.extractStringNamed("workingHoursStart", root);
|
||||
final String workingHoursEnd = this.fromJsonHelper.extractStringNamed("workingHoursEnd", root);
|
||||
final String workingDays = this.fromJsonHelper.extractStringNamed("workingDays", root);
|
||||
final String ifscCode = this.fromJsonHelper.extractStringNamed("ifscCode", root);
|
||||
final String micrCode = this.fromJsonHelper.extractStringNamed("micrCode", root);
|
||||
final java.math.BigDecimal latitude = this.fromJsonHelper.extractBigDecimalWithLocaleNamed("latitude", root);
|
||||
final java.math.BigDecimal longitude = this.fromJsonHelper.extractBigDecimalWithLocaleNamed("longitude", root);
|
||||
|
||||
if (branchCode != null && !branchCode.isBlank()) {
|
||||
final Integer dupe = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_office_profile WHERE branch_code = ? AND office_id <> ?", Integer.class,
|
||||
branchCode, officeId);
|
||||
if (dupe != null && dupe > 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.branchprofile.code.duplicate",
|
||||
"Branch code '" + branchCode + "' is already used by another branch.");
|
||||
}
|
||||
final JsonObject json = root.getAsJsonObject();
|
||||
if (!json.has("locale")) {
|
||||
json.addProperty("locale", "en");
|
||||
}
|
||||
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
final Integer existing = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM m_office_profile WHERE office_id = ?",
|
||||
Integer.class, officeId);
|
||||
if (existing != null && existing > 0) {
|
||||
jdbcTemplate.update("""
|
||||
UPDATE m_office_profile
|
||||
SET branch_code = ?, branch_type = ?, address = ?, pincode = ?, contact_phone = ?, contact_email = ?,
|
||||
working_hours_start = ?, working_hours_end = ?, working_days = ?,
|
||||
ifsc_code = ?, micr_code = ?, latitude = ?, longitude = ?,
|
||||
updated_by_username = ?, updated_at = ?
|
||||
WHERE office_id = ?
|
||||
""", branchCode, branchType, address, pincode, contactPhone, contactEmail, workingHoursStart,
|
||||
workingHoursEnd, workingDays, ifscCode, micrCode, latitude, longitude, currentUser.getUsername(),
|
||||
Timestamp.from(Instant.now()), officeId);
|
||||
} else {
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO m_office_profile
|
||||
(office_id, branch_code, branch_type, address, pincode, contact_phone, contact_email,
|
||||
working_hours_start, working_hours_end, working_days, ifsc_code, micr_code,
|
||||
latitude, longitude, updated_by_username, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
""", officeId, branchCode, branchType, address, pincode, contactPhone, contactEmail, workingHoursStart,
|
||||
workingHoursEnd, workingDays, ifscCode, micrCode, latitude, longitude, currentUser.getUsername(),
|
||||
Timestamp.from(Instant.now()));
|
||||
}
|
||||
|
||||
return list(officeId);
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateBranchProfile(officeId)
|
||||
.withJson(this.gson.toJson(json)).build();
|
||||
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
private String baseSql() {
|
||||
|
||||
+16
-104
@@ -25,14 +25,16 @@ import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.sql.Date;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
@@ -60,6 +62,7 @@ public class InstitutionProfileApiResource {
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final PlatformSecurityContext context;
|
||||
private final FromJsonHelper fromJsonHelper;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@GET
|
||||
@@ -89,100 +92,17 @@ public class InstitutionProfileApiResource {
|
||||
|
||||
@PUT
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
public String update(final String body) {
|
||||
this.context.authenticatedUser().validateHasPermissionTo("UPDATE_INSTITUTIONPROFILE");
|
||||
ensureRowExists();
|
||||
|
||||
final var root = this.fromJsonHelper.parse(body);
|
||||
final String bankName = this.fromJsonHelper.extractStringNamed("bankName", root);
|
||||
final String legalName = this.fromJsonHelper.extractStringNamed("legalName", root);
|
||||
final String shortName = this.fromJsonHelper.extractStringNamed("shortName", root);
|
||||
final String bankCode = this.fromJsonHelper.extractStringNamed("bankCode", root);
|
||||
final String institutionCode = this.fromJsonHelper.extractStringNamed("institutionCode", root);
|
||||
final String bankType = this.fromJsonHelper.extractStringNamed("bankType", root);
|
||||
final String ownershipType = this.fromJsonHelper.extractStringNamed("ownershipType", root);
|
||||
final String parentInstitutionName = this.fromJsonHelper.extractStringNamed("parentInstitutionName", root);
|
||||
final String registrationNumber = this.fromJsonHelper.extractStringNamed("registrationNumber", root);
|
||||
final String licenseNumber = this.fromJsonHelper.extractStringNamed("licenseNumber", root);
|
||||
final LocalDate establishedDate = parseDate(this.fromJsonHelper.extractStringNamed("establishedDate", root));
|
||||
final String status = this.fromJsonHelper.extractStringNamed("status", root);
|
||||
final String country = this.fromJsonHelper.extractStringNamed("country", root);
|
||||
final String regulatoryAuthority = this.fromJsonHelper.extractStringNamed("regulatoryAuthority", root);
|
||||
final LocalDate registrationDate = parseDate(this.fromJsonHelper.extractStringNamed("registrationDate", root));
|
||||
final String licenseStatus = this.fromJsonHelper.extractStringNamed("licenseStatus", root);
|
||||
final LocalDate licenseExpiryDate = parseDate(this.fromJsonHelper.extractStringNamed("licenseExpiryDate", root));
|
||||
final String swiftBic = this.fromJsonHelper.extractStringNamed("swiftBic", root);
|
||||
final String addressLine1 = this.fromJsonHelper.extractStringNamed("addressLine1", root);
|
||||
final String addressLine2 = this.fromJsonHelper.extractStringNamed("addressLine2", root);
|
||||
final String city = this.fromJsonHelper.extractStringNamed("city", root);
|
||||
final String district = this.fromJsonHelper.extractStringNamed("district", root);
|
||||
final String state = this.fromJsonHelper.extractStringNamed("state", root);
|
||||
final String pincode = this.fromJsonHelper.extractStringNamed("pincode", root);
|
||||
final Boolean headOfficeSame = this.fromJsonHelper.parameterExists("headOfficeSameAsRegistered", root)
|
||||
? this.fromJsonHelper.extractBooleanNamed("headOfficeSameAsRegistered", root) : Boolean.TRUE;
|
||||
final String headOfficeAddress = this.fromJsonHelper.extractStringNamed("headOfficeAddress", root);
|
||||
final Boolean communicationSame = this.fromJsonHelper.parameterExists("communicationSameAsRegistered", root)
|
||||
? this.fromJsonHelper.extractBooleanNamed("communicationSameAsRegistered", root) : Boolean.TRUE;
|
||||
final String communicationAddress = this.fromJsonHelper.extractStringNamed("communicationAddress", root);
|
||||
final String contactPhone = this.fromJsonHelper.extractStringNamed("contactPhone", root);
|
||||
final String contactEmail = this.fromJsonHelper.extractStringNamed("contactEmail", root);
|
||||
final String customerCareNumber = this.fromJsonHelper.extractStringNamed("customerCareNumber", root);
|
||||
final String grievanceEmail = this.fromJsonHelper.extractStringNamed("grievanceEmail", root);
|
||||
final String website = this.fromJsonHelper.extractStringNamed("website", root);
|
||||
final String pan = this.fromJsonHelper.extractStringNamed("pan", root);
|
||||
final String tan = this.fromJsonHelper.extractStringNamed("tan", root);
|
||||
final String gstin = this.fromJsonHelper.extractStringNamed("gstin", root);
|
||||
final String statementHeaderName = this.fromJsonHelper.extractStringNamed("statementHeaderName", root);
|
||||
final String passbookHeaderName = this.fromJsonHelper.extractStringNamed("passbookHeaderName", root);
|
||||
final String receiptHeaderName = this.fromJsonHelper.extractStringNamed("receiptHeaderName", root);
|
||||
final String termsAndConditions = this.fromJsonHelper.extractStringNamed("termsAndConditions", root);
|
||||
final String authorizedPersonName = this.fromJsonHelper.extractStringNamed("authorizedPersonName", root);
|
||||
final String authorizedPersonDesignation = this.fromJsonHelper.extractStringNamed("authorizedPersonDesignation",
|
||||
root);
|
||||
final String authorizedPersonContact = this.fromJsonHelper.extractStringNamed("authorizedPersonContact", root);
|
||||
final String authorizedPersonEmail = this.fromJsonHelper.extractStringNamed("authorizedPersonEmail", root);
|
||||
|
||||
// A status flip is stamped automatically the first time it happens, rather than asking the
|
||||
// user to also remember to set a date — the previous row's own status is what "the first
|
||||
// time" is judged against.
|
||||
final String previousStatus = jdbcTemplate.queryForObject("SELECT status FROM m_institution_profile WHERE id = 1",
|
||||
String.class);
|
||||
final boolean turningActive = "ACTIVE".equalsIgnoreCase(status) && !"ACTIVE".equalsIgnoreCase(previousStatus);
|
||||
final boolean turningInactive = "INACTIVE".equalsIgnoreCase(status) && !"INACTIVE".equalsIgnoreCase(previousStatus);
|
||||
final LocalDate today = LocalDate.now();
|
||||
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
jdbcTemplate.update("""
|
||||
UPDATE m_institution_profile
|
||||
SET bank_name = ?, legal_name = ?, short_name = ?, bank_code = ?, institution_code = ?,
|
||||
bank_type = ?, ownership_type = ?, parent_institution_name = ?,
|
||||
registration_number = ?, license_number = ?, established_date = ?, status = ?, country = ?,
|
||||
regulatory_authority = ?, registration_date = ?, license_status = ?, license_expiry_date = ?,
|
||||
swift_bic = ?,
|
||||
address_line1 = ?, address_line2 = ?, city = ?, district = ?, state = ?, pincode = ?,
|
||||
head_office_same_as_registered = ?, head_office_address = ?,
|
||||
communication_same_as_registered = ?, communication_address = ?,
|
||||
contact_phone = ?, contact_email = ?, customer_care_number = ?, grievance_email = ?,
|
||||
website = ?, pan = ?, tan = ?, gstin = ?,
|
||||
statement_header_name = ?, passbook_header_name = ?, receipt_header_name = ?,
|
||||
terms_and_conditions = ?,
|
||||
authorized_person_name = ?, authorized_person_designation = ?, authorized_person_contact = ?,
|
||||
authorized_person_email = ?,
|
||||
activation_date = CASE WHEN ? THEN ? ELSE activation_date END,
|
||||
deactivation_date = CASE WHEN ? THEN ? ELSE deactivation_date END,
|
||||
updated_by_username = ?, updated_at = ?
|
||||
WHERE id = 1
|
||||
""", bankName, legalName, shortName, bankCode, institutionCode, bankType, ownershipType,
|
||||
parentInstitutionName, registrationNumber, licenseNumber, toSqlDate(establishedDate), status, country,
|
||||
regulatoryAuthority, toSqlDate(registrationDate), licenseStatus, toSqlDate(licenseExpiryDate), swiftBic,
|
||||
addressLine1, addressLine2, city, district, state, pincode, headOfficeSame, headOfficeAddress,
|
||||
communicationSame, communicationAddress, contactPhone, contactEmail, customerCareNumber, grievanceEmail,
|
||||
website, pan, tan, gstin, statementHeaderName, passbookHeaderName, receiptHeaderName, termsAndConditions,
|
||||
authorizedPersonName, authorizedPersonDesignation, authorizedPersonContact, authorizedPersonEmail,
|
||||
turningActive, toSqlDate(today), turningInactive, toSqlDate(today), currentUser.getUsername(),
|
||||
Timestamp.from(Instant.now()));
|
||||
|
||||
return get();
|
||||
public CommandProcessingResult update(final String body) {
|
||||
// The write itself (the JDBC UPDATE of m_institution_profile) now lives in
|
||||
// InstitutionProfileWritePlatformService, invoked via UpdateInstitutionProfileCommandHandler so
|
||||
// it goes through the generic Maker-Checker gate. The permission check that used to happen here
|
||||
// explicitly is now enforced by SecurityConfig's matcher plus the command-source pipeline
|
||||
// itself, the same as every other endpoint already migrated to this pipeline (Currency, Working
|
||||
// Days, Payment Type). `body` is forwarded to the command as-is - it already carries the
|
||||
// `locale` key the frontend sends, which JsonCommand's BigDecimal/date extractors would
|
||||
// otherwise require.
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateInstitutionProfile().withJson(body).build();
|
||||
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
private void ensureRowExists() {
|
||||
@@ -204,14 +124,6 @@ public class InstitutionProfileApiResource {
|
||||
return codes.isEmpty() ? null : codes.get(0);
|
||||
}
|
||||
|
||||
private LocalDate parseDate(final String raw) {
|
||||
return (raw != null && !raw.isBlank()) ? LocalDate.parse(raw) : null;
|
||||
}
|
||||
|
||||
private Date toSqlDate(final LocalDate d) {
|
||||
return d == null ? null : Date.valueOf(d);
|
||||
}
|
||||
|
||||
private Map<String, Object> toResult(final Map<String, Object> row) {
|
||||
final Map<String, Object> out = new LinkedHashMap<>();
|
||||
out.put("bankName", row.get("bank_name"));
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.orgsetup.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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.portfolio.orgsetup.service.BranchProfileWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "BRANCHPROFILE", action = "UPDATE")
|
||||
public class UpdateBranchProfileCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final BranchProfileWritePlatformService branchProfileWritePlatformService;
|
||||
|
||||
@Autowired
|
||||
public UpdateBranchProfileCommandHandler(final BranchProfileWritePlatformService branchProfileWritePlatformService) {
|
||||
this.branchProfileWritePlatformService = branchProfileWritePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final Long officeId = command.entityId();
|
||||
|
||||
final CommandProcessingResult result = this.branchProfileWritePlatformService.updateBranchProfile(officeId,
|
||||
command.stringValueOfParameterNamed("branchCode"), command.stringValueOfParameterNamed("branchType"),
|
||||
command.stringValueOfParameterNamed("address"), command.stringValueOfParameterNamed("pincode"),
|
||||
command.stringValueOfParameterNamed("contactPhone"), command.stringValueOfParameterNamed("contactEmail"),
|
||||
command.stringValueOfParameterNamed("workingHoursStart"),
|
||||
command.stringValueOfParameterNamed("workingHoursEnd"), command.stringValueOfParameterNamed("workingDays"),
|
||||
command.stringValueOfParameterNamed("ifscCode"), command.stringValueOfParameterNamed("micrCode"),
|
||||
command.bigDecimalValueOfParameterNamed("latitude"), command.bigDecimalValueOfParameterNamed("longitude"));
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(result.getResourceId()) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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.orgsetup.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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.portfolio.orgsetup.service.InstitutionProfileWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "INSTITUTIONPROFILE", action = "UPDATE")
|
||||
public class UpdateInstitutionProfileCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final InstitutionProfileWritePlatformService institutionProfileWritePlatformService;
|
||||
|
||||
@Autowired
|
||||
public UpdateInstitutionProfileCommandHandler(
|
||||
final InstitutionProfileWritePlatformService institutionProfileWritePlatformService) {
|
||||
this.institutionProfileWritePlatformService = institutionProfileWritePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final Boolean headOfficeSame = command.parameterExists("headOfficeSameAsRegistered")
|
||||
? command.booleanObjectValueOfParameterNamed("headOfficeSameAsRegistered") : Boolean.TRUE;
|
||||
final Boolean communicationSame = command.parameterExists("communicationSameAsRegistered")
|
||||
? command.booleanObjectValueOfParameterNamed("communicationSameAsRegistered") : Boolean.TRUE;
|
||||
|
||||
final CommandProcessingResult result = this.institutionProfileWritePlatformService.updateInstitutionProfile(
|
||||
command.stringValueOfParameterNamed("bankName"), command.stringValueOfParameterNamed("legalName"),
|
||||
command.stringValueOfParameterNamed("shortName"), command.stringValueOfParameterNamed("bankCode"),
|
||||
command.stringValueOfParameterNamed("institutionCode"), command.stringValueOfParameterNamed("bankType"),
|
||||
command.stringValueOfParameterNamed("ownershipType"),
|
||||
command.stringValueOfParameterNamed("parentInstitutionName"),
|
||||
command.stringValueOfParameterNamed("registrationNumber"),
|
||||
command.stringValueOfParameterNamed("licenseNumber"), command.stringValueOfParameterNamed("establishedDate"),
|
||||
command.stringValueOfParameterNamed("status"), command.stringValueOfParameterNamed("country"),
|
||||
command.stringValueOfParameterNamed("regulatoryAuthority"),
|
||||
command.stringValueOfParameterNamed("registrationDate"),
|
||||
command.stringValueOfParameterNamed("licenseStatus"),
|
||||
command.stringValueOfParameterNamed("licenseExpiryDate"), command.stringValueOfParameterNamed("swiftBic"),
|
||||
command.stringValueOfParameterNamed("addressLine1"), command.stringValueOfParameterNamed("addressLine2"),
|
||||
command.stringValueOfParameterNamed("city"), command.stringValueOfParameterNamed("district"),
|
||||
command.stringValueOfParameterNamed("state"), command.stringValueOfParameterNamed("pincode"), headOfficeSame,
|
||||
command.stringValueOfParameterNamed("headOfficeAddress"), communicationSame,
|
||||
command.stringValueOfParameterNamed("communicationAddress"),
|
||||
command.stringValueOfParameterNamed("contactPhone"), command.stringValueOfParameterNamed("contactEmail"),
|
||||
command.stringValueOfParameterNamed("customerCareNumber"),
|
||||
command.stringValueOfParameterNamed("grievanceEmail"), command.stringValueOfParameterNamed("website"),
|
||||
command.stringValueOfParameterNamed("pan"), command.stringValueOfParameterNamed("tan"),
|
||||
command.stringValueOfParameterNamed("gstin"), command.stringValueOfParameterNamed("statementHeaderName"),
|
||||
command.stringValueOfParameterNamed("passbookHeaderName"),
|
||||
command.stringValueOfParameterNamed("receiptHeaderName"),
|
||||
command.stringValueOfParameterNamed("termsAndConditions"),
|
||||
command.stringValueOfParameterNamed("authorizedPersonName"),
|
||||
command.stringValueOfParameterNamed("authorizedPersonDesignation"),
|
||||
command.stringValueOfParameterNamed("authorizedPersonContact"),
|
||||
command.stringValueOfParameterNamed("authorizedPersonEmail"));
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(result.getResourceId()) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.orgsetup.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
public interface BranchProfileWritePlatformService {
|
||||
|
||||
CommandProcessingResult updateBranchProfile(Long officeId, String branchCode, String branchType, String address,
|
||||
String pincode, String contactPhone, String contactEmail, String workingHoursStart, String workingHoursEnd,
|
||||
String workingDays, String ifscCode, String micrCode, BigDecimal latitude, BigDecimal longitude);
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.orgsetup.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
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.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Moved verbatim out of {@code BranchProfileApiResource.update()} so the write can be invoked from a
|
||||
* {@code NewCommandSourceHandler} for Maker-Checker support. The resource's own {@code list()} (read)
|
||||
* method is untouched.
|
||||
*/
|
||||
@Service
|
||||
public class BranchProfileWritePlatformServiceImpl implements BranchProfileWritePlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
@Autowired
|
||||
public BranchProfileWritePlatformServiceImpl(final JdbcTemplate jdbcTemplate, final PlatformSecurityContext context) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult updateBranchProfile(final Long officeId, final String branchCode, final String branchType,
|
||||
final String address, final String pincode, final String contactPhone, final String contactEmail,
|
||||
final String workingHoursStart, final String workingHoursEnd, final String workingDays, final String ifscCode,
|
||||
final String micrCode, final BigDecimal latitude, final BigDecimal longitude) {
|
||||
|
||||
final Integer officeExists = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM m_office WHERE id = ?", Integer.class,
|
||||
officeId);
|
||||
if (officeExists == null || officeExists == 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.branchprofile.office.not.found",
|
||||
"Office " + officeId + " was not found.");
|
||||
}
|
||||
|
||||
if (branchCode != null && !branchCode.isBlank()) {
|
||||
final Integer dupe = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_office_profile WHERE branch_code = ? AND office_id <> ?", Integer.class,
|
||||
branchCode, officeId);
|
||||
if (dupe != null && dupe > 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.branchprofile.code.duplicate",
|
||||
"Branch code '" + branchCode + "' is already used by another branch.");
|
||||
}
|
||||
}
|
||||
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
final Integer existing = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM m_office_profile WHERE office_id = ?",
|
||||
Integer.class, officeId);
|
||||
if (existing != null && existing > 0) {
|
||||
jdbcTemplate.update("""
|
||||
UPDATE m_office_profile
|
||||
SET branch_code = ?, branch_type = ?, address = ?, pincode = ?, contact_phone = ?, contact_email = ?,
|
||||
working_hours_start = ?, working_hours_end = ?, working_days = ?,
|
||||
ifsc_code = ?, micr_code = ?, latitude = ?, longitude = ?,
|
||||
updated_by_username = ?, updated_at = ?
|
||||
WHERE office_id = ?
|
||||
""", branchCode, branchType, address, pincode, contactPhone, contactEmail, workingHoursStart,
|
||||
workingHoursEnd, workingDays, ifscCode, micrCode, latitude, longitude, currentUser.getUsername(),
|
||||
Timestamp.from(Instant.now()), officeId);
|
||||
} else {
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO m_office_profile
|
||||
(office_id, branch_code, branch_type, address, pincode, contact_phone, contact_email,
|
||||
working_hours_start, working_hours_end, working_days, ifsc_code, micr_code,
|
||||
latitude, longitude, updated_by_username, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
""", officeId, branchCode, branchType, address, pincode, contactPhone, contactEmail, workingHoursStart,
|
||||
workingHoursEnd, workingDays, ifscCode, micrCode, latitude, longitude, currentUser.getUsername(),
|
||||
Timestamp.from(Instant.now()));
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder().withEntityId(officeId).build();
|
||||
}
|
||||
}
|
||||
+36
@@ -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.orgsetup.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
public interface InstitutionProfileWritePlatformService {
|
||||
|
||||
CommandProcessingResult updateInstitutionProfile(String bankName, String legalName, String shortName, String bankCode,
|
||||
String institutionCode, String bankType, String ownershipType, String parentInstitutionName,
|
||||
String registrationNumber, String licenseNumber, String establishedDate, String status, String country,
|
||||
String regulatoryAuthority, String registrationDate, String licenseStatus, String licenseExpiryDate,
|
||||
String swiftBic, String addressLine1, String addressLine2, String city, String district, String state,
|
||||
String pincode, Boolean headOfficeSameAsRegistered, String headOfficeAddress,
|
||||
Boolean communicationSameAsRegistered, String communicationAddress, String contactPhone, String contactEmail,
|
||||
String customerCareNumber, String grievanceEmail, String website, String pan, String tan, String gstin,
|
||||
String statementHeaderName, String passbookHeaderName, String receiptHeaderName, String termsAndConditions,
|
||||
String authorizedPersonName, String authorizedPersonDesignation, String authorizedPersonContact,
|
||||
String authorizedPersonEmail);
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 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.orgsetup.service;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Moved verbatim out of {@code InstitutionProfileApiResource.update()} so the write can be invoked
|
||||
* from a {@code NewCommandSourceHandler} for Maker-Checker support. {@code ensureRowExists()},
|
||||
* {@code parseDate()} and {@code toSqlDate()} are copies of the resource's own private helpers rather
|
||||
* than shared ones - the resource's untouched {@code get()} method keeps its own copies so this change
|
||||
* does not have to touch that unrelated read path at all.
|
||||
*/
|
||||
@Service
|
||||
public class InstitutionProfileWritePlatformServiceImpl implements InstitutionProfileWritePlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
@Autowired
|
||||
public InstitutionProfileWritePlatformServiceImpl(final JdbcTemplate jdbcTemplate, final PlatformSecurityContext context) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult updateInstitutionProfile(final String bankName, final String legalName,
|
||||
final String shortName, final String bankCode, final String institutionCode, final String bankType,
|
||||
final String ownershipType, final String parentInstitutionName, final String registrationNumber,
|
||||
final String licenseNumber, final String establishedDate, final String status, final String country,
|
||||
final String regulatoryAuthority, final String registrationDate, final String licenseStatus,
|
||||
final String licenseExpiryDate, final String swiftBic, final String addressLine1, final String addressLine2,
|
||||
final String city, final String district, final String state, final String pincode,
|
||||
final Boolean headOfficeSameAsRegistered, final String headOfficeAddress,
|
||||
final Boolean communicationSameAsRegistered, final String communicationAddress, final String contactPhone,
|
||||
final String contactEmail, final String customerCareNumber, final String grievanceEmail, final String website,
|
||||
final String pan, final String tan, final String gstin, final String statementHeaderName,
|
||||
final String passbookHeaderName, final String receiptHeaderName, final String termsAndConditions,
|
||||
final String authorizedPersonName, final String authorizedPersonDesignation, final String authorizedPersonContact,
|
||||
final String authorizedPersonEmail) {
|
||||
|
||||
ensureRowExists();
|
||||
|
||||
// A status flip is stamped automatically the first time it happens, rather than asking the
|
||||
// user to also remember to set a date — the previous row's own status is what "the first
|
||||
// time" is judged against.
|
||||
final String previousStatus = jdbcTemplate.queryForObject("SELECT status FROM m_institution_profile WHERE id = 1",
|
||||
String.class);
|
||||
final boolean turningActive = "ACTIVE".equalsIgnoreCase(status) && !"ACTIVE".equalsIgnoreCase(previousStatus);
|
||||
final boolean turningInactive = "INACTIVE".equalsIgnoreCase(status) && !"INACTIVE".equalsIgnoreCase(previousStatus);
|
||||
final LocalDate today = LocalDate.now();
|
||||
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
jdbcTemplate.update("""
|
||||
UPDATE m_institution_profile
|
||||
SET bank_name = ?, legal_name = ?, short_name = ?, bank_code = ?, institution_code = ?,
|
||||
bank_type = ?, ownership_type = ?, parent_institution_name = ?,
|
||||
registration_number = ?, license_number = ?, established_date = ?, status = ?, country = ?,
|
||||
regulatory_authority = ?, registration_date = ?, license_status = ?, license_expiry_date = ?,
|
||||
swift_bic = ?,
|
||||
address_line1 = ?, address_line2 = ?, city = ?, district = ?, state = ?, pincode = ?,
|
||||
head_office_same_as_registered = ?, head_office_address = ?,
|
||||
communication_same_as_registered = ?, communication_address = ?,
|
||||
contact_phone = ?, contact_email = ?, customer_care_number = ?, grievance_email = ?,
|
||||
website = ?, pan = ?, tan = ?, gstin = ?,
|
||||
statement_header_name = ?, passbook_header_name = ?, receipt_header_name = ?,
|
||||
terms_and_conditions = ?,
|
||||
authorized_person_name = ?, authorized_person_designation = ?, authorized_person_contact = ?,
|
||||
authorized_person_email = ?,
|
||||
activation_date = CASE WHEN ? THEN ? ELSE activation_date END,
|
||||
deactivation_date = CASE WHEN ? THEN ? ELSE deactivation_date END,
|
||||
updated_by_username = ?, updated_at = ?
|
||||
WHERE id = 1
|
||||
""", bankName, legalName, shortName, bankCode, institutionCode, bankType, ownershipType,
|
||||
parentInstitutionName, registrationNumber, licenseNumber, toSqlDate(parseDate(establishedDate)), status, country,
|
||||
regulatoryAuthority, toSqlDate(parseDate(registrationDate)), licenseStatus, toSqlDate(parseDate(licenseExpiryDate)),
|
||||
swiftBic, addressLine1, addressLine2, city, district, state, pincode, headOfficeSameAsRegistered,
|
||||
headOfficeAddress, communicationSameAsRegistered, communicationAddress, contactPhone, contactEmail,
|
||||
customerCareNumber, grievanceEmail, website, pan, tan, gstin, statementHeaderName, passbookHeaderName,
|
||||
receiptHeaderName, termsAndConditions, authorizedPersonName, authorizedPersonDesignation,
|
||||
authorizedPersonContact, authorizedPersonEmail, turningActive, toSqlDate(today), turningInactive,
|
||||
toSqlDate(today), currentUser.getUsername(), Timestamp.from(Instant.now()));
|
||||
|
||||
return new CommandProcessingResultBuilder().withEntityId(1L).build();
|
||||
}
|
||||
|
||||
private void ensureRowExists() {
|
||||
final Integer count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM m_institution_profile WHERE id = 1",
|
||||
Integer.class);
|
||||
if (count == null || count == 0) {
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO m_institution_profile (id, status, created_by_username, created_at)
|
||||
VALUES (1, 'ACTIVE', ?, ?)
|
||||
""", currentUser.getUsername(), Timestamp.from(Instant.now()));
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate parseDate(final String raw) {
|
||||
return (raw != null && !raw.isBlank()) ? LocalDate.parse(raw) : null;
|
||||
}
|
||||
|
||||
private Date toSqlDate(final LocalDate d) {
|
||||
return d == null ? null : Date.valueOf(d);
|
||||
}
|
||||
}
|
||||
+18
-94
@@ -34,23 +34,19 @@ import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.sponsordd.domain.SponsorBank;
|
||||
import org.apache.fineract.portfolio.sponsordd.domain.SponsorBankRepository;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
|
||||
@Path("/v1/sponsorbanks")
|
||||
@Component
|
||||
@@ -59,8 +55,8 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
public class SponsorBankApiResource {
|
||||
|
||||
private final SponsorBankRepository sponsorBankRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final org.springframework.jdbc.core.JdbcTemplate jdbcTemplate;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final Gson gson = new GsonBuilder().serializeNulls().create();
|
||||
|
||||
/** Mirrors SponsorDDApiResource.safely() - see that class for the full rationale. */
|
||||
@@ -125,78 +121,19 @@ public class SponsorBankApiResource {
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects a payable account that cannot serve as one.
|
||||
*
|
||||
* A DD payable is money the bank owes the sponsor until the draft is presented, so it has to be
|
||||
* a liability account, and it has to accept postings. Catching that here means an administrator
|
||||
* is told while choosing the account, rather than a clerk discovering it when a draft fails.
|
||||
*/
|
||||
private void validateDdPayableGlAccount(final Long glAccountId) {
|
||||
if (glAccountId == null) {
|
||||
return;
|
||||
}
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(
|
||||
"SELECT gl_code, name, classification_enum, account_usage, disabled FROM acc_gl_account WHERE id = ?",
|
||||
glAccountId);
|
||||
if (rows.isEmpty()) {
|
||||
throw new IllegalArgumentException("The selected DD Payable GL account does not exist.");
|
||||
}
|
||||
final Map<String, Object> gl = rows.get(0);
|
||||
final String label = gl.get("gl_code") + " · " + gl.get("name");
|
||||
// 2 is LIABILITY in Fineract's GLAccountType enum.
|
||||
if (((Number) gl.get("classification_enum")).intValue() != 2) {
|
||||
throw new IllegalArgumentException(
|
||||
"DD Payable must be a LIABILITY account — a draft is money owed to the sponsor bank until it is presented. "
|
||||
+ label + " is not a liability account.");
|
||||
}
|
||||
// 2 is HEADER in Fineract's GLAccountUsage enum; nothing can be posted to a header.
|
||||
if (((Number) gl.get("account_usage")).intValue() == 2) {
|
||||
throw new IllegalArgumentException("DD Payable must be a detail account, not a header. Pick a postable account under "
|
||||
+ label + ".");
|
||||
}
|
||||
// Read through both shapes: the column comes back as a Boolean on some drivers and as a
|
||||
// 0/1 Number on others, and a plain Boolean cast would throw on the latter.
|
||||
final Object disabled = gl.get("disabled");
|
||||
final boolean isDisabled = disabled instanceof Boolean b ? b
|
||||
: disabled instanceof Number n && n.intValue() != 0;
|
||||
if (isDisabled) {
|
||||
throw new IllegalArgumentException("The selected DD Payable GL account (" + label + ") is disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Create a Sponsor Bank", description = "Creates a new Sponsor Bank.")
|
||||
public CommandProcessingResult createSponsorBank(final SponsorBankRequest request) {
|
||||
// The actual validation/persistence now lives in SponsorBankWritePlatformService, invoked via
|
||||
// a NewCommandSourceHandler so the write goes through the generic Maker-Checker gate - see
|
||||
// CreateSponsorBankCommandHandler. safely() is kept around the call so a validation failure
|
||||
// still comes back as the same 400 it always has, rather than an unmapped 500.
|
||||
return safely(() -> {
|
||||
if (request.getIfscCode() != null && !request.getIfscCode().isBlank()
|
||||
&& !request.getIfscCode().trim().toUpperCase().matches("^[A-Z]{4}0[A-Z0-9]{6}$")) {
|
||||
throw new IllegalArgumentException(
|
||||
"IFSC Code must be 11 characters: 4 letters, then '0', then 6 alphanumeric characters (e.g. SBIN0001234).");
|
||||
}
|
||||
if (request.getName() == null || request.getName().isBlank()) {
|
||||
throw new IllegalArgumentException("Sponsor Bank Name is required.");
|
||||
}
|
||||
validateDdPayableGlAccount(request.getDdPayableGlAccountId());
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
// Set field by field rather than through the all-args constructor: that constructor is
|
||||
// positional, so adding a column silently shifts every argument after it, and the shift
|
||||
// type-checks whenever two neighbours share a type.
|
||||
final SponsorBank sponsorBank = new SponsorBank();
|
||||
sponsorBank.setName(request.getName().trim());
|
||||
sponsorBank.setDescription(request.getDescription());
|
||||
sponsorBank.setIfscCode(request.getIfscCode() != null && !request.getIfscCode().isBlank()
|
||||
? request.getIfscCode().trim().toUpperCase() : null);
|
||||
sponsorBank.setBankCode(request.getBankCode());
|
||||
sponsorBank.setSwiftCode(request.getSwiftCode() != null && !request.getSwiftCode().isBlank()
|
||||
? request.getSwiftCode().trim().toUpperCase() : null);
|
||||
sponsorBank.setDdPayableGlAccountId(request.getDdPayableGlAccountId());
|
||||
sponsorBank.setCreatedBy(currentUser);
|
||||
sponsorBank.setCreatedDate(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||
this.sponsorBankRepository.saveAndFlush(sponsorBank);
|
||||
return new CommandProcessingResultBuilder().withEntityId(sponsorBank.getId()).build();
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createSponsorBank()
|
||||
.withJson(this.gson.toJson(request)).build();
|
||||
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -214,27 +151,14 @@ public class SponsorBankApiResource {
|
||||
@Operation(summary = "Update a Sponsor Bank", description = "Updates a sponsor bank's details and the DD Payable GL account its drafts are credited to.")
|
||||
public CommandProcessingResult updateSponsorBank(@PathParam("sponsorBankId") final Long sponsorBankId,
|
||||
final SponsorBankRequest request) {
|
||||
// The actual validation/persistence now lives in SponsorBankWritePlatformService, invoked via
|
||||
// a NewCommandSourceHandler so the write goes through the generic Maker-Checker gate - see
|
||||
// UpdateSponsorBankCommandHandler. safely() is kept around the call so a validation failure
|
||||
// still comes back as the same 400 it always has, rather than an unmapped 500.
|
||||
return safely(() -> {
|
||||
final SponsorBank sponsorBank = this.sponsorBankRepository.findById(sponsorBankId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Sponsor bank not found."));
|
||||
if (request.getIfscCode() != null && !request.getIfscCode().isBlank()
|
||||
&& !request.getIfscCode().trim().toUpperCase().matches("^[A-Z]{4}0[A-Z0-9]{6}$")) {
|
||||
throw new IllegalArgumentException(
|
||||
"IFSC Code must be 11 characters: 4 letters, then '0', then 6 alphanumeric characters (e.g. SBIN0001234).");
|
||||
}
|
||||
validateDdPayableGlAccount(request.getDdPayableGlAccountId());
|
||||
if (request.getName() != null && !request.getName().isBlank()) {
|
||||
sponsorBank.setName(request.getName().trim());
|
||||
}
|
||||
sponsorBank.setDescription(request.getDescription());
|
||||
sponsorBank.setIfscCode(request.getIfscCode() != null && !request.getIfscCode().isBlank()
|
||||
? request.getIfscCode().trim().toUpperCase() : null);
|
||||
sponsorBank.setBankCode(request.getBankCode());
|
||||
sponsorBank.setSwiftCode(request.getSwiftCode() != null && !request.getSwiftCode().isBlank()
|
||||
? request.getSwiftCode().trim().toUpperCase() : null);
|
||||
sponsorBank.setDdPayableGlAccountId(request.getDdPayableGlAccountId());
|
||||
this.sponsorBankRepository.saveAndFlush(sponsorBank);
|
||||
return new CommandProcessingResultBuilder().withEntityId(sponsorBank.getId()).build();
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSponsorBank(sponsorBankId)
|
||||
.withJson(this.gson.toJson(request)).build();
|
||||
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+26
-10
@@ -41,6 +41,9 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDBatchIssueResult;
|
||||
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDCashierDepositOptionData;
|
||||
@@ -76,6 +79,7 @@ public class SponsorDDApiResource {
|
||||
private final SponsorDDReprintRequestWritePlatformService reprintRequestWritePlatformService;
|
||||
private final SponsorDDRevokeRequestWritePlatformService revokeRequestWritePlatformService;
|
||||
private final SponsorDDAmendmentRequestWritePlatformService amendmentRequestWritePlatformService;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final PlatformSecurityContext context;
|
||||
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private final Gson gson = new GsonBuilder().serializeNulls().create();
|
||||
@@ -539,16 +543,28 @@ public class SponsorDDApiResource {
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Configure a Sponsor DD GL mapping", description = "Creates or updates the DD Payable / cheque-clearing GL accounts for a sponsor bank / payment type.")
|
||||
public CommandProcessingResult upsertGlMapping(final GlMappingRequest request) {
|
||||
return safely(() -> this.issueRequestWritePlatformService.upsertGlMapping(
|
||||
request.getSponsorBankId(),
|
||||
request.getDdPayableGlAccountId(),
|
||||
request.getChequeClearingGlAccountId(),
|
||||
request.getCashGlAccountId(),
|
||||
request.getChargesIncomeGlAccountId(),
|
||||
request.getCashSuspenseGlAccountId(),
|
||||
request.getGstPercentage(),
|
||||
request.getGstPayableGlAccountId(),
|
||||
request.getDdLimitAmount()));
|
||||
// Routed through the generic command-source pipeline (CommandWrapperBuilder ->
|
||||
// logCommandSource() -> UpdateSponsorDdGlMappingCommandHandler) so this genuinely gains
|
||||
// Maker-Checker support; the write itself (issueRequestWritePlatformService.upsertGlMapping)
|
||||
// is unchanged and now called from that handler instead of directly from here. The JSON is
|
||||
// built by hand (not Gson-serializing `request` directly) purely to include a "locale" key -
|
||||
// JsonCommand's BigDecimal extractor requires one, and GlMappingRequest has no such field.
|
||||
return safely(() -> {
|
||||
final JsonObject json = new JsonObject();
|
||||
json.addProperty("sponsorBankId", request.getSponsorBankId());
|
||||
json.addProperty("ddPayableGlAccountId", request.getDdPayableGlAccountId());
|
||||
json.addProperty("chequeClearingGlAccountId", request.getChequeClearingGlAccountId());
|
||||
json.addProperty("cashGlAccountId", request.getCashGlAccountId());
|
||||
json.addProperty("chargesIncomeGlAccountId", request.getChargesIncomeGlAccountId());
|
||||
json.addProperty("cashSuspenseGlAccountId", request.getCashSuspenseGlAccountId());
|
||||
json.addProperty("gstPercentage", request.getGstPercentage());
|
||||
json.addProperty("gstPayableGlAccountId", request.getGstPayableGlAccountId());
|
||||
json.addProperty("ddLimitAmount", request.getDdLimitAmount());
|
||||
json.addProperty("locale", "en");
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSponsorDdGlMapping()
|
||||
.withJson(this.gson.toJson(json)).build();
|
||||
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
});
|
||||
}
|
||||
|
||||
@POST
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.sponsordd.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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.portfolio.sponsordd.api.SponsorBankApiResource.SponsorBankRequest;
|
||||
import org.apache.fineract.portfolio.sponsordd.service.SponsorBankWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "SPONSORBANK", action = "CREATE")
|
||||
public class CreateSponsorBankCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SponsorBankWritePlatformService sponsorBankWritePlatformService;
|
||||
|
||||
@Autowired
|
||||
public CreateSponsorBankCommandHandler(final SponsorBankWritePlatformService sponsorBankWritePlatformService) {
|
||||
this.sponsorBankWritePlatformService = sponsorBankWritePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final SponsorBankRequest request = new SponsorBankRequest();
|
||||
request.setName(command.stringValueOfParameterNamed("name"));
|
||||
request.setDescription(command.stringValueOfParameterNamed("description"));
|
||||
request.setIfscCode(command.stringValueOfParameterNamed("ifscCode"));
|
||||
request.setBankCode(command.stringValueOfParameterNamed("bankCode"));
|
||||
request.setSwiftCode(command.stringValueOfParameterNamed("swiftCode"));
|
||||
request.setDdPayableGlAccountId(command.longValueOfParameterNamed("ddPayableGlAccountId"));
|
||||
|
||||
final CommandProcessingResult result = this.sponsorBankWritePlatformService.createSponsorBank(request);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(result.getResourceId()) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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.sponsordd.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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.portfolio.sponsordd.api.SponsorBankApiResource.SponsorBankRequest;
|
||||
import org.apache.fineract.portfolio.sponsordd.service.SponsorBankWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "SPONSORBANK", action = "UPDATE")
|
||||
public class UpdateSponsorBankCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SponsorBankWritePlatformService sponsorBankWritePlatformService;
|
||||
|
||||
@Autowired
|
||||
public UpdateSponsorBankCommandHandler(final SponsorBankWritePlatformService sponsorBankWritePlatformService) {
|
||||
this.sponsorBankWritePlatformService = sponsorBankWritePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final SponsorBankRequest request = new SponsorBankRequest();
|
||||
request.setName(command.stringValueOfParameterNamed("name"));
|
||||
request.setDescription(command.stringValueOfParameterNamed("description"));
|
||||
request.setIfscCode(command.stringValueOfParameterNamed("ifscCode"));
|
||||
request.setBankCode(command.stringValueOfParameterNamed("bankCode"));
|
||||
request.setSwiftCode(command.stringValueOfParameterNamed("swiftCode"));
|
||||
request.setDdPayableGlAccountId(command.longValueOfParameterNamed("ddPayableGlAccountId"));
|
||||
|
||||
final CommandProcessingResult result = this.sponsorBankWritePlatformService.updateSponsorBank(command.entityId(),
|
||||
request);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(result.getResourceId()) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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.sponsordd.handler;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.portfolio.sponsordd.service.SponsorDDIssueRequestWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* The underlying write ({@link SponsorDDIssueRequestWritePlatformService#upsertGlMapping}) is a
|
||||
* genuine upsert - one call creates the single {@code m_sponsor_dd_gl_mapping} row the first time and
|
||||
* updates it every time after - so there is exactly one command/permission here rather than a
|
||||
* separate CREATE and UPDATE, matching that reality instead of inventing a split the endpoint has
|
||||
* never had.
|
||||
*/
|
||||
@Service
|
||||
@CommandType(entity = "SPONSORDDGLMAPPING", action = "UPDATE")
|
||||
public class UpdateSponsorDdGlMappingCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SponsorDDIssueRequestWritePlatformService issueRequestWritePlatformService;
|
||||
|
||||
@Autowired
|
||||
public UpdateSponsorDdGlMappingCommandHandler(
|
||||
final SponsorDDIssueRequestWritePlatformService issueRequestWritePlatformService) {
|
||||
this.issueRequestWritePlatformService = issueRequestWritePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final Long sponsorBankId = command.longValueOfParameterNamed("sponsorBankId");
|
||||
final Long ddPayableGlAccountId = command.longValueOfParameterNamed("ddPayableGlAccountId");
|
||||
final Long chequeClearingGlAccountId = command.longValueOfParameterNamed("chequeClearingGlAccountId");
|
||||
final Long cashGlAccountId = command.longValueOfParameterNamed("cashGlAccountId");
|
||||
final Long chargesIncomeGlAccountId = command.longValueOfParameterNamed("chargesIncomeGlAccountId");
|
||||
final Long cashSuspenseGlAccountId = command.longValueOfParameterNamed("cashSuspenseGlAccountId");
|
||||
final BigDecimal gstPercentage = command.bigDecimalValueOfParameterNamed("gstPercentage");
|
||||
final Long gstPayableGlAccountId = command.longValueOfParameterNamed("gstPayableGlAccountId");
|
||||
final BigDecimal ddLimitAmount = command.bigDecimalValueOfParameterNamed("ddLimitAmount");
|
||||
|
||||
final CommandProcessingResult result = this.issueRequestWritePlatformService.upsertGlMapping(sponsorBankId,
|
||||
ddPayableGlAccountId, chequeClearingGlAccountId, cashGlAccountId, chargesIncomeGlAccountId,
|
||||
cashSuspenseGlAccountId, gstPercentage, gstPayableGlAccountId, ddLimitAmount);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(result.getResourceId()) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.sponsordd.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.sponsordd.api.SponsorBankApiResource.SponsorBankRequest;
|
||||
|
||||
public interface SponsorBankWritePlatformService {
|
||||
|
||||
CommandProcessingResult createSponsorBank(SponsorBankRequest request);
|
||||
|
||||
CommandProcessingResult updateSponsorBank(Long sponsorBankId, SponsorBankRequest request);
|
||||
}
|
||||
+147
@@ -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.sponsordd.service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.sponsordd.api.SponsorBankApiResource.SponsorBankRequest;
|
||||
import org.apache.fineract.portfolio.sponsordd.domain.SponsorBank;
|
||||
import org.apache.fineract.portfolio.sponsordd.domain.SponsorBankRepository;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Moved verbatim out of {@code SponsorBankApiResource.createSponsorBank()} and
|
||||
* {@code updateSponsorBank()} so both writes can be invoked from a {@code NewCommandSourceHandler}
|
||||
* for Maker-Checker support. {@link #validateDdPayableGlAccount(Long)} is a copy of the resource's
|
||||
* own (now-unused) validator, kept here since both CREATE and UPDATE rely on it.
|
||||
*/
|
||||
@Service
|
||||
public class SponsorBankWritePlatformServiceImpl implements SponsorBankWritePlatformService {
|
||||
|
||||
private final SponsorBankRepository sponsorBankRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Autowired
|
||||
public SponsorBankWritePlatformServiceImpl(final SponsorBankRepository sponsorBankRepository,
|
||||
final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate) {
|
||||
this.sponsorBankRepository = sponsorBankRepository;
|
||||
this.context = context;
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult createSponsorBank(final SponsorBankRequest request) {
|
||||
if (request.getIfscCode() != null && !request.getIfscCode().isBlank()
|
||||
&& !request.getIfscCode().trim().toUpperCase().matches("^[A-Z]{4}0[A-Z0-9]{6}$")) {
|
||||
throw new IllegalArgumentException(
|
||||
"IFSC Code must be 11 characters: 4 letters, then '0', then 6 alphanumeric characters (e.g. SBIN0001234).");
|
||||
}
|
||||
if (request.getName() == null || request.getName().isBlank()) {
|
||||
throw new IllegalArgumentException("Sponsor Bank Name is required.");
|
||||
}
|
||||
validateDdPayableGlAccount(request.getDdPayableGlAccountId());
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
// Set field by field rather than through the all-args constructor: that constructor is
|
||||
// positional, so adding a column silently shifts every argument after it, and the shift
|
||||
// type-checks whenever two neighbours share a type.
|
||||
final SponsorBank sponsorBank = new SponsorBank();
|
||||
sponsorBank.setName(request.getName().trim());
|
||||
sponsorBank.setDescription(request.getDescription());
|
||||
sponsorBank.setIfscCode(request.getIfscCode() != null && !request.getIfscCode().isBlank()
|
||||
? request.getIfscCode().trim().toUpperCase() : null);
|
||||
sponsorBank.setBankCode(request.getBankCode());
|
||||
sponsorBank.setSwiftCode(request.getSwiftCode() != null && !request.getSwiftCode().isBlank()
|
||||
? request.getSwiftCode().trim().toUpperCase() : null);
|
||||
sponsorBank.setDdPayableGlAccountId(request.getDdPayableGlAccountId());
|
||||
sponsorBank.setCreatedBy(currentUser);
|
||||
sponsorBank.setCreatedDate(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||
this.sponsorBankRepository.saveAndFlush(sponsorBank);
|
||||
return new CommandProcessingResultBuilder().withEntityId(sponsorBank.getId()).build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult updateSponsorBank(final Long sponsorBankId, final SponsorBankRequest request) {
|
||||
final SponsorBank sponsorBank = this.sponsorBankRepository.findById(sponsorBankId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Sponsor bank not found."));
|
||||
if (request.getIfscCode() != null && !request.getIfscCode().isBlank()
|
||||
&& !request.getIfscCode().trim().toUpperCase().matches("^[A-Z]{4}0[A-Z0-9]{6}$")) {
|
||||
throw new IllegalArgumentException(
|
||||
"IFSC Code must be 11 characters: 4 letters, then '0', then 6 alphanumeric characters (e.g. SBIN0001234).");
|
||||
}
|
||||
validateDdPayableGlAccount(request.getDdPayableGlAccountId());
|
||||
if (request.getName() != null && !request.getName().isBlank()) {
|
||||
sponsorBank.setName(request.getName().trim());
|
||||
}
|
||||
sponsorBank.setDescription(request.getDescription());
|
||||
sponsorBank.setIfscCode(request.getIfscCode() != null && !request.getIfscCode().isBlank()
|
||||
? request.getIfscCode().trim().toUpperCase() : null);
|
||||
sponsorBank.setBankCode(request.getBankCode());
|
||||
sponsorBank.setSwiftCode(request.getSwiftCode() != null && !request.getSwiftCode().isBlank()
|
||||
? request.getSwiftCode().trim().toUpperCase() : null);
|
||||
sponsorBank.setDdPayableGlAccountId(request.getDdPayableGlAccountId());
|
||||
this.sponsorBankRepository.saveAndFlush(sponsorBank);
|
||||
return new CommandProcessingResultBuilder().withEntityId(sponsorBank.getId()).build();
|
||||
}
|
||||
|
||||
/** Copied from {@code SponsorBankApiResource} - see that class for the full rationale. */
|
||||
private void validateDdPayableGlAccount(final Long glAccountId) {
|
||||
if (glAccountId == null) {
|
||||
return;
|
||||
}
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(
|
||||
"SELECT gl_code, name, classification_enum, account_usage, disabled FROM acc_gl_account WHERE id = ?",
|
||||
glAccountId);
|
||||
if (rows.isEmpty()) {
|
||||
throw new IllegalArgumentException("The selected DD Payable GL account does not exist.");
|
||||
}
|
||||
final Map<String, Object> gl = rows.get(0);
|
||||
final String label = gl.get("gl_code") + " · " + gl.get("name");
|
||||
// 2 is LIABILITY in Fineract's GLAccountType enum.
|
||||
if (((Number) gl.get("classification_enum")).intValue() != 2) {
|
||||
throw new IllegalArgumentException(
|
||||
"DD Payable must be a LIABILITY account — a draft is money owed to the sponsor bank until it is presented. "
|
||||
+ label + " is not a liability account.");
|
||||
}
|
||||
// 2 is HEADER in Fineract's GLAccountUsage enum; nothing can be posted to a header.
|
||||
if (((Number) gl.get("account_usage")).intValue() == 2) {
|
||||
throw new IllegalArgumentException("DD Payable must be a detail account, not a header. Pick a postable account under "
|
||||
+ label + ".");
|
||||
}
|
||||
// Read through both shapes: the column comes back as a Boolean on some drivers and as a
|
||||
// 0/1 Number on others, and a plain Boolean cast would throw on the latter.
|
||||
final Object disabled = gl.get("disabled");
|
||||
final boolean isDisabled = disabled instanceof Boolean b ? b
|
||||
: disabled instanceof Number n && n.intValue() != 0;
|
||||
if (isDisabled) {
|
||||
throw new IllegalArgumentException("The selected DD Payable GL account (" + label + ") is disabled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@
|
||||
*/
|
||||
package org.apache.fineract.spm.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
@@ -37,10 +36,14 @@ import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.spm.data.SurveyData;
|
||||
import org.apache.fineract.spm.domain.Survey;
|
||||
@@ -57,6 +60,8 @@ public class SpmApiResource {
|
||||
|
||||
private final PlatformSecurityContext securityContext;
|
||||
private final SpmService spmService;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final DefaultToApiJsonSerializer<CommandProcessingResult> toApiJsonSerializer;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@@ -103,12 +108,10 @@ public class SpmApiResource {
|
||||
@Operation(summary = "Create a Survey", description = "Adds a new survey to collect client related data.\n" + "\n"
|
||||
+ "Mandatory Fields\n" + "\n" + "countryCode, key, name, questions, responses, sequenceNo, text, description")
|
||||
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||
public String createSurvey(@Parameter(description = "Create survey") final SurveyData surveyData) {
|
||||
this.securityContext.authenticatedUser();
|
||||
final Survey survey = SurveyMapper.map(surveyData, new Survey());
|
||||
this.spmService.createSurvey(survey);
|
||||
return getResponse(survey.getId());
|
||||
|
||||
public String createSurvey(@Parameter(description = "Create survey") final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createSurvey().withJson(apiRequestBodyAsJson).build();
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@@ -116,12 +119,10 @@ public class SpmApiResource {
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Transactional
|
||||
public String editSurvey(@PathParam("id") final Long id, final SurveyData surveyData) {
|
||||
this.securityContext.authenticatedUser();
|
||||
final Survey surveyToUpdate = this.spmService.findById(id);
|
||||
final Survey survey = SurveyMapper.map(surveyData, surveyToUpdate);
|
||||
this.spmService.updateSurvey(survey);
|
||||
return getResponse(survey.getId());
|
||||
public String editSurvey(@PathParam("id") final Long id, final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSurvey(id).withJson(apiRequestBodyAsJson).build();
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -131,22 +132,17 @@ public class SpmApiResource {
|
||||
@Transactional
|
||||
@Operation(summary = "Deactivate Survey", description = "")
|
||||
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||
public void activateOrDeactivateSurvey(@PathParam("id") final Long id, @QueryParam("command") final String command) {
|
||||
this.securityContext.authenticatedUser();
|
||||
public String activateOrDeactivateSurvey(@PathParam("id") final Long id, @QueryParam("command") final String command) {
|
||||
final CommandWrapperBuilder builder = new CommandWrapperBuilder();
|
||||
final CommandWrapper commandRequest;
|
||||
if (command != null && command.equalsIgnoreCase("activate")) {
|
||||
this.spmService.activateSurvey(id);
|
||||
commandRequest = builder.activateSurvey(id).build();
|
||||
} else if (command != null && command.equalsIgnoreCase("deactivate")) {
|
||||
this.spmService.deactivateSurvey(id);
|
||||
commandRequest = builder.deactivateSurvey(id).build();
|
||||
} else {
|
||||
throw new UnrecognizedQueryParamException("command", command);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String getResponse(Long id) {
|
||||
Gson gson = new Gson();
|
||||
HashMap<String, Object> response = new HashMap<>();
|
||||
response.put("resourceId", id);
|
||||
return gson.toJson(response);
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
}
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.spm.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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.spm.service.SpmService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "SURVEY", action = "ACTIVATE")
|
||||
public class ActivateSurveyCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SpmService spmService;
|
||||
|
||||
@Autowired
|
||||
public ActivateSurveyCommandHandler(final SpmService spmService) {
|
||||
this.spmService = spmService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
this.spmService.activateSurvey(command.entityId());
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(command.entityId()) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+71
@@ -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.spm.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.spm.data.SurveyData;
|
||||
import org.apache.fineract.spm.domain.Survey;
|
||||
import org.apache.fineract.spm.service.SpmService;
|
||||
import org.apache.fineract.spm.util.SurveyMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "SURVEY", action = "CREATE")
|
||||
public class CreateSurveyCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SpmService spmService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Autowired
|
||||
public CreateSurveyCommandHandler(final SpmService spmService, final ObjectMapper objectMapper) {
|
||||
this.spmService = spmService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final SurveyData surveyData = readSurveyData(command);
|
||||
final Survey survey = SurveyMapper.map(surveyData, new Survey());
|
||||
this.spmService.createSurvey(survey);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(survey.getId()) //
|
||||
.build();
|
||||
}
|
||||
|
||||
private SurveyData readSurveyData(final JsonCommand command) {
|
||||
try {
|
||||
return this.objectMapper.readValue(command.json(), SurveyData.class);
|
||||
} catch (final JsonProcessingException e) {
|
||||
throw new PlatformDataIntegrityException("error.msg.survey.invalid.json", "Invalid survey payload: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.spm.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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.spm.service.SpmService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "SURVEY", action = "DEACTIVATE")
|
||||
public class DeactivateSurveyCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SpmService spmService;
|
||||
|
||||
@Autowired
|
||||
public DeactivateSurveyCommandHandler(final SpmService spmService) {
|
||||
this.spmService = spmService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
this.spmService.deactivateSurvey(command.entityId());
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(command.entityId()) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.spm.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
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.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.spm.data.SurveyData;
|
||||
import org.apache.fineract.spm.domain.Survey;
|
||||
import org.apache.fineract.spm.service.SpmService;
|
||||
import org.apache.fineract.spm.util.SurveyMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "SURVEY", action = "UPDATE")
|
||||
public class UpdateSurveyCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final SpmService spmService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Autowired
|
||||
public UpdateSurveyCommandHandler(final SpmService spmService, final ObjectMapper objectMapper) {
|
||||
this.spmService = spmService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final SurveyData surveyData = readSurveyData(command);
|
||||
final Survey surveyToUpdate = this.spmService.findById(command.entityId());
|
||||
final Survey survey = SurveyMapper.map(surveyData, surveyToUpdate);
|
||||
this.spmService.updateSurvey(survey);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(survey.getId()) //
|
||||
.build();
|
||||
}
|
||||
|
||||
private SurveyData readSurveyData(final JsonCommand command) {
|
||||
try {
|
||||
return this.objectMapper.readValue(command.json(), SurveyData.class);
|
||||
} catch (final JsonProcessingException e) {
|
||||
throw new PlatformDataIntegrityException("error.msg.survey.invalid.json", "Invalid survey payload: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-1
@@ -97,15 +97,50 @@ public class PermissionReadPlatformServiceImpl implements PermissionReadPlatform
|
||||
+ " order by p.grouping, coalesce(entity_name, ''), p.code";
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission codes whose approval workflow is a bespoke, unconditional pending-request mechanism
|
||||
* (m_user_pending_request / m_share_account_pending_edit / m_current_account_pending_edit /
|
||||
* m_client_pending_edit) that never reads {@code can_maker_checker} - verified by inspecting every
|
||||
* write-service on this path (UserPendingWritePlatformServiceImpl, ShareAccountEditWritePlatformServiceJpaRepositoryImpl,
|
||||
* CurrentAccountEditWritePlatformServiceJpaRepositoryImpl, and the client edit equivalent): none of
|
||||
* them consult ConfigurationDomainService in any way. Toggling this checkbox on the Configure
|
||||
* Maker-Checker Tasks screen therefore has zero effect on these tasks in either direction, so they
|
||||
* are excluded here rather than presented as configurable when they are not. CREATE_USER/UPDATE_USER
|
||||
* remain governed by the separate global "maker-checker" configuration switch (System ->
|
||||
* Configurations), not by this per-task flag.
|
||||
*/
|
||||
private static final String DEAD_MAKER_CHECKER_CODES = "'CREATE_USER','UPDATE_USER',"
|
||||
+ "'SUBMITEDIT_SHAREACCOUNT','APPROVEEDIT_SHAREACCOUNT','REJECTEDIT_SHAREACCOUNT',"
|
||||
+ "'SUBMITEDIT_CURRENTACCOUNT','APPROVEEDIT_CURRENTACCOUNT','REJECTEDIT_CURRENTACCOUNT',"
|
||||
+ "'SUBMITEDIT_CLIENT','APPROVEEDIT_CLIENT','REJECTEDIT_CLIENT'";
|
||||
|
||||
/*
|
||||
* Unlike DEAD_MAKER_CHECKER_CODES above, these codes ARE read by the live maker-checker pipeline
|
||||
* (CommandSourceService.isMakerCheckerEnabledForTask()) - they are excluded from this screen for a
|
||||
* different reason: UPDATE_PERMISSION governs this very "Configure Maker Checker Tasks" screen
|
||||
* (toggling which tasks require maker-checker), and UPDATE_CONFIGURATION governs the global
|
||||
* maker-checker on/off switch and other System -> Configurations settings. If either were flipped
|
||||
* to can_maker_checker=true and the approving checker.user lacked CHECKER_SUPER_USER, an admin
|
||||
* could lock themselves out of ever approving further changes to maker-checker configuration
|
||||
* itself, with no ordinary way to reverse it. Excluding them here only hides them from this
|
||||
* screen; it does not touch their can_maker_checker value or their normal enforcement.
|
||||
*/
|
||||
private static final String SELF_REFERENTIAL_MAKER_CHECKER_CODES = "'UPDATE_PERMISSION','UPDATE_CONFIGURATION'";
|
||||
|
||||
public String makerCheckerablePermissionSchema() {
|
||||
/*
|
||||
* get all 'Maker-Checkerable' permissions - Maintenance permissions (i.e. exclude the 'special' grouping,
|
||||
* the READ permissions and the CHECKER permissions
|
||||
* the READ permissions and the CHECKER permissions), and exclude permission codes whose approval
|
||||
* workflow does not actually read can_maker_checker (see DEAD_MAKER_CHECKER_CODES above), as well as
|
||||
* codes that control maker-checker configuration itself (see SELF_REFERENTIAL_MAKER_CHECKER_CODES
|
||||
* above) to avoid a self-lockout.
|
||||
*/
|
||||
|
||||
return "select p.grouping, p.code, p.entity_name as entityName, p.action_name as actionName, p.can_maker_checker as selected"
|
||||
+ " from m_permission p " + " where " + sqlGenerator.escape("grouping")
|
||||
+ " != 'special' and code not like 'READ_%' and code not like '%\\_CHECKER'"
|
||||
+ " and code not in (" + DEAD_MAKER_CHECKER_CODES + ")"
|
||||
+ " and code not in (" + SELF_REFERENTIAL_MAKER_CHECKER_CODES + ")"
|
||||
+ " order by p.grouping, coalesce(entity_name, ''), p.code";
|
||||
}
|
||||
|
||||
|
||||
+101
-1
@@ -36,7 +36,13 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.Page;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.staff.domain.Staff;
|
||||
import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
|
||||
import org.apache.fineract.useradministration.data.UserPendingRequestData;
|
||||
import org.apache.fineract.useradministration.domain.Role;
|
||||
import org.apache.fineract.useradministration.domain.RoleRepository;
|
||||
import org.apache.fineract.useradministration.domain.UserPendingRequest;
|
||||
import org.apache.fineract.useradministration.domain.UserPendingRequestRepositoryWrapper;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
@@ -51,8 +57,13 @@ public class UserPendingReadPlatformServiceImpl implements UserPendingReadPlatfo
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final UserPendingRequestRepositoryWrapper pendingRequestRepositoryWrapper;
|
||||
private final OfficeRepositoryWrapper officeRepositoryWrapper;
|
||||
private final StaffRepositoryWrapper staffRepositoryWrapper;
|
||||
private final RoleRepository roleRepository;
|
||||
private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson();
|
||||
|
||||
private static final String UNKNOWN_REFERENCE_FORMAT = "Unknown (ID: %s)";
|
||||
|
||||
private static final String SELECT_SQL = """
|
||||
SELECT r.id AS id,
|
||||
r.user_id AS userId,
|
||||
@@ -115,7 +126,96 @@ public class UserPendingReadPlatformServiceImpl implements UserPendingReadPlatfo
|
||||
public UserPendingRequestData retrievePendingRequest(final Long id) {
|
||||
final UserPendingRequest request = this.pendingRequestRepositoryWrapper.findOneWithNotFoundDetection(id);
|
||||
final String sql = SELECT_SQL + " WHERE r.id = ?";
|
||||
return this.jdbcTemplate.queryForObject(sql, new UserPendingDetailRowMapper(), id);
|
||||
final UserPendingRequestData data = this.jdbcTemplate.queryForObject(sql, new UserPendingDetailRowMapper(), id);
|
||||
resolveDisplayNames(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves office/staff/role ids on the pending-user comparison payload to human-readable names, and folds
|
||||
* the point-in-time original-side name fields (officeName/staffName/roleNames) into their corresponding
|
||||
* id-keyed rows so the compare dialog shows one row per field instead of a redundant name-only row.
|
||||
*/
|
||||
private void resolveDisplayNames(final UserPendingRequestData data) {
|
||||
final Map<String, Object> originalValues = data.getOriginalValues();
|
||||
final Map<String, Object> proposedValues = data.getProposedValues();
|
||||
|
||||
// CREATE_USER requests have no original snapshot - nothing to fold on that side.
|
||||
if (originalValues != null && !originalValues.isEmpty()) {
|
||||
foldNameIntoId(originalValues, "officeName", "officeId");
|
||||
foldNameIntoId(originalValues, "staffName", "staffId");
|
||||
if (originalValues.containsKey("roleNames")) {
|
||||
originalValues.put("roles", sortNames(toStringList(originalValues.get("roleNames"))));
|
||||
originalValues.remove("roleNames");
|
||||
}
|
||||
}
|
||||
|
||||
if (proposedValues != null && !proposedValues.isEmpty()) {
|
||||
if (proposedValues.get("officeId") instanceof Number officeIdNum) {
|
||||
proposedValues.put("officeId", resolveOfficeName(officeIdNum.longValue()));
|
||||
}
|
||||
if (proposedValues.get("staffId") instanceof Number staffIdNum) {
|
||||
proposedValues.put("staffId", resolveStaffName(staffIdNum.longValue()));
|
||||
}
|
||||
if (proposedValues.get("roles") instanceof Collection<?> rolesCollection) {
|
||||
proposedValues.put("roles", sortNames(resolveRoleNames(toStringList(rolesCollection))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void foldNameIntoId(final Map<String, Object> values, final String nameKey, final String idKey) {
|
||||
if (values.containsKey(nameKey)) {
|
||||
values.put(idKey, values.get(nameKey));
|
||||
values.remove(nameKey);
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveOfficeName(final Long officeId) {
|
||||
try {
|
||||
final Office office = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId);
|
||||
return office.getName();
|
||||
} catch (final RuntimeException e) {
|
||||
return String.format(UNKNOWN_REFERENCE_FORMAT, officeId);
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveStaffName(final Long staffId) {
|
||||
try {
|
||||
final Staff staff = this.staffRepositoryWrapper.findOneWithNotFoundDetection(staffId);
|
||||
return staff.displayName();
|
||||
} catch (final RuntimeException e) {
|
||||
return String.format(UNKNOWN_REFERENCE_FORMAT, staffId);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> resolveRoleNames(final List<String> roleIds) {
|
||||
final List<String> names = new ArrayList<>();
|
||||
for (final String roleIdStr : roleIds) {
|
||||
try {
|
||||
final Long roleId = Long.valueOf(roleIdStr);
|
||||
final Role role = this.roleRepository.findById(roleId).orElse(null);
|
||||
names.add(role != null ? role.getName() : String.format(UNKNOWN_REFERENCE_FORMAT, roleId));
|
||||
} catch (final NumberFormatException e) {
|
||||
names.add(roleIdStr);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private static List<String> sortNames(final List<String> names) {
|
||||
final List<String> sorted = new ArrayList<>(names);
|
||||
sorted.sort(String.CASE_INSENSITIVE_ORDER);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private static List<String> toStringList(final Object value) {
|
||||
final List<String> result = new ArrayList<>();
|
||||
if (value instanceof Collection<?> collection) {
|
||||
for (final Object item : collection) {
|
||||
result.add(String.valueOf(item));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class UserPendingRowMapper implements RowMapper<UserPendingRequestData> {
|
||||
|
||||
+1
-1
@@ -162,8 +162,8 @@ public class UserPendingWritePlatformServiceImpl implements UserPendingWritePlat
|
||||
originalSnapshot.put("officeId", userToUpdate.getOffice() != null ? userToUpdate.getOffice().getId() : null);
|
||||
originalSnapshot.put("officeName", userToUpdate.getOffice() != null ? userToUpdate.getOffice().getName() : null);
|
||||
originalSnapshot.put("staffId", userToUpdate.getStaff() != null ? userToUpdate.getStaff().getId() : null);
|
||||
originalSnapshot.put("staffName", userToUpdate.getStaff() != null ? userToUpdate.getStaff().displayName() : null);
|
||||
originalSnapshot.put("passwordNeverExpires", userToUpdate.getPasswordNeverExpires());
|
||||
originalSnapshot.put("isSelfServiceUser", userToUpdate.isSelfServiceUser());
|
||||
final Set<String> roleNames = new HashSet<>();
|
||||
final Set<Long> roleIds = new HashSet<>();
|
||||
if (userToUpdate.getRoles() != null) {
|
||||
|
||||
@@ -46,7 +46,7 @@ fineract.security.oauth2.client.registrations.frontend-client.require-authorizat
|
||||
fineract.tenant.host=${FINERACT_DEFAULT_TENANTDB_HOSTNAME:localhost}
|
||||
fineract.tenant.port=${FINERACT_DEFAULT_TENANTDB_PORT:5432}
|
||||
fineract.tenant.username=${FINERACT_DEFAULT_TENANTDB_UID:postgres}
|
||||
fineract.tenant.password=${FINERACT_DEFAULT_TENANTDB_PWD:Keshav@898966}
|
||||
fineract.tenant.password=${FINERACT_DEFAULT_TENANTDB_PWD:anshul@9009}
|
||||
fineract.tenant.parameters=${FINERACT_DEFAULT_TENANTDB_CONN_PARAMS:}
|
||||
fineract.tenant.timezone=${FINERACT_DEFAULT_TENANTDB_TIMEZONE:Asia/Kolkata}
|
||||
fineract.tenant.identifier=${FINERACT_DEFAULT_TENANTDB_IDENTIFIER:default}
|
||||
@@ -415,7 +415,7 @@ server.tomcat.mbeanregistry.enabled=${FINERACT_SERVER_TOMCAT_MBEANREGISTRY_ENABL
|
||||
spring.datasource.hikari.driverClassName=${FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME:org.postgresql.Driver}
|
||||
spring.datasource.hikari.jdbcUrl=${FINERACT_HIKARI_JDBC_URL:jdbc:postgresql://localhost:5432/fineract_tenants}
|
||||
spring.datasource.hikari.username=${FINERACT_HIKARI_USERNAME:postgres}
|
||||
spring.datasource.hikari.password=${FINERACT_HIKARI_PASSWORD:Keshav@898966}
|
||||
spring.datasource.hikari.password=${FINERACT_HIKARI_PASSWORD:anshul@9009}
|
||||
spring.datasource.hikari.minimumIdle=${FINERACT_HIKARI_MINIMUM_IDLE:3}
|
||||
spring.datasource.hikari.maximumPoolSize=${FINERACT_HIKARI_MAXIMUM_POOL_SIZE:10}
|
||||
spring.datasource.hikari.idleTimeout=${FINERACT_HIKARI_IDLE_TIMEOUT:60000}
|
||||
|
||||
@@ -120,6 +120,15 @@
|
||||
<include file="parts/0572_add_renewal_grace_period_to_deposit_product_term.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0573_add_renewal_grace_period_to_saving_product_temp.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0574_add_renewal_grace_period_to_deposit_account_term.xml" relativeToChangelogFile="true"/>
|
||||
<!-- NOTE: numbered 0571/0572 like the dev-latest FD migrations directly above - both were added
|
||||
independently (this Maker-Checker work on feature/bugfix, the FD work on dev-latest) before
|
||||
either branch saw the other's changes, so the numeric prefixes collide. Left as originally
|
||||
named (not renumbered) since only the changelog include list, not the migration files
|
||||
themselves, is in scope for this merge; Liquibase identifies changesets by id+author+file
|
||||
path, not by filename number, so the collision is cosmetic only and does not affect
|
||||
correctness. -->
|
||||
<include file="parts/0571_add_sponsor_dd_gl_mapping_permission.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0572_add_sponsor_bank_update_permission.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0571_create_rbi_reserve_config.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0572_create_oss_return_permissions.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0573_drop_not_null_on_sponsor_dd_mapping_payable.xml" relativeToChangelogFile="true"/>
|
||||
@@ -136,5 +145,15 @@
|
||||
<include file="parts/0583_save_rbi_form_a_return_job.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0584_form9_slr_assets.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0582_create_tds_config.xml" relativeToChangelogFile="true"/>
|
||||
<!-- Our local 0575 Loan Originator checker-permission migration collided with dev-latest's own
|
||||
0575_rbi_reserve_custom_roles.xml once merged (it was only stashed/uncommitted locally, so it
|
||||
never appeared on either side of this merge as a real conflict - the collision only became
|
||||
visible once the stash was inspected). Renumbered to 0583 (the first number free after
|
||||
dev-latest's highest, 0582) to avoid ambiguity; content/semantics unchanged, only the file
|
||||
name and this changeset's id were updated to match. -->
|
||||
<include file="parts/0583_add_loan_originator_checker_permissions.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0584_add_manage_survey_permissions.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0585_add_loan_cob_catchup_permission.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0586_add_system_module_checker_permissions.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0587_fix_current_product_od_scheme_pending_columns.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
|
||||
<!-- No permission code existed at all for the Sponsor DD GST/GL settings mapping before this -
|
||||
the endpoint (POST /sponsordd/gl-mapping) was reachable by any authenticated user with no
|
||||
permission check anywhere in the request path. This adds the one permission the endpoint now
|
||||
requires, now that it is routed through the generic Maker-Checker command pipeline
|
||||
(UpdateSponsorDdGlMappingCommandHandler). One code, not a CREATE/UPDATE pair: the underlying
|
||||
write is a genuine upsert (m_sponsor_dd_gl_mapping is always a single row), so there is no
|
||||
separate create step to give its own code to. -->
|
||||
<changeSet id="0571_add_sponsor_dd_gl_mapping_permission" author="cbs-anshul">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'UPDATE_SPONSORDDGLMAPPING'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="UPDATE_SPONSORDDGLMAPPING"/>
|
||||
<column name="entity_name" value="SPONSORDDGLMAPPING"/>
|
||||
<column name="action_name" value="UPDATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<!-- Granted to the same roles the sibling org-setup permissions (0511) grant to - Super user and
|
||||
any manager-named role - since configuring the sponsor DD GL/GST mapping is a manager/admin
|
||||
act, not a teller or cashier one. -->
|
||||
<changeSet id="0571_grant_sponsor_dd_gl_mapping_permission" author="cbs-anshul">
|
||||
<sql>
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM m_role r
|
||||
CROSS JOIN m_permission p
|
||||
WHERE p.code = 'UPDATE_SPONSORDDGLMAPPING'
|
||||
AND (LOWER(r.name) LIKE '%manager%' OR LOWER(r.name) = 'super user')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp
|
||||
WHERE rp.role_id = r.id AND rp.permission_id = p.id
|
||||
)
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
|
||||
<!-- CREATE_SPONSORBANK and READ_SPONSORBANK already exist (seeded outside this changelog history -
|
||||
see the drift noted when CREATE_SPONSORBANK was wired for Maker-Checker), but no UPDATE
|
||||
permission for this entity has ever existed, so PUT /sponsorbanks/{id} had no permission code
|
||||
to check anywhere in its request path. This adds it, seeded consistently with the existing
|
||||
Sponsor Bank permissions (same grouping/entity_name, can_maker_checker off by default - an
|
||||
administrator opts in via Configure Maker-Checker Tasks, same as CREATE_SPONSORBANK was). -->
|
||||
<changeSet id="0572_add_sponsor_bank_update_permission" author="cbs-anshul">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'UPDATE_SPONSORBANK'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="UPDATE_SPONSORBANK"/>
|
||||
<column name="entity_name" value="SPONSORBANK"/>
|
||||
<column name="action_name" value="UPDATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<!-- Granted to every role that already holds CREATE_SPONSORBANK, so whoever could create a sponsor
|
||||
bank before this change can also still edit one after it - no role loses access it already
|
||||
had. -->
|
||||
<changeSet id="0572_grant_sponsor_bank_update_permission" author="cbs-anshul">
|
||||
<sql>
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT rp.role_id, p.id
|
||||
FROM m_role_permission rp
|
||||
JOIN m_permission existing ON existing.id = rp.permission_id AND existing.code = 'CREATE_SPONSORBANK'
|
||||
CROSS JOIN m_permission p
|
||||
WHERE p.code = 'UPDATE_SPONSORBANK'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp2
|
||||
WHERE rp2.role_id = rp.role_id AND rp2.permission_id = p.id
|
||||
)
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
|
||||
<!-- The 5 LOAN_ORIGINATOR write permissions (CREATE/UPDATE/DELETE/ATTACH/DETACH_LOAN_ORIGINATOR)
|
||||
were seeded with can_maker_checker=false and no corresponding "_CHECKER" permission rows.
|
||||
AppUser.validateHasCheckerPermissionTo(permissionCode) (fineract-core/.../useradministration/
|
||||
domain/AppUser.java) computes checkerPermissionName = permissionCode.toUpperCase() + "_CHECKER"
|
||||
and requires the caller to hold either CHECKER_SUPER_USER or that exact permission code -
|
||||
since m_role_permission.permission_id foreign-keys to m_permission(id), a "_CHECKER" row that
|
||||
was never seeded can never be granted to any ordinary role. This means that even if
|
||||
can_maker_checker were flipped on for these 5 permissions later, no non-super-user checker
|
||||
could ever approve the resulting AWAITING_APPROVAL commands. This changeset only adds the
|
||||
missing "_CHECKER" rows so the approval path is structurally ready; it deliberately does NOT
|
||||
touch can_maker_checker on the base permissions (still false, unchanged). -->
|
||||
<changeSet id="0583_add_loan_originator_checker_permissions" author="cbs-anshul">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'CREATE_LOAN_ORIGINATOR_CHECKER'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="CREATE_LOAN_ORIGINATOR_CHECKER"/>
|
||||
<column name="entity_name" value="LOAN_ORIGINATOR"/>
|
||||
<column name="action_name" value="CREATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="UPDATE_LOAN_ORIGINATOR_CHECKER"/>
|
||||
<column name="entity_name" value="LOAN_ORIGINATOR"/>
|
||||
<column name="action_name" value="UPDATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="DELETE_LOAN_ORIGINATOR_CHECKER"/>
|
||||
<column name="entity_name" value="LOAN_ORIGINATOR"/>
|
||||
<column name="action_name" value="DELETE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="ATTACH_LOAN_ORIGINATOR_CHECKER"/>
|
||||
<column name="entity_name" value="LOAN_ORIGINATOR"/>
|
||||
<column name="action_name" value="ATTACH_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="DETACH_LOAN_ORIGINATOR_CHECKER"/>
|
||||
<column name="entity_name" value="LOAN_ORIGINATOR"/>
|
||||
<column name="action_name" value="DETACH_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<!-- Granted to whichever roles already hold the corresponding base LOAN_ORIGINATOR permission
|
||||
(confirmed live: Branch Manager and Clerk hold all 5 base write permissions) - the same
|
||||
"grant to roles that already hold the base permission" convention already used for
|
||||
UPDATE_SPONSORBANK (0572_add_sponsor_bank_update_permission.xml). -->
|
||||
<changeSet id="0583_grant_loan_originator_checker_permissions" author="cbs-anshul">
|
||||
<sql>
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT rp.role_id, checker.id
|
||||
FROM m_role_permission rp
|
||||
JOIN m_permission base ON base.id = rp.permission_id
|
||||
JOIN m_permission checker ON checker.code = base.code || '_CHECKER'
|
||||
WHERE base.code IN ('CREATE_LOAN_ORIGINATOR', 'UPDATE_LOAN_ORIGINATOR', 'DELETE_LOAN_ORIGINATOR',
|
||||
'ATTACH_LOAN_ORIGINATOR', 'DETACH_LOAN_ORIGINATOR')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp2
|
||||
WHERE rp2.role_id = rp.role_id AND rp2.permission_id = checker.id
|
||||
)
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
|
||||
<!-- The Manage Surveys screen (SPM scorecards, spm.api.SpmApiResource) previously performed
|
||||
create/update/activate/deactivate directly against SpmService with only a login check -
|
||||
no permission check at any layer. These 4 write operations have now been wired into the
|
||||
standard command pipeline (CommandWrapperBuilder.createSurvey/updateSurvey/activateSurvey/
|
||||
deactivateSurvey -> CreateSurveyCommandHandler/UpdateSurveyCommandHandler/
|
||||
ActivateSurveyCommandHandler/DeactivateSurveyCommandHandler, entity "SURVEY"), which requires
|
||||
these permission rows to exist so PortfolioCommandSourceWritePlatformServiceImpl.logCommandSource()
|
||||
can authorize the caller. Seeded with can_maker_checker=false: no prior maker-checker behaviour
|
||||
existed for this screen, and enabling maker-checker is a business decision left for later. -->
|
||||
<changeSet id="0584_add_manage_survey_permissions" author="cbs-anshul">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'CREATE_SURVEY'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="configuration"/>
|
||||
<column name="code" value="CREATE_SURVEY"/>
|
||||
<column name="entity_name" value="SURVEY"/>
|
||||
<column name="action_name" value="CREATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="configuration"/>
|
||||
<column name="code" value="UPDATE_SURVEY"/>
|
||||
<column name="entity_name" value="SURVEY"/>
|
||||
<column name="action_name" value="UPDATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="configuration"/>
|
||||
<column name="code" value="ACTIVATE_SURVEY"/>
|
||||
<column name="entity_name" value="SURVEY"/>
|
||||
<column name="action_name" value="ACTIVATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="configuration"/>
|
||||
<column name="code" value="DEACTIVATE_SURVEY"/>
|
||||
<column name="entity_name" value="SURVEY"/>
|
||||
<column name="action_name" value="DEACTIVATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<!-- No role previously held any permission for this screen (it had no permission check at all),
|
||||
so there are no "roles that already hold the base permission" to copy from. Following the
|
||||
0511_institution_and_branch_profile.xml convention for a brand-new permission model: grant to
|
||||
Super user plus any manager-named role. -->
|
||||
<changeSet id="0584_grant_manage_survey_permissions" author="cbs-anshul">
|
||||
<sql>
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM m_role r
|
||||
JOIN m_permission p ON p.code IN ('CREATE_SURVEY', 'UPDATE_SURVEY', 'ACTIVATE_SURVEY', 'DEACTIVATE_SURVEY')
|
||||
WHERE (LOWER(r.name) LIKE '%manager%' OR LOWER(r.name) = 'super user')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp2
|
||||
WHERE rp2.role_id = r.id AND rp2.permission_id = p.id
|
||||
)
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
|
||||
<!-- LoanCOBCatchUpApiResource.executeLoanCOBCatchUp() (POST /v1/loans/catch-up) previously had no
|
||||
permission check at all. A validateHasPermissionTo("EXECUTE_LOANCOBCATCHUP") check was added
|
||||
directly (not full CommandWrapper/maker-checker wiring, since this is a job-execution trigger,
|
||||
not a CRUD operation on a persisted entity - matching the existing EXECUTE_INLINE_JOB precedent),
|
||||
which requires this permission row to exist. can_maker_checker=false: this closes the
|
||||
authorization gap only, it does not introduce maker-checker for job execution. -->
|
||||
<changeSet id="0585_add_loan_cob_catchup_permission" author="cbs-anshul">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'EXECUTE_LOANCOBCATCHUP'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="EXECUTE_LOANCOBCATCHUP"/>
|
||||
<column name="entity_name" value="LOANCOBCATCHUP"/>
|
||||
<column name="action_name" value="EXECUTE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<!-- No role previously held any permission for this endpoint (it had no permission check at all),
|
||||
so there are no "roles that already hold the base permission" to copy from. Following the
|
||||
0511_institution_and_branch_profile.xml convention for a brand-new permission model: grant to
|
||||
Super user plus any manager-named role. -->
|
||||
<changeSet id="0585_grant_loan_cob_catchup_permission" author="cbs-anshul">
|
||||
<sql>
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM m_role r
|
||||
JOIN m_permission p ON p.code = 'EXECUTE_LOANCOBCATCHUP'
|
||||
WHERE (LOWER(r.name) LIKE '%manager%' OR LOWER(r.name) = 'super user')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp2
|
||||
WHERE rp2.role_id = r.id AND rp2.permission_id = p.id
|
||||
)
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
|
||||
<!-- The Admin -> System maker-checker audit found 8 base permissions whose "_CHECKER" counterpart
|
||||
row was never seeded (AppUser.validateHasCheckerPermissionTo() computes
|
||||
checkerPermissionName = code.toUpperCase() + "_CHECKER" and requires the approver to hold
|
||||
either CHECKER_SUPER_USER or that exact permission code - see the identical issue already
|
||||
fixed for LOAN_ORIGINATOR in 0583_add_loan_originator_checker_permissions.xml). Without these
|
||||
rows, no ordinary role can ever be granted checker rights for these operations, and
|
||||
UPDATE_BATCH_BUSINESS_STEP (Manage Jobs - COB Workflow Step Reorder) is the one permission in
|
||||
all of Admin -> System that already has can_maker_checker=true today, making its missing
|
||||
checker row a live gap, not merely a structural one. All 8 rows are seeded with
|
||||
can_maker_checker=false: this changeset only makes the approval path structurally ready, it
|
||||
does not itself enable maker-checker on any base permission.
|
||||
|
||||
For CREATE/UPDATE/DELETE_ACCOUNTNUMBERFORMAT_CHECKER, entity_name is set to
|
||||
"ACCOUNTNUMBERFORMAT" for all three (matching CREATE_ACCOUNTNUMBERFORMAT and
|
||||
UPDATE_ACCOUNTNUMBERFORMAT). The pre-existing base permission DELETE_ACCOUNTNUMBERFORMAT has
|
||||
entity_name="HOOK" (a pre-existing data bug, confirmed via read-only query, left untouched -
|
||||
fixing an existing permission row's value was out of scope for this change). This checker row
|
||||
deliberately uses the correct entity name rather than propagating that bug. -->
|
||||
<changeSet id="0586_add_system_module_checker_permissions" author="cbs-anshul">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'CREATE_ACCOUNTNUMBERFORMAT_CHECKER'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="configuration"/>
|
||||
<column name="code" value="CREATE_ACCOUNTNUMBERFORMAT_CHECKER"/>
|
||||
<column name="entity_name" value="ACCOUNTNUMBERFORMAT"/>
|
||||
<column name="action_name" value="CREATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="configuration"/>
|
||||
<column name="code" value="UPDATE_ACCOUNTNUMBERFORMAT_CHECKER"/>
|
||||
<column name="entity_name" value="ACCOUNTNUMBERFORMAT"/>
|
||||
<column name="action_name" value="UPDATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="configuration"/>
|
||||
<column name="code" value="DELETE_ACCOUNTNUMBERFORMAT_CHECKER"/>
|
||||
<column name="entity_name" value="ACCOUNTNUMBERFORMAT"/>
|
||||
<column name="action_name" value="DELETE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="infrastructure"/>
|
||||
<column name="code" value="CREATE_ENTITYMAPPING_CHECKER"/>
|
||||
<column name="entity_name" value="ENTITYMAPPING"/>
|
||||
<column name="action_name" value="CREATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="infrastructure"/>
|
||||
<column name="code" value="UPDATE_ENTITYMAPPING_CHECKER"/>
|
||||
<column name="entity_name" value="ENTITYMAPPING"/>
|
||||
<column name="action_name" value="UPDATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="infrastructure"/>
|
||||
<column name="code" value="DELETE_ENTITYMAPPING_CHECKER"/>
|
||||
<column name="entity_name" value="ENTITYMAPPING"/>
|
||||
<column name="action_name" value="DELETE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="CREATE_USERAGREEMENT_CHECKER"/>
|
||||
<column name="entity_name" value="USERAGREEMENT"/>
|
||||
<column name="action_name" value="CREATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="UPDATE_BATCH_BUSINESS_STEP_CHECKER"/>
|
||||
<column name="entity_name" value="BATCH_BUSINESS_STEP"/>
|
||||
<column name="action_name" value="UPDATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<!-- Granted to whichever roles already hold the corresponding base permission - the same
|
||||
"grant to roles that already hold the base permission" convention used in
|
||||
0583_add_loan_originator_checker_permissions.xml. -->
|
||||
<changeSet id="0586_grant_system_module_checker_permissions" author="cbs-anshul">
|
||||
<sql>
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT rp.role_id, checker.id
|
||||
FROM m_role_permission rp
|
||||
JOIN m_permission base ON base.id = rp.permission_id
|
||||
JOIN m_permission checker ON checker.code = base.code || '_CHECKER'
|
||||
WHERE base.code IN ('CREATE_ACCOUNTNUMBERFORMAT', 'UPDATE_ACCOUNTNUMBERFORMAT', 'DELETE_ACCOUNTNUMBERFORMAT',
|
||||
'CREATE_ENTITYMAPPING', 'UPDATE_ENTITYMAPPING', 'DELETE_ENTITYMAPPING',
|
||||
'CREATE_USERAGREEMENT', 'UPDATE_BATCH_BUSINESS_STEP')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp2
|
||||
WHERE rp2.role_id = rp.role_id AND rp2.permission_id = checker.id
|
||||
)
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user