This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 388612b1a FINERACT-1724 Added batch support for the Get Charge by Id 
call. Added externalId field to PostLoansLoanIdChargesRequest. Added 
submittedOnDate column to m_loan_charge table. Updated logic to return new 
submittedOnDate field in GetLoansLoanIdChargesChargeIdResponse.
388612b1a is described below

commit 388612b1ae68bc3ea69678cf352029372fe10faf
Author: Arnold Galovics <[email protected]>
AuthorDate: Fri Oct 14 16:57:26 2022 +0200

    FINERACT-1724
    Added batch support for the Get Charge by Id call.
    Added externalId field to PostLoansLoanIdChargesRequest.
    Added submittedOnDate column to m_loan_charge table.
    Updated logic to return new submittedOnDate field in 
GetLoansLoanIdChargesChargeIdResponse.
---
 build.gradle                                       |   1 +
 .../batch/command/CommandStrategyProvider.java     |   2 +
 .../internal/GetChargeByIdCommandStrategy.java     |  87 ++++++++++++
 .../api/LoanChargesApiResourceSwagger.java         |   5 +
 .../portfolio/loanaccount/data/LoanChargeData.java |  18 ++-
 .../portfolio/loanaccount/domain/LoanCharge.java   |   9 ++
 .../service/LoanChargeReadPlatformServiceImpl.java |   9 +-
 .../serialization/LoanProductDataValidator.java    |   3 +-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 .../0055_add_submitted_on_date_to_loan_charge.xml  |  30 +++++
 .../batch/command/CommandStrategyProviderTest.java |   6 +-
 .../internal/GetChargeByIdCommandStrategyTest.java | 149 +++++++++++++++++++++
 .../fineract/integrationtests/BatchApiTest.java    |  65 +++++++++
 .../integrationtests/common/BatchHelper.java       |  38 +++++-
 14 files changed, 410 insertions(+), 13 deletions(-)

diff --git a/build.gradle b/build.gradle
index 87272d239..af38e01e2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -156,6 +156,7 @@ allprojects  {
             "**/build.gradle.mustache",
             "**/pom.mustache",
             "**/avro/**/*.java",
+            "**/org/apache/fineract/client/**/*.java"
         ])
         strictCheck true
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java
 
b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java
index 581681ac3..6005a4a9e 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java
@@ -91,6 +91,8 @@ public class CommandStrategyProvider {
         
this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/charges").method("POST").build(),
 "createChargeCommandStrategy");
         
this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/charges").method("GET").build(),
                 "collectChargesCommandStrategy");
+        
this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/charges\\/\\d+").method("GET").build(),
+                "getChargeByIdCommandStrategy");
         
