galovics commented on code in PR #4046:
URL: https://github.com/apache/fineract/pull/4046#discussion_r1762849916


##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java:
##########
@@ -295,6 +303,139 @@ public CommandProcessingResult disburseLoan(Long loanId, 
JsonCommand command, Bo
         return disburseLoan(loanId, command, isAccountTransfer, false);
     }
 
+    @Transactional
+    @Override
+    public CommandProcessingResult disburseLoanToLinkedAccount(Long loanId, 
JsonCommand command, Boolean isAccountTransfer) {
+        Boolean isWithoutAutoPayment = false;
+        boolean isPaymentHubIntegrationEnabled = 
configurationDomainService.isPaymentHubIntegrationEnabled();
+        if (!isPaymentHubIntegrationEnabled) {
+            throw new 
GlobalConfigurationNotEnabledException("enable-payment-hub-integration");
+        }
+        loanTransactionValidator.validateDisbursement(command, 
isAccountTransfer, loanId);
+
+        Loan loan = loanAssembler.assembleFrom(loanId);
+
+        if (loan.loanProduct().isDisallowExpectedDisbursements()) {
+            List<LoanDisbursementDetails> filteredList = 
loan.getDisbursementDetails().stream()
+                    .filter(disbursementDetails -> 
disbursementDetails.actualDisbursementDate() == null).toList();
+            // Check whether a new LoanDisbursementDetails is required
+            if (filteredList.isEmpty()) {
+                // create artificial 'tranche/expected disbursal' as current 
disburse code expects it for
+                // multi-disbursal products
+                final LocalDate artificialExpectedDate = 
loan.getExpectedDisbursedOnLocalDate();
+                LoanDisbursementDetails disbursementDetail = new 
LoanDisbursementDetails(artificialExpectedDate, null,
+                        loan.getDisbursedAmount(), null, false);
+                disbursementDetail.updateLoan(loan);
+                loan.getAllDisbursementDetails().add(disbursementDetail);
+            }
+        }
+
+        final LocalDate nextPossibleRepaymentDate = 
loan.getNextPossibleRepaymentDateForRescheduling();
+        final LocalDate rescheduledRepaymentDate = 
command.localDateValueOfParameterNamed("adjustRepaymentDate");
+        final LocalDate actualDisbursementDate = 
command.localDateValueOfParameterNamed("actualDisbursementDate");
+        if (!loan.isMultiDisburmentLoan()) {
+            loan.setActualDisbursementDate(actualDisbursementDate);
+        }
+
+        // validate actual disbursement date against meeting date
+        ScheduleGeneratorDTO scheduleGeneratorDTO = 
this.loanUtilService.buildScheduleGeneratorDTO(loan, null);
+
+        final AppUser currentUser = getAppUserIfPresent();
+        final Map<String, Object> changes = new LinkedHashMap<>();
+
+        final PaymentDetail paymentDetail = 
this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, 
changes);
+        if (paymentDetail != null && paymentDetail.getPaymentType() != null && 
paymentDetail.getPaymentType().getIsCashPayment()) {
+            BigDecimal transactionAmount = 
command.bigDecimalValueOfParameterNamed("transactionAmount");
+            
this.cashierTransactionDataValidator.validateOnLoanDisbursal(currentUser, 
loan.getCurrencyCode(), transactionAmount);
+        }
+        final boolean isPaymentTypeApplicableForDisbursementCharge = 
configurationDomainService
+                .isPaymentTypeApplicableForDisbursementCharge();
+
+        Money amountBeforeAdjust = loan.getPrincipal();
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = 
DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
+
+        if (loan.canDisburse()) {
+            // Get netDisbursalAmount from disbursal screen field.
+            final BigDecimal netDisbursalAmount = command
+                    
.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementNetDisbursalAmountParameterName);
+            if (netDisbursalAmount != null) {
+                loan.setNetDisbursalAmount(netDisbursalAmount);
+            }
+            Money disburseAmount = loan.adjustDisburseAmount(command, 
actualDisbursementDate);
+            boolean recalculateSchedule = 
amountBeforeAdjust.isNotEqualTo(loan.getPrincipal());
+            final ExternalId txnExternalId = 
externalIdFactory.createFromCommand(command, 
LoanApiConstants.externalIdParameterName);
+
+            if (loan.isTopup() && loan.getClientId() != null) {
+                final Long loanIdToClose = 
loan.getTopupLoanDetails().getLoanIdToClose();
+                final Loan loanToClose = 
this.loanRepositoryWrapper.findNonClosedLoanThatBelongsToClient(loanIdToClose, 
loan.getClientId());
+                if (loanToClose == null) {
+                    throw new 
GeneralPlatformDomainRuleException("error.msg.loan.to.be.closed.with.topup.is.not.active",
+                            "Loan to be closed with this topup is not 
active.");
+                }
+                final LocalDate lastUserTransactionOnLoanToClose = 
loanToClose.getLastUserTransactionDate();
+                if (DateUtils.isBefore(loan.getDisbursementDate(), 
lastUserTransactionOnLoanToClose)) {
+                    throw new GeneralPlatformDomainRuleException(
+                            
"error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed",
+                            "Disbursal date of this loan application " + 
loan.getDisbursementDate()
+                                    + " should be after last transaction date 
of loan to be closed " + lastUserTransactionOnLoanToClose);
+                }
+
+                BigDecimal loanOutstanding = this.loanReadPlatformService
+                        
.retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, 
actualDisbursementDate).getAmount();
+                final BigDecimal firstDisbursalAmount = 
loan.getFirstDisbursalAmount();
+                if (loanOutstanding.compareTo(firstDisbursalAmount) > 0) {
+                    throw new 
GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed",
+                            "Topup loan amount should be greater than 
outstanding amount of loan to be closed.");
+                }
+            }
+            if (loan.getRepaymentScheduleInstallments().isEmpty()) {
+                /*
+                 * If no schedule, generate one (applicable to non-tranche 
multi-disbursal loans)
+                 */
+                recalculateSchedule = true;
+            }
+
+            regenerateScheduleOnDisbursement(command, loan, 
recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate,
+                    rescheduledRepaymentDate);
+            boolean downPaymentEnabled = 
loan.repaymentScheduleDetail().isEnableDownPayment();
+            if 
(loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || 
downPaymentEnabled) {
+                createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
+            }
+        }
+
+        final PortfolioAccountData portfolioAccountData = 
this.accountAssociationsReadPlatformService
+                .retriveLoanLinkedAssociation(loan.getId());
+        if (portfolioAccountData == null) {
+            final String errorMessage = "Disburse Loan with id:" + 
loan.getId() + " requires linked savings account for payment";
+            throw new 
LinkedAccountRequiredException("loan.disburse.to.savings", errorMessage, 
loan.getId());
+        }
+
+        InteropIdentifier identifier1 = 
interopService.getIdentifierByAccountId(portfolioAccountData.getId());
+        String payerIdentifierType = 
InteropIdentifierType.ACCOUNT_ID.toString();
+        String payerIdentifierValue = loan.getAccountNumber();
+        String payeeIdentifierType = identifier1.getType().toString();
+        String payeeIdentifierValue = identifier1.getValue();
+        String currency = amountBeforeAdjust.getCurrencyCode();
+        String amount = 
Integer.toString(amountBeforeAdjust.getAmount().intValue());
+        SdkDisbursalService sdkDisbursalService = new 
SdkDisbursalServiceImpl();

