diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index fd1c61cf8..9f3d7586d 100644
--- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -3878,8 +3878,8 @@ public class CommandWrapperBuilder {
}
public CommandWrapperBuilder updateLoanApprovedAmount(final Long loanId) {
- this.actionName = "UPDATE";
- this.entityName = "LOAN_APPROVED_AMOUNT";
+ this.actionName = "UPDATE_APPROVED_AMOUNT";
+ this.entityName = "LOAN";
this.entityId = loanId;
this.loanId = loanId;
this.href = "/loans/" + loanId;
@@ -3887,8 +3887,8 @@ public class CommandWrapperBuilder {
}
public CommandWrapperBuilder manualInterestRefund(final Long loanId, final Long transactionId) {
- this.actionName = "MANUAL_INTEREST_REFUND";
- this.entityName = "LOAN_TRANSACTION";
+ this.actionName = "MANUAL_INTEREST_REFUND_TRANSACTION";
+ this.entityName = "LOAN";
this.loanId = loanId;
this.entityId = transactionId;
this.href = "/loans/" + loanId + "/transactions/" + transactionId + "?command=interest-refund";
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApprovedAmountModificationCommandHandler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApprovedAmountModificationCommandHandler.java
index 1be514b86..a88a6b496 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApprovedAmountModificationCommandHandler.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/LoanApprovedAmountModificationCommandHandler.java
@@ -29,7 +29,7 @@ import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
-@CommandType(entity = "LOAN_APPROVED_AMOUNT", action = "UPDATE")
+@CommandType(entity = "LOAN", action = "UPDATE_APPROVED_AMOUNT")
public class LoanApprovedAmountModificationCommandHandler implements NewCommandSourceHandler {
private final LoanApprovedAmountWritePlatformService loanApprovedAmountWritePlatformService;
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ManualInterestRefundCommandHandler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ManualInterestRefundCommandHandler.java
index 09c9abc09..b5257eecd 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ManualInterestRefundCommandHandler.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/ManualInterestRefundCommandHandler.java
@@ -29,7 +29,7 @@ import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
-@CommandType(entity = "LOAN_TRANSACTION", action = "MANUAL_INTEREST_REFUND")
+@CommandType(entity = "LOAN", action = "MANUAL_INTEREST_REFUND_TRANSACTION")
public class ManualInterestRefundCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService loanWritePlatformService;
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 1e12348ff..94ffeb11b 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -212,4 +212,5 @@
+
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0194_fix_missing_permission.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0194_fix_missing_permission.xml
new file mode 100644
index 000000000..7319ff99f
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0194_fix_missing_permission.xml
@@ -0,0 +1,568 @@
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'CLOSE_CLIENT_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'CLOSE_GROUP_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'CLOSE_CENTER_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'REMOVESAVINGSOFFICER_SAVINGSACCOUNT_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'UPDATESAVINGSOFFICER_SAVINGSACCOUNT_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'CREATE_TELLER_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'UPDATE_TELLER_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'ALLOCATECASHIER_TELLER_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'UPDATECASHIERALLOCATION_TELLER_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'DELETECASHIERALLOCATION_TELLER_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'ALLOCATECASHTOCASHIER_TELLER_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'SETTLECASHFROMCASHIER_TELLER_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'DEFINEOPENINGBALANCE_JOURNALENTRY_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'DELETE_TELLER_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'READ_RATE_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'CREATE_RATE_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'UPDATE_RATE_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'UPDATEOPENINGBALANCE_JOURNALENTRY'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'UPDATEOPENINGBALANCE_JOURNALENTRY_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'CREATE_STANDINGINSTRUCTION'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'UPDATE_STANDINGINSTRUCTION'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'DELETE_STANDINGINSTRUCTION'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'UPDATE_COLLECTIONSHEET'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'POSTINTERESTASONDATE_SAVINGSACCOUNT'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'POSTINTERESTASONDATE_SAVINGSACCOUNT_CHECKER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'ALLOCATECASHIER_TELLER'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'CAPITALIZEDINCOME_LOAN'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'CAPITALIZEDINCOMEADJUSTMENT_LOAN'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'BUYDOWNFEEADJUSTMENT_LOAN'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'BUYDOWNFEE_LOAN'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'CONTRACT_TERMINATION_UNDO_LOAN'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'UPDATE_APPROVED_AMOUNT_LOAN'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(1) FROM m_permission WHERE code = 'MANUAL_INTEREST_REFUND_TRANSACTION_LOAN'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(*) FROM m_permission
+ WHERE code = 'CREATE_ADDRESS_CHECKER' AND can_maker_checker = 'true'
+
+
+
+ UPDATE m_permission
+ SET can_maker_checker = 'false'
+ WHERE code = 'CREATE_ADDRESS_CHECKER';
+
+
+
+
+
+ SELECT COUNT(*) FROM m_permission
+ WHERE code = 'UPDATE_ADDRESS_CHECKER' AND can_maker_checker = 'true'
+
+
+
+ UPDATE m_permission
+ SET can_maker_checker = 'false'
+ WHERE code = 'UPDATE_ADDRESS_CHECKER';
+
+
+
+
+
+ SELECT COUNT(*) FROM m_permission
+ WHERE code = 'DELETE_ADDRESS_CHECKER' AND can_maker_checker = 'true'
+
+
+
+ UPDATE m_permission
+ SET can_maker_checker = 'false'
+ WHERE code = 'DELETE_ADDRESS_CHECKER';
+
+
+
+
+
+ SELECT COUNT(*) FROM m_permission
+ WHERE code = 'UNDOREJECT_CLIENT_CHECKER' AND can_maker_checker = 'true'
+
+
+
+ UPDATE m_permission
+ SET can_maker_checker = 'false'
+ WHERE code = 'UNDOREJECT_CLIENT_CHECKER';
+
+
+
+
+
+ SELECT COUNT(*) FROM m_permission
+ WHERE code = 'UNDOWITHDRAWAL_CLIENT_CHECKER' AND can_maker_checker = 'true'
+
+
+
+ UPDATE m_permission
+ SET can_maker_checker = 'false'
+ WHERE code = 'UNDOWITHDRAWAL_CLIENT_CHECKER';
+
+
+
+
+
+ SELECT COUNT(*) FROM m_permission
+ WHERE code = 'UPDATEDEPOSITAMOUNT_RECURRINGDEPOSITACCOUNT_CHECKER' AND can_maker_checker = 'true'
+
+
+
+ UPDATE m_permission
+ SET can_maker_checker = 'false'
+ WHERE code = 'UPDATEDEPOSITAMOUNT_RECURRINGDEPOSITACCOUNT_CHECKER';
+
+
+
+
+
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
index b4d01afd3..47c64147b 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
@@ -82,15 +82,19 @@ import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.client.models.PostRolesRequest;
+import org.apache.fineract.client.models.PostUsersRequest;
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
import org.apache.fineract.client.models.PutLoansApprovedAmountRequest;
import org.apache.fineract.client.models.PutLoansApprovedAmountResponse;
import org.apache.fineract.client.models.PutLoansAvailableDisbursementAmountRequest;
import org.apache.fineract.client.models.PutLoansAvailableDisbursementAmountResponse;
import org.apache.fineract.client.models.PutLoansLoanIdResponse;
+import org.apache.fineract.client.models.PutRolesRoleIdPermissionsRequest;
import org.apache.fineract.client.models.RetrieveLoansPointInTimeRequest;
import org.apache.fineract.client.util.CallFailedRuntimeException;
import org.apache.fineract.client.util.Calls;
+import org.apache.fineract.client.util.FineractClient;
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
import org.apache.fineract.infrastructure.event.external.data.ExternalEventResponse;
import org.apache.fineract.integrationtests.client.IntegrationTest;
@@ -126,12 +130,15 @@ import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.extension.ExtendWith;
+import retrofit2.Call;
+import retrofit2.Response;
@Slf4j
@ExtendWith({ LoanTestLifecycleExtension.class, ExternalEventsExtension.class })
public abstract class BaseLoanIntegrationTest extends IntegrationTest {
protected static final String DATETIME_PATTERN = "dd MMMM yyyy";
+ protected static final String LOCALE = "en";
static {
Utils.initializeRESTAssured();
@@ -305,6 +312,86 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
return repaymentId;
}
+ /**
+ * Executes a Loan transaction request by a user created with no permissions then verifies it fails with
+ * authentication error. Then alters user permissions to get the given permission then executes the query again. It
+ * verifies that it returns with no error.
+ *
+ * @param loanId
+ * loan id
+ * @param postLoansLoanIdTransactionsRequest
+ * transaction request
+ * @param command
+ * the command for loan transaction
+ * @param permission
+ * the given permission related to the loan transaction
+ * @return Result body
+ */
+ public PostLoansLoanIdTransactionsResponse makeLoanTransactionWithPermissionVerification(final Long loanId,
+ PostLoansLoanIdTransactionsRequest postLoansLoanIdTransactionsRequest, final String command, final String permission) {
+ return performPermissionTestForRequest(permission, fineractClient -> fineractClient.loanTransactions.executeLoanTransaction(loanId,
+ postLoansLoanIdTransactionsRequest, command));
+ }
+
+ /**
+ * Executes a Loan transaction adjustment request by a user created with no permissions then verifies it fails with
+ * authentication error. Then alters user permissions to get the given permission then executes the query again. It
+ * verifies that it returns with no error.
+ *
+ * @param loanId
+ * loan ID
+ * @param transactionIdToAdjust
+ * transaction ID to adjust
+ * @param postLoansLoanIdTransactionsRequest
+ * transaction request
+ * @param command
+ * the command for loan transaction
+ * @param permission
+ * the given permission related to the loan transaction
+ * @return Result body
+ */
+ public PostLoansLoanIdTransactionsResponse adjustLoanTransactionWithPermissionVerification(final Long loanId,
+ final Long transactionIdToAdjust, PostLoansLoanIdTransactionsTransactionIdRequest postLoansLoanIdTransactionsRequest,
+ final String command, final String permission) {
+ return performPermissionTestForRequest(permission, fineractClient -> fineractClient.loanTransactions.adjustLoanTransaction(loanId,
+ transactionIdToAdjust, postLoansLoanIdTransactionsRequest, command));
+ }
+
+ public T performPermissionTestForRequest(final String permission, Function> callback) {
+ // create role
+ String roleName = Utils.uniqueRandomStringGenerator("TEST_ROLE_", 10);
+ Long roleId = Calls
+ .ok(fineractClient().roles.createRole(new PostRolesRequest().name(roleName).description("Test role Description")))
+ .getResourceId();
+
+ Calls.ok(fineractClient().roles.updateRolePermissions(roleId,
+ new PutRolesRoleIdPermissionsRequest().putPermissionsItem(permission, false)));
+ // create user with role
+ String firstname = "Test";
+ String lastname = Utils.uniqueRandomStringGenerator("User", 6);
+ String userName = Utils.uniqueRandomStringGenerator("testUserName", 4);
+ String password = "AKleRbDhK421$";
+ String email = firstname + "." + lastname + "@whatever.mifos.org";
+ Calls.ok(fineractClient().users
+ .create15(new PostUsersRequest().addRolesItem(roleId).email(email).firstname(firstname).lastname(lastname)
+ .repeatPassword(password).sendPasswordToEmail(false).officeId(1L).username(userName).password(password)));
+
+ // login user
+ FineractClient fineractClientOfUser = newFineractClient(userName, password);
+
+ // try to make transaction - should fail
+ Response responseFail = Calls.executeU(callback.apply(fineractClientOfUser));
+ Assertions.assertEquals(403, responseFail.code());
+
+ // edit role to have permission for transaction
+ Calls.ok(fineractClient().roles.updateRolePermissions(roleId,
+ new PutRolesRoleIdPermissionsRequest().putPermissionsItem(permission, true)));
+ // try to make transaction - should pass
+ Response responseOk = Calls.executeU(callback.apply(fineractClientOfUser));
+ Assertions.assertEquals(200, responseOk.code());
+ return responseOk.body();
+ }
+
private String getNonByPassUserAuthKey(RequestSpecification requestSpec, ResponseSpecification responseSpec) {
// creates the user
UserHelper.getSimpleUserWithoutBypassPermission(requestSpec, responseSpec);
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/UserLoanPermissionTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/UserLoanPermissionTest.java
new file mode 100644
index 000000000..fb133fc36
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/UserLoanPermissionTest.java
@@ -0,0 +1,154 @@
+/**
+ * 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.integrationtests;
+
+import java.math.BigDecimal;
+import org.apache.fineract.client.models.PostLoanProductsRequest;
+import org.apache.fineract.client.models.PostLoansLoanIdRequest;
+import org.apache.fineract.client.models.PostLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.client.models.PutLoansApprovedAmountRequest;
+import org.apache.fineract.client.util.Calls;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import retrofit2.Response;
+
+public class UserLoanPermissionTest extends BaseLoanIntegrationTest {
+
+ Long clientId;
+ Long loanProductId;
+ private Long loanId;
+
+ @BeforeEach
+ public void setup() {
+ if (clientId == null) {
+ clientId = ClientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+ }
+ if (loanProductId == null) {
+ loanProductId = loanProductHelper.createLoanProduct(create4IProgressiveWithCapitalizedIncome()
+ .addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND)
+ .overAppliedCalculationType(null).overAppliedNumber(null).allowApprovedDisbursedAmountsOverApplied(false)
+ .enableBuyDownFee(true).buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT)
+ .buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION)
+ .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE)
+ .receivableInterestAccountId(interestReceivableAccount.getAccountID().longValue())
+ .receivableFeeAccountId(feeReceivableAccount.getAccountID().longValue())
+ .receivablePenaltyAccountId(penaltyReceivableAccount.getAccountID().longValue())
+ .buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue())
+ .incomeFromBuyDownAccountId(feeIncomeAccount.getAccountID().longValue())
+ .deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue())).getResourceId();
+ }
+ runAt("1 January 2025", () -> {
+
+ PostLoansResponse postLoansResponse = loanTransactionHelper
+ .applyLoan(applyLP2ProgressiveLoanRequest(clientId, loanProductId, "1 January 2025", 10000.0, 12.0, 4, null));
+
+ loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(2000.0, "1 January 2025"));
+
+ loanId = postLoansResponse.getResourceId();
+ disburseLoan(loanId, BigDecimal.valueOf(1000.0), "1 January 2025");
+ });
+ }
+
+ @Test
+ public void testCapitalizedIncomeAndCapitalizedIncomeAdjustmentPermissions() {
+ runAt("1 January 2025", () -> {
+ Long capitalizedIncomeId = makeLoanTransactionWithPermissionVerification(loanId, new PostLoansLoanIdTransactionsRequest()
+ .dateFormat(DATETIME_PATTERN).locale("en").transactionAmount(50.0).transactionDate("01 January 2025"),
+ "capitalizedIncome", "CAPITALIZEDINCOME_LOAN").getResourceId();
+
+ adjustLoanTransactionWithPermissionVerification(
+ loanId, capitalizedIncomeId, new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN)
+ .locale("en").transactionAmount(50.0).transactionDate("1 January 2025"),
+ "capitalizedIncomeAdjustment", "CAPITALIZEDINCOMEADJUSTMENT_LOAN");
+
+ });
+
+ }
+
+ @Test
+ public void testBuyDownFeeAndBuyDownFeeAdjustmentPermissions() {
+ runAt("1 January 2025", () -> {
+ final Long buyDownFeeTransactionId = makeLoanTransactionWithPermissionVerification(loanId,
+ new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("01 January 2025").locale("en")
+ .transactionAmount(100.0d),
+ "buyDownFee", "BUYDOWNFEE_LOAN").getResourceId();
+
+ adjustLoanTransactionWithPermissionVerification(
+ loanId, buyDownFeeTransactionId, new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN)
+ .transactionDate("01 January 2025").locale("en").transactionAmount(100.0d),
+ "buyDownFeeAdjustment", "BUYDOWNFEEADJUSTMENT_LOAN");
+ });
+ }
+
+ @Test
+ public void testManualInterestRefundPermission() {
+ runAt("1 February 2025", () -> {
+ final Long merchantIssuedRefundId = loanTransactionHelper
+ .makeMerchantIssuedRefund(loanId,
+ new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).locale("en")
+ .transactionDate("01 February 2025").transactionAmount(100.0D).interestRefundCalculation(false))
+ .getResourceId();
+
+ performPermissionTestForRequest("MANUAL_INTEREST_REFUND_TRANSACTION_LOAN",
+ fineractClient -> fineractClient.loanTransactions.adjustLoanTransaction(loanId, merchantIssuedRefundId,
+ new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN).locale("en")
+ .transactionAmount(1.20D),
+ "interest-refund"));
+ });
+ }
+
+ @Test
+ public void testUpdateApprovedAmountPermission() {
+ runAt("1 January 2025", () -> {
+ // disbursement should be rejected upon validation error
+ Response response = Calls.executeU(
+ fineractClient().loans.stateTransitions(loanId, new PostLoansLoanIdRequest().actualDisbursementDate("1 January 2025")
+ .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(2000.0)).locale("en"), "disburse"));
+
+ Assertions.assertEquals(403, response.code());
+
+ // update approved amount
+ performPermissionTestForRequest("UPDATE_APPROVED_AMOUNT_LOAN",
+ fineractClient -> fineractClient.loans.modifyLoanApprovedAmount(loanId,
+ new PutLoansApprovedAmountRequest().amount(BigDecimal.valueOf(4000.0d)).locale("en")));
+
+ // disbursement should be performed without error
+ Calls.ok(fineractClient().loans.stateTransitions(loanId, new PostLoansLoanIdRequest().actualDisbursementDate("1 January 2025")
+ .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(2000.0)).locale("en"), "disburse"));
+ });
+ }
+
+ @Test
+ public void testContractTerminationAndUndoContractTerminationPermission() {
+
+ runAt("2 January 2025", () -> {
+ performPermissionTestForRequest("CONTRACT_TERMINATION_LOAN", fineractClient -> fineractClient.loans.stateTransitions(loanId,
+ new PostLoansLoanIdRequest().note(""), "contractTermination"));
+
+ performPermissionTestForRequest("CONTRACT_TERMINATION_UNDO_LOAN",
+ fineractClient -> fineractClient.loans.stateTransitions(loanId,
+ new PostLoansLoanIdRequest().note("Contract Termination Undo Test Note"), "undoContractTermination"));
+ });
+ }
+}