this.commandStrategies.put(CommandContext.resource("loans\\/\\d+\\/transactions\\?command=repayment").method("POST").build(),
                 "createTransactionLoanCommandStrategy");
         this.commandStrategies.put(
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategy.java
 
b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategy.java
new file mode 100644
index 000000000..2df109b1d
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategy.java
@@ -0,0 +1,87 @@
+/**
+ * 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.batch.command.internal;
+
+import com.google.common.base.Splitter;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.core.UriInfo;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.batch.command.CommandStrategy;
+import org.apache.fineract.batch.command.CommandStrategyUtils;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.infrastructure.core.api.MutableUriInfo;
+import org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource;
+import org.apache.http.HttpStatus;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implements {@link CommandStrategy} to retrieve a charge by id. It passes 
the contents of the body from the
+ * BatchRequest to {@link LoanChargesApiResource} and gets back the response. 
This class will also catch any errors
+ * raised by {@link LoanChargesApiResource} and map those errors to 
appropriate status codes in BatchResponse.
+ *
+ * @see CommandStrategy
+ * @see BatchRequest
+ * @see BatchResponse
+ */
+@Component
+@RequiredArgsConstructor
+public class GetChargeByIdCommandStrategy implements CommandStrategy {
+
+    private final LoanChargesApiResource loanChargesApiResource;
+
+    @Override
+    public BatchResponse execute(final BatchRequest request, UriInfo uriInfo) {
+        final MutableUriInfo parameterizedUriInfo = new 
MutableUriInfo(uriInfo);
+
+        final BatchResponse response = new BatchResponse();
+        final String responseBody;
+
+        response.setRequestId(request.getRequestId());
+        response.setHeaders(request.getHeaders());
+
+        final String relativeUrl = request.getRelativeUrl();
+
+        // Get the loan and charge ids for use in loanChargesApiResource
+        final List<String> pathParameters = 
Splitter.on('/').splitToList(relativeUrl);
+        final Long loanId = Long.parseLong(pathParameters.get(1));
+        Long chargeId;
+        if (relativeUrl.indexOf('?') > 0) {
+            chargeId = 
Long.parseLong(StringUtils.substringBeforeLast(pathParameters.get(3), "?"));
+        } else {
+            chargeId = Long.parseLong(pathParameters.get(3));
+        }
+
+        Map<String, String> queryParameters;
+        if (relativeUrl.indexOf('?') > 0) {
+            queryParameters = 
CommandStrategyUtils.getQueryParameters(relativeUrl);
+
+            // Add the query parameters sent in the relative URL to UriInfo
+            
CommandStrategyUtils.addQueryParametersToUriInfo(parameterizedUriInfo, 
queryParameters);
+        }
+
+        responseBody = loanChargesApiResource.retrieveLoanCharge(loanId, 
chargeId, parameterizedUriInfo);
+        response.setStatusCode(HttpStatus.SC_OK);
+        response.setBody(responseBody);
+
+        return response;
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResourceSwagger.java
index 32853c09b..5f97c02a5 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResourceSwagger.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResourceSwagger.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.portfolio.loanaccount.api;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import java.time.LocalDate;
 import java.util.Set;
 
 /**
@@ -102,6 +103,8 @@ final class LoanChargesApiResourceSwagger {
         public Double amountOrPercentage;
         @Schema(example = "false")
         public Boolean penalty;
+        @Schema(example = "27 March 2013")
+        public LocalDate submittedOnDate;
     }
 
     @Schema(description = "GetLoansLoanIdChargesTemplateResponse")
@@ -179,6 +182,8 @@ final class LoanChargesApiResourceSwagger {
         public String dateFormat;
         @Schema(example = "29 April 2013")
         public String dueDate;
+        @Schema(example = "786444UUUYYH7")
+        public String externalId;
     }
 
     @Schema(description = " PostLoansLoanIdChargesResponse")
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargeData.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargeData.java
index 68cd919b6..b22825ad1 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargeData.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargeData.java
@@ -39,6 +39,8 @@ public class LoanChargeData {
     private final String name;
     private final EnumOptionData chargeTimeType;
 
+    private final LocalDate submittedOnDate;
+
     private final LocalDate dueDate;
 
     private final EnumOptionData chargeCalculationType;
@@ -103,10 +105,10 @@ public class LoanChargeData {
 
     public LoanChargeData(final Long id, final Long chargeId, final String 
name, final CurrencyData currency, final BigDecimal amount,
             final BigDecimal amountPaid, final BigDecimal amountWaived, final 
BigDecimal amountWrittenOff,
-            final BigDecimal amountOutstanding, final EnumOptionData 
chargeTimeType, final LocalDate dueDate,
-            final EnumOptionData chargeCalculationType, final BigDecimal 
percentage, final BigDecimal amountPercentageAppliedTo,
-            final boolean penalty, final EnumOptionData chargePaymentMode, 
final boolean paid, final boolean waived, final Long loanId,
-            final BigDecimal minCap, final BigDecimal maxCap, final BigDecimal 
amountOrPercentage,
+            final BigDecimal amountOutstanding, final EnumOptionData 
chargeTimeType, final LocalDate submittedOnDate,
+            final LocalDate dueDate, final EnumOptionData 
chargeCalculationType, final BigDecimal percentage,
+            final BigDecimal amountPercentageAppliedTo, final boolean penalty, 
final EnumOptionData chargePaymentMode, final boolean paid,
+            final boolean waived, final Long loanId, final BigDecimal minCap, 
final BigDecimal maxCap, final BigDecimal amountOrPercentage,
             Collection<LoanInstallmentChargeData> installmentChargeData, final 
String externalId) {
         this.id = id;
         this.chargeId = chargeId;
@@ -118,6 +120,7 @@ public class LoanChargeData {
         this.amountWrittenOff = amountWrittenOff;
         this.amountOutstanding = amountOutstanding;
         this.chargeTimeType = chargeTimeType;
+        this.submittedOnDate = submittedOnDate;
         this.dueDate = dueDate;
         this.chargeCalculationType = chargeCalculationType;
         this.percentage = percentage;
@@ -162,6 +165,7 @@ public class LoanChargeData {
         this.amountWrittenOff = BigDecimal.ZERO;
         this.amountOutstanding = amount;
         this.chargeTimeType = chargeTimeType;
+        this.submittedOnDate = null;
         this.dueDate = null;
         this.chargeCalculationType = chargeCalculationType;
         this.percentage = percentage;
@@ -203,6 +207,7 @@ public class LoanChargeData {
         this.amountWrittenOff = null;
         this.amountOutstanding = amountOutstanding;
         this.chargeTimeType = chargeTimeType;
+        this.submittedOnDate = null;
         this.dueDate = dueAsOfDate;
         this.chargeCalculationType = null;
         this.percentage = null;
@@ -234,6 +239,7 @@ public class LoanChargeData {
         this.amountWrittenOff = null;
         this.amountOutstanding = null;
         this.chargeTimeType = chargeTimeType;
+        this.submittedOnDate = null;
         this.dueDate = dueAsOfDate;
         this.chargeCalculationType = null;
         this.percentage = null;
@@ -264,6 +270,7 @@ public class LoanChargeData {
         this.amountWrittenOff = null;
         this.amountOutstanding = null;
         this.chargeTimeType = chargeData.chargeTimeType;
+        this.submittedOnDate = chargeData.submittedOnDate;
         this.dueDate = chargeData.dueDate;
         this.chargeCalculationType = null;
         this.percentage = null;
@@ -294,6 +301,7 @@ public class LoanChargeData {
         this.amountWrittenOff = chargeData.amountWrittenOff;
         this.amountOutstanding = chargeData.amountOutstanding;
         this.chargeTimeType = chargeData.chargeTimeType;
+        this.submittedOnDate = chargeData.submittedOnDate;
         this.dueDate = chargeData.dueDate;
         this.chargeCalculationType = chargeData.chargeCalculationType;
         this.percentage = chargeData.percentage;
@@ -326,6 +334,7 @@ public class LoanChargeData {
         this.amountWrittenOff = null;
         this.amountOutstanding = null;
         this.chargeTimeType = chargeTimeType;
+        this.submittedOnDate = null;
         this.dueDate = dueAsOfDate;
         this.chargeCalculationType = chargeCalculationType;
         this.percentage = null;
@@ -356,6 +365,7 @@ public class LoanChargeData {
         this.amountWrittenOff = null;
         this.amountOutstanding = null;
         this.chargeTimeType = null;
+        this.submittedOnDate = null;
         this.dueDate = dueAsOfDate;
         this.chargeCalculationType = null;
         this.percentage = null;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
index 09079b4ca..302a2b461 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
@@ -42,6 +42,7 @@ import javax.persistence.Table;
 import javax.persistence.UniqueConstraint;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import 
org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
 import org.apache.fineract.organisation.monetary.domain.Money;
 import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
@@ -68,6 +69,9 @@ public class LoanCharge extends AbstractPersistableCustom {
     @Column(name = "charge_time_enum", nullable = false)
     private Integer chargeTime;
 
+    @Column(name = "submitted_on_date", nullable = true)
+    private LocalDate submittedOnDate;
+
     @Column(name = "due_for_collection_as_of_date")
     private LocalDate dueDate;
 
@@ -229,6 +233,7 @@ public class LoanCharge extends AbstractPersistableCustom {
             final ChargePaymentMode chargePaymentMode, final Integer 
numberOfRepayments, final BigDecimal loanCharge) {
         this.loan = loan;
         this.charge = chargeDefinition;
+        this.submittedOnDate = DateUtils.getBusinessLocalDate();
         this.penaltyCharge = chargeDefinition.isPenalty();
         this.minCap = chargeDefinition.getMinCap();
         this.maxCap = chargeDefinition.getMaxCap();
@@ -597,6 +602,10 @@ public class LoanCharge extends AbstractPersistableCustom {
         return this.dueDate;
     }
 
+    public LocalDate getSubmittedOnDate() {
+        return submittedOnDate;
+    }
+
     private boolean determineIfFullyPaid() {
         if (this.amount == null) {
             return true;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java
index 3c5a7f12c..f9300bcc2 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java
@@ -59,8 +59,8 @@ public class LoanChargeReadPlatformServiceImpl implements 
LoanChargeReadPlatform
     private static final class LoanChargeMapper implements 
RowMapper<LoanChargeData> {
 
         public String schema() {
-            return "lc.id as id, lc.external_id as externalId, c.id as 
chargeId, c.name as name, " + "lc.amount as amountDue, "
-                    + "lc.amount_paid_derived as amountPaid, " + 
"lc.amount_waived_derived as amountWaived, "
+            return "lc.id as id, lc.external_id as externalId, c.id as 
chargeId, c.name as name, lc.submitted_on_date as submittedOnDate, "
+                    + "lc.amount as amountDue, " + "lc.amount_paid_derived as 
amountPaid, " + "lc.amount_waived_derived as amountWaived, "
                     + "lc.amount_writtenoff_derived as amountWrittenOff, " + 
"lc.amount_outstanding_derived as amountOutstanding, "
                     + "lc.calculation_percentage as percentageOf, 
lc.calculation_on_amount as amountPercentageAppliedTo, "
                     + "lc.charge_time_enum as chargeTime, " + "lc.is_penalty 
as penalty, "
@@ -116,6 +116,7 @@ public class LoanChargeReadPlatformServiceImpl implements 
LoanChargeReadPlatform
             final BigDecimal maxCap = rs.getBigDecimal("maxCap");
             final BigDecimal amountOrPercentage = 
rs.getBigDecimal("amountOrPercentage");
             final LocalDate disbursementDate = JdbcSupport.getLocalDate(rs, 
"disbursementDate");
+            final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, 
"submittedOnDate");
 
             if (disbursementDate != null) {
                 dueAsOfDate = disbursementDate;
@@ -123,8 +124,8 @@ public class LoanChargeReadPlatformServiceImpl implements 
LoanChargeReadPlatform
             final String externalId = rs.getString("externalId");
 
             return new LoanChargeData(id, chargeId, name, currency, amount, 
amountPaid, amountWaived, amountWrittenOff, amountOutstanding,
-                    chargeTimeType, dueAsOfDate, chargeCalculationType, 
percentageOf, amountPercentageAppliedTo, penalty, paymentMode, paid,
-                    waived, null, minCap, maxCap, amountOrPercentage, null, 
externalId);
+                    chargeTimeType, submittedOnDate, dueAsOfDate, 
chargeCalculationType, percentageOf, amountPercentageAppliedTo, penalty,
+                    paymentMode, paid, waived, null, minCap, maxCap, 
amountOrPercentage, null, externalId);
         }
     }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index 1a83d5120..cb89efb54 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -526,7 +526,8 @@ public final class LoanProductDataValidator {
         
baseDataValidator.reset().parameter(LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName)
                 
.value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE).notGreaterThanMax(BigDecimal.valueOf(100));
 
-        if 
(!amortizationType.equals(AmortizationMethod.EQUAL_PRINCIPAL.getValue()) && 
fixedPrincipalPercentagePerInstallment != null) {
+        if (amortizationType != null && 
!amortizationType.equals(AmortizationMethod.EQUAL_PRINCIPAL.getValue())
+                && fixedPrincipalPercentagePerInstallment != null) {
             
baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName).failWithCode(
                     
"not.supported.principal.fixing.not.allowed.with.equal.installments",
                     "Principal fixing cannot be done with equal installment 
amortization");
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 c7e64d63a..1d9f51d22 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
@@ -74,4 +74,5 @@
     <include file="parts/0052_loan_transaction_chargeback.xml" 
relativeToChangelogFile="true"/>
     <include file="parts/0053_add_external_events_purge_job.xml" 
relativeToChangelogFile="true"/>
     <include file="parts/0054_additional_fields_loan_repayment_schedule.xml" 
relativeToChangelogFile="true"/>
+    <include file="parts/0055_add_submitted_on_date_to_loan_charge.xml" 
relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0055_add_submitted_on_date_to_loan_charge.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0055_add_submitted_on_date_to_loan_charge.xml
new file mode 100644
index 000000000..a27aff8e9
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0055_add_submitted_on_date_to_loan_charge.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
+    <changeSet author="fineract" id="1">
+        <addColumn tableName="m_loan_charge">
+            <column name="submitted_on_date" type="date">
+                <constraints nullable="true"/>
+            </column>
+        </addColumn>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java
index d8ae731d4..1fa216a46 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java
@@ -36,6 +36,7 @@ import 
org.apache.fineract.batch.command.internal.CreateClientCommandStrategy;
 import 
org.apache.fineract.batch.command.internal.CreateLoanRescheduleRequestCommandStrategy;
 import 
org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandStrategy;
 import org.apache.fineract.batch.command.internal.DisburseLoanCommandStrategy;
+import org.apache.fineract.batch.command.internal.GetChargeByIdCommandStrategy;
 import 
org.apache.fineract.batch.command.internal.GetDatatableEntryByAppTableIdCommandStrategy;
 import org.apache.fineract.batch.command.internal.GetLoanByIdCommandStrategy;
 import 
org.apache.fineract.batch.command.internal.GetTransactionByIdCommandStrategy;
@@ -69,6 +70,8 @@ public class CommandStrategyProviderTest {
                 Arguments.of("loans/123/charges", HttpMethod.POST, 
"createChargeCommandStrategy", mock(CreateChargeCommandStrategy.class)),
                 Arguments.of("loans/123/charges", HttpMethod.GET, 
"collectChargesCommandStrategy",
                         mock(CollectChargesCommandStrategy.class)),
+                Arguments.of("loans/123/charges/123", HttpMethod.GET, 
"getChargeByIdCommandStrategy",
+                        mock(GetChargeByIdCommandStrategy.class)),
                 Arguments.of("loans/123/transactions?command=repayment", 
HttpMethod.POST, "createTransactionLoanCommandStrategy",
                         mock(CreateTransactionLoanCommandStrategy.class)),
                 
Arguments.of("loans/123/transactions?command=creditBalanceRefund", 
HttpMethod.POST, "createTransactionLoanCommandStrategy",
@@ -130,7 +133,8 @@ public class CommandStrategyProviderTest {
     private static Stream<Arguments> 
provideCommandStrategyResourceDetailsForErrors() {
         return Stream.of(Arguments.of("loans/123?command=reject", 
HttpMethod.POST),
                 Arguments.of("loans/glimAccount/746?command=approve", 
HttpMethod.POST), Arguments.of("loans/123", HttpMethod.PUT),
-                Arguments.of("datatables/test_dt_table", HttpMethod.GET), 
Arguments.of("datatables", HttpMethod.GET));
+                Arguments.of("datatables/test_dt_table", HttpMethod.GET), 
Arguments.of("datatables", HttpMethod.GET),
+                Arguments.of("loans//charges/123", HttpMethod.GET), 
Arguments.of("loans/123/charges/", HttpMethod.GET));
 
     }
 
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategyTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategyTest.java
new file mode 100644
index 000000000..3e81a1dd6
--- /dev/null
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/GetChargeByIdCommandStrategyTest.java
@@ -0,0 +1,149 @@
+/**
+ * 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.batch.command.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.verify;
+
+import java.util.stream.Stream;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.UriInfo;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.infrastructure.core.api.MutableUriInfo;
+import org.apache.fineract.portfolio.loanaccount.api.LoanChargesApiResource;
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * The {@link GetChargeByIdCommandStrategy} test class.
+ */
+public class GetChargeByIdCommandStrategyTest {
+
+    /**
+     * Query parameter provider.
+     *
+     * @return the test data stream
+     */
+    private static Stream<Arguments> provideQueryParameters() {
+        return Stream.of(Arguments.of(null, 0), 
Arguments.of("fields=name,amountOrPercentage", 1));
+    }
+
+    /**
+     * Test {@link GetChargeByIdCommandStrategy#execute} happy path scenario.
+     */
+    @ParameterizedTest
+    @MethodSource("provideQueryParameters")
+    public void testExecuteSuccessScenario(final String queryParameter, final 
int numberOfQueryParams) {
+        final TestContext testContext = new TestContext();
+
+        final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(4));
+        final Long chargeId = Long.valueOf(RandomStringUtils.randomNumeric(4));
+        final BatchRequest request = getBatchRequest(loanId, chargeId, 
queryParameter);
+        final String responseBody = "someResponseBody";
+
+        
given(testContext.loanChargesApiResource.retrieveLoanCharge(eq(loanId), 
eq(chargeId), any(UriInfo.class))).willReturn(responseBody);
+
+        final BatchResponse response = 
testContext.subjectToTest.execute(request, testContext.uriInfo);
+
+        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+        assertThat(response.getRequestId()).isEqualTo(request.getRequestId());
+        assertThat(response.getHeaders()).isEqualTo(request.getHeaders());
+        assertThat(response.getBody()).isEqualTo(responseBody);
+
+        
verify(testContext.loanChargesApiResource).retrieveLoanCharge(eq(loanId), 
eq(chargeId), testContext.uriInfoCaptor.capture());
+        MutableUriInfo mutableUriInfo = testContext.uriInfoCaptor.getValue();
+        
assertThat(mutableUriInfo.getAdditionalQueryParameters()).hasSize(numberOfQueryParams);
+    }
+
+    /**
+     * Creates and returns a request with the given loan id and charge id.
+     *
+     * @param loanId
+     *            the loan id
+     * @param chargeId
+     *            the charge id
+     * @param queryParameter
+     *            the query parameter
+     * @return BatchRequest
+     */
+    private BatchRequest getBatchRequest(final Long loanId, final Long 
chargeId, final String queryParameter) {
+        final BatchRequest br = new BatchRequest();
+        String relativeUrl = "loans/" + loanId + "/charges/" + chargeId;
+
+        if (queryParameter != null) {
+            relativeUrl = relativeUrl + "?" + queryParameter;
+        }
+
+        br.setRequestId(Long.valueOf(RandomStringUtils.randomNumeric(5)));
+        br.setRelativeUrl(relativeUrl);
+        br.setMethod(HttpMethod.GET);
+        br.setReference(Long.valueOf(RandomStringUtils.randomNumeric(5)));
+        br.setBody("{}");
+
+        return br;
+    }
+
+    /**
+     * Private test context class used since testng runs in parallel to avoid 
state between tests
+     */
+    private static class TestContext {
+
+        /**
+         * The subject to test
+         */
+        private final GetChargeByIdCommandStrategy subjectToTest;
+
+        /**
+         * The mock uri info
+         */
+        @Mock
+        private UriInfo uriInfo;
+
+        /**
+         * The mock {@link LoanChargesApiResource} object.
+         */
+        @Mock
+        private LoanChargesApiResource loanChargesApiResource;
+
+        /**
+         * The uri info captor
+         */
+        @Captor
+        private ArgumentCaptor<MutableUriInfo> uriInfoCaptor;
+
+        /**
+         * Test Context constructor
+         */
+        TestContext() {
+            MockitoAnnotations.openMocks(this);
+            subjectToTest = new 
GetChargeByIdCommandStrategy(loanChargesApiResource);
+        }
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java
index 1d1ca330d..c29d6371f 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BatchApiTest.java
@@ -34,6 +34,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
+import org.apache.commons.lang3.RandomStringUtils;
 import 
org.apache.fineract.batch.command.internal.AdjustTransactionCommandStrategy;
 import 
org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandStrategy;
 import org.apache.fineract.batch.domain.BatchRequest;
@@ -43,6 +44,7 @@ import 
org.apache.fineract.integrationtests.common.BatchHelper;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
 import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
 import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
 import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
 import 
org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
@@ -377,6 +379,69 @@ public class BatchApiTest {
         Assertions.assertEquals(HttpStatus.SC_OK, (long) 
response.get(3).getStatusCode(), "Verify Status Code 200 for Create Loan 
Charge");
     }
 
+    /**
+     * Tests that a new charge was added to a newly created loan and charges 
are Collected properly 200(OK) status was
+     * returned for successful responses. It first creates a new client and 
apply a loan, then creates a new charge for
+     * the create loan and then fetches all the applied charges
+     *
+     * @see 
org.apache.fineract.batch.command.internal.CollectChargesCommandStrategy
+     * @see 
org.apache.fineract.batch.command.internal.CreateChargeCommandStrategy
+     */
+    @Test
+    public void shouldReturnOkStatusForCreateAndGetChargeByIdCommand() {
+        final String loanProductJSON = new LoanProductTestBuilder() //
+                .withPrincipal("1000.00") //
+                .withNumberOfRepayments("24") //
+                .withRepaymentAfterEvery("1") //
+                .withRepaymentTypeAsMonth() //
+                .withinterestRatePerPeriod("2") //
+                .withInterestRateFrequencyTypeAsMonths() //
+                .withAmortizationTypeAsEqualPrincipalPayment() //
+                .withInterestTypeAsDecliningBalance() //
+                .currencyDetails("0", "100").build(null);
+
+        final Long applyLoanRequestId = 
Long.valueOf(RandomStringUtils.randomNumeric(4));
+        final Long approveLoanRequestId = applyLoanRequestId + 1;
+        final Long disburseLoanRequestId = approveLoanRequestId + 1;
+        final Long createChargeRequestId = disburseLoanRequestId + 1;
+        final Long getChargeByIdRequestId = createChargeRequestId + 1;
+
+        // Create product
+        final Integer productId = new LoanTransactionHelper(this.requestSpec, 
this.responseSpec).getLoanProductId(loanProductJSON);
+
+        // Create client
+        final Integer clientId = ClientHelper.createClient(this.requestSpec, 
this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, 
this.responseSpec, clientId);
+
+        // Create charge object and get id
+        final Integer chargeId = ChargesHelper.createCharges(this.requestSpec, 
this.responseSpec,
+                ChargesHelper.getLoanSpecifiedDueDateJSON());
+
+        final BatchRequest applyLoanRequest = 
BatchHelper.applyLoanRequestWithClientId(applyLoanRequestId, clientId, 
productId);
+
+        final BatchRequest approveLoanRequest = 
BatchHelper.approveLoanRequest(approveLoanRequestId, applyLoanRequestId);
+
+        final BatchRequest disburseLoanRequest = 
BatchHelper.disburseLoanRequest(disburseLoanRequestId, approveLoanRequestId);
+
+        final BatchRequest createChargeRequest = 
BatchHelper.createChargeRequest(createChargeRequestId, disburseLoanRequestId, 
chargeId);
+
+        final BatchRequest getChargeByIdRequest = 
BatchHelper.getChargeByIdCommandStrategy(getChargeByIdRequestId, 
createChargeRequestId);
+
+        // Create batch requests list
+        final List<BatchRequest> batchRequests = 
Arrays.asList(applyLoanRequest, approveLoanRequest, disburseLoanRequest,
+                createChargeRequest, getChargeByIdRequest);
+
+        // Create batch responses list
+        final List<BatchResponse> responses = 
BatchHelper.postBatchRequestsWithoutEnclosingTransaction(this.requestSpec, 
this.responseSpec,
+                BatchHelper.toJsonString(batchRequests));
+
+        Assertions.assertEquals(HttpStatus.SC_OK, 
responses.get(0).getStatusCode(), "Verify Status Code 200 for Apply Loan");
+        Assertions.assertEquals(HttpStatus.SC_OK, 
responses.get(1).getStatusCode(), "Verify Status Code 200 for Approve Loan");
+        Assertions.assertEquals(HttpStatus.SC_OK, 
responses.get(2).getStatusCode(), "Verify Status Code 200 for Disburse Loan");
+        Assertions.assertEquals(HttpStatus.SC_OK, 
responses.get(3).getStatusCode(), "Verify Status Code 200 for Create Charge");
+        Assertions.assertEquals(HttpStatus.SC_OK, 
responses.get(4).getStatusCode(), "Verify Status Code 200 for Get Charge By 
Id");
+    }
+
     /**
      * Tests that batch repayment for loans is happening properly. Collected 
properly 200(OK) status was returned for
      * successful responses. It first creates a new loan and then makes two 
repayments for it and then verifies that
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java
index 93693c882..925764c67 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/BatchHelper.java
@@ -349,10 +349,14 @@ public final class BatchHelper {
      * given requestId and reference
      *
      * @param requestId
+     *            the batch request id.
      * @param reference
+     *            the reference id.
+     * @param chargeId
+     *            the charge id used for getting charge type.
      * @return BatchRequest
      */
-    public static BatchRequest createChargeRequest(final Long requestId, final 
Long reference) {
+    public static BatchRequest createChargeRequest(final Long requestId, final 
Long reference, final Integer chargeId) {
 
         final BatchRequest br = new BatchRequest();
         br.setRequestId(requestId);
@@ -360,8 +364,12 @@ public final class BatchHelper {
         br.setMethod("POST");
         br.setReference(reference);
 
-        final String body = "{\"chargeId\": \"2\", \"locale\": \"en\", 
\"amount\": \"100\", "
-                + "\"dateFormat\": \"dd MMMM yyyy\", \"dueDate\": \"29 April 
2013\"}";
+        final String dateFormat = "dd MMMM yyyy";
+        final String dateString = 
LocalDate.now(Utils.getZoneIdOfTenant()).format(DateTimeFormatter.ofPattern(dateFormat));
+
+        final String body = String.format(
+                "{\"chargeId\": \"%d\", \"locale\": \"en\", \"amount\": 
\"11.15\", " + "\"dateFormat\": \"%s\", \"dueDate\": \"%s\"}",
+                chargeId, dateFormat, dateString);
         br.setBody(body);
 
         return br;
@@ -388,6 +396,30 @@ public final class BatchHelper {
         return br;
     }
 
+    /**
+     * Creates and returns a {@link 
org.apache.fineract.batch.command.internal.GetChargeByIdCommandStrategy} request
+     * with given requestId and reference.
+     *
+     * @param requestId
+     *            the request id
+     * @param reference
+     *            the reference
+     * @return the {@link BatchRequest}
+     */
+    public static BatchRequest getChargeByIdCommandStrategy(final Long 
requestId, final Long reference) {
+
+        final BatchRequest br = new BatchRequest();
+        String relativeUrl = "loans/$.loanId/charges/$.resourceId";
+
+        br.setRequestId(requestId);
+        br.setRelativeUrl(relativeUrl);
+        br.setMethod(HttpMethod.GET);
+        br.setReference(reference);
+        br.setBody("{}");
+
+        return br;
+    }
+
     /**
      * Creates and returns a {@link 
org.apache.fineract.batch.command.internal.ActivateClientCommandStrategy} 
Request
      * with given requestId and reference.


Reply via email to