Review Comment:
   This is super fishy. You shouldn't construct these objects directly but they 
should be spring beans



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SdkWithdrawalService.java:
##########
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.savings.service;
+
+import org.pheesdk.transfer.Utils.SdkApiException;
+import org.pheesdk.transfer.Utils.SdkValidationException;
+
+public interface SdkWithdrawalService {
+
+    String processWithdrawal(String payerType, String payerId, String 
payeeType, String payeeId, String amount, String currencyCode)

Review Comment:
   Same as for the other.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/SdkDisbursalServiceImpl.java:
##########
@@ -0,0 +1,59 @@
+/**
+ * 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.loanaccount.service;
+
+import org.apache.fineract.portfolio.savings.service.SdkWithdrawalServiceImpl;
+import org.pheesdk.transfer.Services.TransferService;
+import org.pheesdk.transfer.Utils.SdkApiException;
+import org.pheesdk.transfer.Utils.SdkValidationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SdkDisbursalServiceImpl implements SdkDisbursalService {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(SdkWithdrawalServiceImpl.class);
+
+    private TransferService transferService;
+
+    public SdkDisbursalServiceImpl() {
+        TransferService transferService = new TransferService();
+        this.transferService = transferService;
+
+    }
+
+    @Override
+    public String processDisbursal(String payerType, String payerId, String 
payeeType, String payeeId, String amount, String currencyCode)
+            throws SdkValidationException, SdkApiException {
+        transferService.setBaseUrl("http://localhost:1111";);
+        transferService.setPlatformTenantId("gorilla");
+        String id = null;
+        try {
+            id = transferService.processPayment(payerType, payerId, payeeType, 
payeeId, amount, currencyCode);
+            logger.info(id);
+        } catch (SdkApiException e) {
+            logger.error("Error status code: " + e.getStatusCode());

Review Comment:
   Also, use placeholders.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java:
##########
@@ -199,6 +201,7 @@ public final class LoanApplicationValidator {
     private final CalendarInstanceRepository calendarInstanceRepository;
     private final LoanUtilService loanUtilService;
     private final EntityDatatableChecksWritePlatformService 
entityDatatableChecksWritePlatformService;
+    private static final Logger logger = 
LoggerFactory.getLogger(LoanApplicationValidator.class);

Review Comment:
   Please remove this and use Lombok's  SLF4J annotation.



##########
fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java:
##########
@@ -641,6 +656,10 @@ public InteropIdentifier findIdentifier(@NotNull 
InteropIdentifierType idType, @
         return identifierRepository.findOneByTypeAndValueAndSubType(idType, 
idValue, subIdOrType);
     }
 
+    public InteropIdentifier findIdentifier(@NotNull SavingsAccount account) {

Review Comment:
   Why don't we have a Transactional annotation on this?



##########
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/ConfigurationNotEnabledExceptionMapper.java:
##########
@@ -0,0 +1,45 @@
+/**
+ * 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.infrastructure.core.exceptionmapper;
+
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
+import 
org.apache.fineract.infrastructure.core.exception.ConfigurationNotEnabledException;
+import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Provider
+@Component
+@Scope("singleton")

Review Comment:
   Not needed.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java:
##########
@@ -295,6 +303,139 @@ public CommandProcessingResult disburseLoan(Long loanId, 
JsonCommand command, Bo
         return disburseLoan(loanId, command, isAccountTransfer, false);
     }
 
+    @Transactional
+    @Override
+    public CommandProcessingResult disburseLoanToLinkedAccount(Long loanId, 
JsonCommand command, Boolean isAccountTransfer) {
+        Boolean isWithoutAutoPayment = false;

Review Comment:
   Wow this is super complicated. I understand the existing methods are already 
there in  this complicated form but let's not introduce a new complex thing 
here.
   
   Let's follow the clean code principles, have separate methods for the 
individual logical pieces. Have a completely separate class that handles the 
entire process, let's have small methods, etc etc.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/SdkDisbursalService.java:
##########
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import org.pheesdk.transfer.Utils.SdkApiException;
+import org.pheesdk.transfer.Utils.SdkValidationException;
+
+public interface SdkDisbursalService {
+
+    String processDisbursal(String payerType, String payerId, String 
payeeType, String payeeId, String amount, String currencyCode)

Review Comment:
   I really don't like this method signature. Everything is a string. Please 
use a DTO instead so you can't mess up the order of parameters.
   
   Also, the amount is a String? Why?



##########
fineract-core/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java:
##########
@@ -35,6 +36,7 @@
 
 @Entity
 @Getter
+@Setter

Review Comment:
   Why was this resolved?



##########
fineract-provider/build.gradle:
##########
@@ -164,15 +171,15 @@ configurations.driver.each {File file ->
 task createDB {
     description= "Creates the MariaDB Database. Needs database name to be 
passed (like: -PdbName=someDBname)"
     doLast {
-        def sql = Sql.newInstance( 'jdbc:mariadb://localhost:3306/', 
mysqlUser, mysqlPassword, 'org.mariadb.jdbc.Driver' )
+        def sql = Sql.newInstance( 'jdbc:mariadb://localhost:3307/', 
mysqlUser, mysqlPassword, 'org.mariadb.jdbc.Driver' )
         sql.execute( 'CREATE DATABASE '+"`$dbName` CHARACTER SET utf8mb4" )
     }
 }
 
 task dropDB {
     description= "Drops the specified MariaDB database. The database name has 
to be passed (like: -PdbName=someDBname)"
     doLast {
-        def sql = Sql.newInstance( 'jdbc:mariadb://localhost:3306/', 
mysqlUser, mysqlPassword, 'org.mariadb.jdbc.Driver' )
+        def sql = Sql.newInstance( 'jdbc:mariadb://localhost:3307/', 
mysqlUser, mysqlPassword, 'org.mariadb.jdbc.Driver' )

Review Comment:
   This comment shouldn't be resolved since the changes weren't yet made.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/SdkDisbursalService.java:
##########
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import org.pheesdk.transfer.Utils.SdkApiException;
+import org.pheesdk.transfer.Utils.SdkValidationException;

Review Comment:
   We definitely should not expose the internal SDK exception into the code. 



##########
fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java:
##########
@@ -52,6 +53,7 @@
  */
 
 @Getter
+@Setter

Review Comment:
   Why was this resolved?



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/SdkDisbursalServiceImpl.java:
##########
@@ -0,0 +1,59 @@
+/**
+ * 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.loanaccount.service;
+
+import org.apache.fineract.portfolio.savings.service.SdkWithdrawalServiceImpl;
+import org.pheesdk.transfer.Services.TransferService;
+import org.pheesdk.transfer.Utils.SdkApiException;
+import org.pheesdk.transfer.Utils.SdkValidationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SdkDisbursalServiceImpl implements SdkDisbursalService {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(SdkWithdrawalServiceImpl.class);

Review Comment:
   Use Lombok instead.



##########
fineract-provider/build.gradle:
##########
@@ -28,6 +28,13 @@ apply plugin: 'com.google.cloud.tools.jib'
 apply plugin: 'org.springframework.boot'
 apply plugin: 'se.thinkcode.cucumber-runner'
 
+
+repositories {

Review Comment:
   This is absolutely a no-go. If Payment Hub SDK is needed, let's fix it over 
there to be published to a publicly accessed repository, for example Maven 
central.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/SdkDisbursalServiceImpl.java:
##########
@@ -0,0 +1,59 @@
+/**
+ * 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.loanaccount.service;
+
+import org.apache.fineract.portfolio.savings.service.SdkWithdrawalServiceImpl;
+import org.pheesdk.transfer.Services.TransferService;
+import org.pheesdk.transfer.Utils.SdkApiException;
+import org.pheesdk.transfer.Utils.SdkValidationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SdkDisbursalServiceImpl implements SdkDisbursalService {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(SdkWithdrawalServiceImpl.class);
+
+    private TransferService transferService;
+
+    public SdkDisbursalServiceImpl() {
+        TransferService transferService = new TransferService();
+        this.transferService = transferService;
+
+    }

Review Comment:
   Should not be used like this, but rather as a Spring Bean.



##########
fineract-provider/build.gradle:
##########
@@ -225,6 +232,7 @@ bootRun {
     dependencies {
         implementation 'org.mariadb.jdbc:mariadb-java-client'
         implementation 'org.postgresql:postgresql'
+        implementation 'org.pheesdk:PaymentHubSDK:1.0.0'

Review Comment:
   The version shouldn't be here that's for sure. In terms of packaging the 
PaymentHubSDK with Fineract, there are 2 options:
   - bundle with Fineract as it is
   - extract into a separate module where the payment hub dependency is there - 
in this case there needs to be build adjustments so that Fineract is published 
in 2 versions, with and without payment hub
   
   I'm not sure how much extra weight the Payment Hub SDK is, but if it's just 
DTOs and API classes, I'd say it's acceptable to be bundled with Fineract. 



##########
fineract-provider/build.gradle:
##########
@@ -164,15 +171,15 @@ configurations.driver.each {File file ->
 task createDB {
     description= "Creates the MariaDB Database. Needs database name to be 
passed (like: -PdbName=someDBname)"
     doLast {
-        def sql = Sql.newInstance( 'jdbc:mariadb://localhost:3306/', 
mysqlUser, mysqlPassword, 'org.mariadb.jdbc.Driver' )
+        def sql = Sql.newInstance( 'jdbc:mariadb://localhost:3307/', 
mysqlUser, mysqlPassword, 'org.mariadb.jdbc.Driver' )

Review Comment:
   This comment shouldn't be resolved since the changes weren't yet made.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java:
##########
@@ -295,6 +303,139 @@ public CommandProcessingResult disburseLoan(Long loanId, 
JsonCommand command, Bo
         return disburseLoan(loanId, command, isAccountTransfer, false);
     }
 
+    @Transactional
+    @Override
+    public CommandProcessingResult disburseLoanToLinkedAccount(Long loanId, 
JsonCommand command, Boolean isAccountTransfer) {
+        Boolean isWithoutAutoPayment = false;
+        boolean isPaymentHubIntegrationEnabled = 
configurationDomainService.isPaymentHubIntegrationEnabled();
+        if (!isPaymentHubIntegrationEnabled) {
+            throw new 
GlobalConfigurationNotEnabledException("enable-payment-hub-integration");
+        }
+        loanTransactionValidator.validateDisbursement(command, 
isAccountTransfer, loanId);
+
+        Loan loan = loanAssembler.assembleFrom(loanId);
+
+        if (loan.loanProduct().isDisallowExpectedDisbursements()) {
+            List<LoanDisbursementDetails> filteredList = 
loan.getDisbursementDetails().stream()
+                    .filter(disbursementDetails -> 
disbursementDetails.actualDisbursementDate() == null).toList();
+            // Check whether a new LoanDisbursementDetails is required
+            if (filteredList.isEmpty()) {
+                // create artificial 'tranche/expected disbursal' as current 
disburse code expects it for
+                // multi-disbursal products
+                final LocalDate artificialExpectedDate = 
loan.getExpectedDisbursedOnLocalDate();
+                LoanDisbursementDetails disbursementDetail = new 
LoanDisbursementDetails(artificialExpectedDate, null,
+                        loan.getDisbursedAmount(), null, false);
+                disbursementDetail.updateLoan(loan);
+                loan.getAllDisbursementDetails().add(disbursementDetail);
+            }
+        }
+
+        final LocalDate nextPossibleRepaymentDate = 
loan.getNextPossibleRepaymentDateForRescheduling();
+        final LocalDate rescheduledRepaymentDate = 
command.localDateValueOfParameterNamed("adjustRepaymentDate");
+        final LocalDate actualDisbursementDate = 
command.localDateValueOfParameterNamed("actualDisbursementDate");
+        if (!loan.isMultiDisburmentLoan()) {
+            loan.setActualDisbursementDate(actualDisbursementDate);
+        }
+
+        // validate actual disbursement date against meeting date
+        ScheduleGeneratorDTO scheduleGeneratorDTO = 
this.loanUtilService.buildScheduleGeneratorDTO(loan, null);
+
+        final AppUser currentUser = getAppUserIfPresent();
+        final Map<String, Object> changes = new LinkedHashMap<>();
+
+        final PaymentDetail paymentDetail = 
this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, 
changes);
+        if (paymentDetail != null && paymentDetail.getPaymentType() != null && 
paymentDetail.getPaymentType().getIsCashPayment()) {
+            BigDecimal transactionAmount = 
command.bigDecimalValueOfParameterNamed("transactionAmount");
+            
this.cashierTransactionDataValidator.validateOnLoanDisbursal(currentUser, 
loan.getCurrencyCode(), transactionAmount);
+        }
+        final boolean isPaymentTypeApplicableForDisbursementCharge = 
configurationDomainService
+                .isPaymentTypeApplicableForDisbursementCharge();
+
+        Money amountBeforeAdjust = loan.getPrincipal();
+        final Locale locale = command.extractLocale();
+        final DateTimeFormatter fmt = 
DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
+
+        if (loan.canDisburse()) {
+            // Get netDisbursalAmount from disbursal screen field.
+            final BigDecimal netDisbursalAmount = command
+                    
.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementNetDisbursalAmountParameterName);
+            if (netDisbursalAmount != null) {
+                loan.setNetDisbursalAmount(netDisbursalAmount);
+            }
+            Money disburseAmount = loan.adjustDisburseAmount(command, 
actualDisbursementDate);
+            boolean recalculateSchedule = 
amountBeforeAdjust.isNotEqualTo(loan.getPrincipal());
+            final ExternalId txnExternalId = 
externalIdFactory.createFromCommand(command, 
LoanApiConstants.externalIdParameterName);
+
+            if (loan.isTopup() && loan.getClientId() != null) {
+                final Long loanIdToClose = 
loan.getTopupLoanDetails().getLoanIdToClose();
+                final Loan loanToClose = 
this.loanRepositoryWrapper.findNonClosedLoanThatBelongsToClient(loanIdToClose, 
loan.getClientId());
+                if (loanToClose == null) {
+                    throw new 
GeneralPlatformDomainRuleException("error.msg.loan.to.be.closed.with.topup.is.not.active",
+                            "Loan to be closed with this topup is not 
active.");
+                }
+                final LocalDate lastUserTransactionOnLoanToClose = 
loanToClose.getLastUserTransactionDate();
+                if (DateUtils.isBefore(loan.getDisbursementDate(), 
lastUserTransactionOnLoanToClose)) {
+                    throw new GeneralPlatformDomainRuleException(
+                            
"error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed",
+                            "Disbursal date of this loan application " + 
loan.getDisbursementDate()
+                                    + " should be after last transaction date 
of loan to be closed " + lastUserTransactionOnLoanToClose);
+                }
+
+                BigDecimal loanOutstanding = this.loanReadPlatformService
+                        
.retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, 
actualDisbursementDate).getAmount();
+                final BigDecimal firstDisbursalAmount = 
loan.getFirstDisbursalAmount();
+                if (loanOutstanding.compareTo(firstDisbursalAmount) > 0) {
+                    throw new 
GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed",
+                            "Topup loan amount should be greater than 
outstanding amount of loan to be closed.");
+                }
+            }
+            if (loan.getRepaymentScheduleInstallments().isEmpty()) {
+                /*
+                 * If no schedule, generate one (applicable to non-tranche 
multi-disbursal loans)
+                 */
+                recalculateSchedule = true;
+            }
+
+            regenerateScheduleOnDisbursement(command, loan, 
recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate,
+                    rescheduledRepaymentDate);
+            boolean downPaymentEnabled = 
loan.repaymentScheduleDetail().isEnableDownPayment();
+            if 
(loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || 
downPaymentEnabled) {
+                createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
+            }
+        }
+
+        final PortfolioAccountData portfolioAccountData = 
this.accountAssociationsReadPlatformService
+                .retriveLoanLinkedAssociation(loan.getId());
+        if (portfolioAccountData == null) {
+            final String errorMessage = "Disburse Loan with id:" + 
loan.getId() + " requires linked savings account for payment";
+            throw new 
LinkedAccountRequiredException("loan.disburse.to.savings", errorMessage, 
loan.getId());
+        }
+
+        InteropIdentifier identifier1 = 
interopService.getIdentifierByAccountId(portfolioAccountData.getId());
+        String payerIdentifierType = 
InteropIdentifierType.ACCOUNT_ID.toString();
+        String payerIdentifierValue = loan.getAccountNumber();
+        String payeeIdentifierType = identifier1.getType().toString();
+        String payeeIdentifierValue = identifier1.getValue();
+        String currency = amountBeforeAdjust.getCurrencyCode();
+        String amount = 
Integer.toString(amountBeforeAdjust.getAmount().intValue());
+        SdkDisbursalService sdkDisbursalService = new 
SdkDisbursalServiceImpl();
+        try {
+            String id = 
sdkDisbursalService.processDisbursal(payerIdentifierType, payerIdentifierValue, 
payeeIdentifierType,
+                    payeeIdentifierValue, amount, currency);
+            logger.info("Payment hub transaction started with transaction id: 
" + id);

Review Comment:
   As an extra, please use placeholders instead of concatenating the parameters.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java:
##########
@@ -2123,6 +2126,7 @@ public static void validateOrThrow(String resource, 
Consumer<DataValidatorBuilde
         baseDataValidator.accept(dataValidatorBuilder);
 
         if (!dataValidationErrors.isEmpty()) {
+            logger.info(dataValidationErrors.toString());

Review Comment:
   Why was this comment resolved?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to