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

commit d8f483ab45ba80c3bef01e9941a5b7ba2fd0cce9
Author: Adam Saghy <[email protected]>
AuthorDate: Sun Jul 24 23:14:45 2022 +0200

    Refactor Client to support auditable fields
---
 .../core/api/OffsetDateTimeAdapter.java            |  43 +++++++
 .../api/InternalClientInformationApiResource.java  |  87 +++++++++++++
 .../fineract/portfolio/client/domain/Client.java   |  31 ++---
 .../service/ClientReadPlatformServiceImpl.java     |   4 +-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 .../tenant/parts/0020_add_audit_entries.xml        | 141 +++++++++++++++++++++
 .../ClientAuditingIntegrationTest.java             | 132 +++++++++++++++++++
 .../integrationtests/common/ClientHelper.java      |   9 +-
 .../useradministration/users/UserHelper.java       |   2 +-
 9 files changed, 424 insertions(+), 26 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/OffsetDateTimeAdapter.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/OffsetDateTimeAdapter.java
new file mode 100644
index 000000000..c98f5610d
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/OffsetDateTimeAdapter.java
@@ -0,0 +1,43 @@
+/**
+ * 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.api;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import java.lang.reflect.Type;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Serializer for Java Offset Date Time {@link OffsetDateTime} that returns 
the date in ISO-8601 Offset date time format
+ */
+public class OffsetDateTimeAdapter implements JsonSerializer<OffsetDateTime> {
+
+    @Override
+    @SuppressWarnings("unused")
+    public JsonElement serialize(final OffsetDateTime dateTime, final Type 
typeOfSrc, final JsonSerializationContext context) {
+        JsonElement object = null;
+        if (dateTime != null) {
+            object = new 
JsonPrimitive(DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(dateTime));
+        }
+        return object;
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/InternalClientInformationApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/InternalClientInformationApiResource.java
new file mode 100644
index 000000000..f302abefe
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/InternalClientInformationApiResource.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.portfolio.client.api;
+
+import static 
org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY;
+import static 
org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_DATE;
+import static 
org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_BY;
+import static 
org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_DATE;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import 
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import 
org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.stereotype.Component;
+
+//@Profile("test")
+@Component
+@Path("/internal/client")
+@RequiredArgsConstructor
+@Slf4j
+public class InternalClientInformationApiResource implements InitializingBean {
+
+    private final ClientRepositoryWrapper clientRepositoryWrapper;
+    private final ToApiJsonSerializer<Map> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+
+    @Override
+    public void afterPropertiesSet() {
+        
log.warn("------------------------------------------------------------");
+        log.warn("                                                            
");
+        log.warn("DO NOT USE THIS IN PRODUCTION!");
+        log.warn("Internal client services mode is enabled");
+        log.warn("DO NOT USE THIS IN PRODUCTION!");
+        log.warn("                                                            
");
+        
log.warn("------------------------------------------------------------");
+
+    }
+
+    @GET
+    @Path("{clientId}/audit")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String getClientAuditFields(@Context final UriInfo uriInfo, 
@PathParam("clientId") Long clientId) {
+        
log.warn("------------------------------------------------------------");
+        log.warn("                                                            
");
+        log.warn("Fetching client with {}", clientId);
+        log.warn("                                                            
");
+        
log.warn("------------------------------------------------------------");
+
+        final Client client = 
clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
+        Map<String, Object> auditFields = new HashMap<>(
+                Map.of(CREATED_BY, client.getCreatedBy().orElse(null), 
CREATED_DATE, client.getCreatedDate().orElse(null), LAST_MODIFIED_BY,
+                        client.getLastModifiedBy().orElse(null), 
LAST_MODIFIED_DATE, client.getLastModifiedDate().orElse(null)));
+        final ApiRequestJsonSerializationSettings settings = 
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, auditFields);
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java
index c092ef970..059c88f66 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java
@@ -44,7 +44,7 @@ import 
org.apache.fineract.infrastructure.codes.domain.CodeValue;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.ApiParameterError;
 import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
-import 
org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+import 
org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
 import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.infrastructure.documentmanagement.domain.Image;
@@ -59,7 +59,7 @@ import org.apache.fineract.useradministration.domain.AppUser;
 @Entity
 @Table(name = "m_client", uniqueConstraints = { @UniqueConstraint(columnNames 
= { "account_no" }, name = "account_no_UNIQUE"), //
         @UniqueConstraint(columnNames = { "mobile_no" }, name = 
"mobile_no_UNIQUE") })
-public final class Client extends AbstractPersistableCustom {
+public class Client extends AbstractAuditableWithUTCDateTimeCustom {
 
     @Column(name = "account_no", length = 20, unique = true, nullable = false)
     private String accountNumber;
@@ -180,16 +180,14 @@ public final class Client extends 
AbstractPersistableCustom {
     @Column(name = "submittedon_date", nullable = true)
     private LocalDate submittedOnDate;
 
-    @ManyToOne(optional = true, fetch = FetchType.LAZY)
-    @JoinColumn(name = "submittedon_userid", nullable = true)
-    private AppUser submittedBy;
+    // Deprecated since common Auditable fields were introduced. Columns and 
data left untouched to help migration.
 
-    @Column(name = "updated_on", nullable = true)
-    private LocalDate updatedOnDate;
+    // @Column(name = "updated_on", nullable = true)
+    // private LocalDate updatedOnDate;
 
-    @ManyToOne(optional = true, fetch = FetchType.LAZY)
-    @JoinColumn(name = "updated_by", nullable = true)
-    private AppUser updatedBy;
+    // @ManyToOne(optional = true, fetch = FetchType.LAZY)
+    // @JoinColumn(name = "updated_by", nullable = true)
+    // private AppUser updatedBy;
 
     @ManyToOne(optional = true, fetch = FetchType.LAZY)
     @JoinColumn(name = "activatedon_userid", nullable = true)
@@ -270,9 +268,7 @@ public final class Client extends AbstractPersistableCustom 
{
                 savingsAccountId, dataOfBirth, gender, clientType, 
clientClassification, legalForm, isStaff);
     }
 
-    Client() {
-        this.setLegalForm(null);
-    }
+    protected Client() {}
 
     private Client(final AppUser currentUser, final ClientStatus status, final 
Office office, final Group clientParentGroup,
             final String accountNo, final String firstname, final String 
middlename, final String lastname, final String fullname,
@@ -289,7 +285,6 @@ public final class Client extends AbstractPersistableCustom 
{
         }
 
         this.submittedOnDate = submittedOnDate;
-        this.submittedBy = currentUser;
 
         this.status = status.getValue();
         this.office = office;
@@ -969,8 +964,6 @@ public final class Client extends AbstractPersistableCustom 
{
         this.rejectionReason = rejectionReason;
         this.rejectionDate = rejectionDate;
         this.rejectedBy = currentUser;
-        this.updatedBy = currentUser;
-        this.updatedOnDate = rejectionDate;
         this.status = ClientStatus.REJECTED.getValue();
 
     }
@@ -979,8 +972,6 @@ public final class Client extends AbstractPersistableCustom 
{
         this.withdrawalReason = withdrawalReason;
         this.withdrawalDate = withdrawalDate;
         this.withdrawnBy = currentUser;
-        this.updatedBy = currentUser;
-        this.updatedOnDate = withdrawalDate;
         this.status = ClientStatus.WITHDRAWN.getValue();
 
     }
@@ -990,8 +981,6 @@ public final class Client extends AbstractPersistableCustom 
{
         this.closureReason = null;
         this.reactivateDate = reactivateDate;
         this.reactivatedBy = currentUser;
-        this.updatedBy = currentUser;
-        this.updatedOnDate = reactivateDate;
         this.status = ClientStatus.PENDING.getValue();
 
     }
@@ -999,8 +988,6 @@ public final class Client extends AbstractPersistableCustom 
{
     public void reOpened(AppUser currentUser, LocalDate reopenedDate) {
         this.reopenedDate = reopenedDate;
         this.reopenedBy = currentUser;
-        this.updatedBy = currentUser;
-        this.updatedOnDate = reopenedDate;
         this.status = ClientStatus.PENDING.getValue();
 
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
index 4c8525e5a..5804fb96f 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
@@ -430,7 +430,7 @@ public class ClientReadPlatformServiceImpl implements 
ClientReadPlatformService
             sqlBuilder.append("left join m_savings_product sp on sp.id = 
c.default_savings_product ");
             sqlBuilder.append("left join m_office transferToOffice on 
transferToOffice.id = c.transfer_to_office_id ");
 
-            sqlBuilder.append("left join m_appuser sbu on sbu.id = 
c.submittedon_userid ");
+            sqlBuilder.append("left join m_appuser sbu on sbu.id = 
c.created_by ");
             sqlBuilder.append("left join m_appuser acu on acu.id = 
c.activatedon_userid ");
             sqlBuilder.append("left join m_appuser clu on clu.id = 
c.closedon_userid ");
             sqlBuilder.append("left join m_code_value cv on cv.id = 
c.gender_cv_id ");
@@ -618,7 +618,7 @@ public class ClientReadPlatformServiceImpl implements 
ClientReadPlatformService
             builder.append("left join m_staff s on s.id = c.staff_id ");
             builder.append("left join m_savings_product sp on sp.id = 
c.default_savings_product ");
             builder.append("left join m_office transferToOffice on 
transferToOffice.id = c.transfer_to_office_id ");
-            builder.append("left join m_appuser sbu on sbu.id = 
c.submittedon_userid ");
+            builder.append("left join m_appuser sbu on sbu.id = c.created_by 
");
             builder.append("left join m_appuser acu on acu.id = 
c.activatedon_userid ");
             builder.append("left join m_appuser clu on clu.id = 
c.closedon_userid ");
             builder.append("left join m_code_value cv on cv.id = 
c.gender_cv_id ");
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 e4954526c..25e775ef4 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
@@ -39,4 +39,5 @@
     <include file="parts/0017_fix_stretchy_reports.xml" 
relativeToChangelogFile="true"/>
     <include file="parts/0018_pentaho_reports_to_table.xml" 
relativeToChangelogFile="true"/>
     <include file="parts/0019_refactor_loan_transaction.xml" 
relativeToChangelogFile="true"/>
+    <include file="parts/0020_add_audit_entries.xml" 
relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0020_add_audit_entries.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0020_add_audit_entries.xml
new file mode 100644
index 000000000..779bd1da2
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0020_add_audit_entries.xml
@@ -0,0 +1,141 @@
+<?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";>
+    <!--Client-->
+    <changeSet author="fineract" id="1" context="mysql">
+        <addColumn tableName="m_client">
+            <column name="created_on_utc" type="DATETIME"/>
+            <column name="created_by" type="BIGINT" 
valueComputed="submittedon_userid"/>
+            <column name="last_modified_by" type="BIGINT"/>
+            <column name="last_modified_on_utc" type="DATETIME"/>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="1" context="postgresql">
+        <addColumn tableName="m_client">
+            <column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
+            <column name="created_by" type="BIGINT" 
valueComputed="submittedon_userid"/>
+            <column name="last_modified_by" type="BIGINT"/>
+            <column name="last_modified_on_utc" type="TIMESTAMP WITH TIME 
ZONE"/>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="2">
+        <addForeignKeyConstraint baseColumnNames="created_by" 
baseTableName="m_client"
+                                 constraintName="FK_client_created_by" 
deferrable="false" initiallyDeferred="false"
+                                 onDelete="RESTRICT" onUpdate="RESTRICT" 
referencedColumnNames="id"
+                                 referencedTableName="m_appuser" 
validate="true"/>
+        <addForeignKeyConstraint baseColumnNames="last_modified_by" 
baseTableName="m_client"
+                                 constraintName="FK_client_last_modified_by" 
deferrable="false" initiallyDeferred="false"
+                                 onDelete="RESTRICT" onUpdate="RESTRICT" 
referencedColumnNames="id"
+                                 referencedTableName="m_appuser" 
validate="true"/>
+    </changeSet>
+    <changeSet id="3" author="fineract">
+        <dropColumn tableName="m_client">
+            <column name="submittedon_userid"/>
+        </dropColumn>
+    </changeSet>
+    <!--Loan-->
+    <changeSet author="fineract" id="4" context="mysql">
+        <addColumn tableName="m_loan">
+            <column name="created_on_utc" type="DATETIME"/>
+            <column name="created_by" type="BIGINT" 
valueComputed="submittedon_userid"/>
+            <column name="last_modified_by" type="BIGINT"/>
+            <column name="last_modified_on_utc" type="DATETIME"/>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="4" context="postgresql">
+        <addColumn tableName="m_loan">
+            <column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
+            <column name="created_by" type="BIGINT" 
valueComputed="submittedon_userid"/>
+            <column name="last_modified_by" type="BIGINT"/>
+            <column name="last_modified_on_utc" type="TIMESTAMP WITH TIME 
ZONE"/>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="5">
+        <addForeignKeyConstraint baseColumnNames="created_by" 
baseTableName="m_loan"
+                                 constraintName="FK_loan_created_by" 
deferrable="false" initiallyDeferred="false"
+                                 onDelete="RESTRICT" onUpdate="RESTRICT" 
referencedColumnNames="id"
+                                 referencedTableName="m_appuser" 
validate="true"/>
+        <addForeignKeyConstraint baseColumnNames="last_modified_by" 
baseTableName="m_loan"
+                                 constraintName="FK_loan_last_modified_by" 
deferrable="false" initiallyDeferred="false"
+                                 onDelete="RESTRICT" onUpdate="RESTRICT" 
referencedColumnNames="id"
+                                 referencedTableName="m_appuser" 
validate="true"/>
+    </changeSet>
+    <changeSet id="6" author="fineract">
+        <dropForeignKeyConstraint baseTableName="m_loan" 
constraintName="FK_submittedon_userid"/>
+        <dropColumn tableName="m_loan">
+            <column name="submittedon_userid"/>
+        </dropColumn>
+    </changeSet>
+    <!-- Loan transactions -->
+    <changeSet author="fineract" id="7" context="mysql">
+        <addColumn tableName="m_loan_transaction">
+            <column name="created_on_utc" type="DATETIME"/>
+            <column name="last_modified_on_utc" type="DATETIME"/>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="8" context="postgresql">
+        <addColumn tableName="m_loan_transaction">
+            <column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE"/>
+            <column name="last_modified_on_utc" type="TIMESTAMP WITH TIME 
ZONE"/>
+        </addColumn>
+    </changeSet>
+    <changeSet id="9" author="fineract">
+        <renameColumn tableName="m_loan_transaction" 
oldColumnName="createdby_id" newColumnName="created_by" 
columnDataType="BIGINT"/>
+        <renameColumn tableName="m_loan_transaction" 
oldColumnName="lastmodifiedby_id" newColumnName="last_modified_by" 
columnDataType="BIGINT"/>
+    </changeSet>
+    <changeSet author="fineract" id="10">
+        <addForeignKeyConstraint baseColumnNames="created_by" 
baseTableName="m_loan_transaction"
+                                 
constraintName="FK_loan_transaction_created_by" deferrable="false" 
initiallyDeferred="false"
+                                 onDelete="RESTRICT" onUpdate="RESTRICT" 
referencedColumnNames="id"
+                                 referencedTableName="m_appuser" 
validate="true"/>
+        <addForeignKeyConstraint baseColumnNames="last_modified_by" 
baseTableName="m_loan_transaction"
+                                 
constraintName="FK_loan_transaction_last_modified_by" deferrable="false" 
initiallyDeferred="false"
+                                 onDelete="RESTRICT" onUpdate="RESTRICT" 
referencedColumnNames="id"
+                                 referencedTableName="m_appuser" 
validate="true"/>
+    </changeSet>
+    <changeSet id="11" author="fineract" context="mysql">
+        <update tableName="stretchy_report">
+            <column name="report_sql"
+                    value="SELECT &#13;&#10;mc.id AS &quot;id&quot;, 
&#13;&#10;mc.firstname AS &quot;firstName&quot;,&#13;&#10;mc.middlename AS 
&quot;middleName&quot;,&#13;&#10;mc.lastname AS 
&quot;lastName&quot;,&#13;&#10;mc.display_name AS 
&quot;fullName&quot;,&#13;&#10;mc.mobile_no AS &quot;mobileNo&quot;, 
&#13;&#10;ml.principal_amount AS &quot;loanAmount&quot;, 
&#13;&#10;(IFNULL(ml.principal_outstanding_derived, 0) + 
IFNULL(ml.interest_outstanding_derived, 0) + IFNULL(ml.fee_charges_ [...]
+            <where>report_name = 'Loan payments received (Active 
Loans)'</where>
+        </update>
+        <update tableName="stretchy_report">
+            <column name="report_sql"
+                    value="SELECT &#13;&#10;ml.id AS &quot;loanId&quot;, 
&#13;&#10;mc.id AS &quot;id&quot;, &#13;&#10;mc.firstname AS 
&quot;firstName&quot;,&#13;&#10;mc.middlename AS 
&quot;middleName&quot;,&#13;&#10;mc.lastname AS 
&quot;lastName&quot;,&#13;&#10;mc.display_name AS 
&quot;fullName&quot;,&#13;&#10;mc.mobile_no AS &quot;mobileNo&quot;, 
&#13;&#10;ml.principal_amount AS &quot;loanAmount&quot;, 
&#13;&#10;(IFNULL(ml.principal_outstanding_derived, 0) + 
IFNULL(ml.interest_outstandi [...]
+            <where>report_name = 'Loan payments received (Overdue 
Loans)'</where>
+        </update>
+    </changeSet>
+    <changeSet id="12" author="fineract" context="postgresql">
+        <update tableName="stretchy_report">
+            <column name="report_sql"
+                    value="SELECT mc.id AS id, mc.firstname AS firstName, 
mc.middlename AS middleName, mc.lastname AS lastName, mc.display_name AS 
fullName, mc.mobile_no AS mobileNo, ml.principal_amount AS loanAmount, 
(COALESCE(ml.principal_outstanding_derived, 0) + 
COALESCE(ml.interest_outstanding_derived, 0) + 
COALESCE(ml.fee_charges_outstanding_derived, 0) + 
COALESCE(ml.penalty_charges_outstanding_derived, 0)) AS loanOutstanding, 
ounder.id AS officeNumber, ml.account_no AS loanAccount [...]
+            <where>report_name = 'Loan payments received (Active 
Loans)'</where>
+        </update>
+        <update tableName="stretchy_report">
+            <column name="report_sql"
+                    value="SELECT ml.id AS loanId, mc.id AS id, mc.firstname 
AS firstName, mc.middlename AS middleName, mc.lastname AS lastName, 
mc.display_name AS fullName, mc.mobile_no AS mobileNo, ml.principal_amount AS 
loanAmount, (COALESCE(ml.principal_outstanding_derived, 0) + 
COALESCE(ml.interest_outstanding_derived, 0) + 
COALESCE(ml.fee_charges_outstanding_derived, 0) + 
COALESCE(ml.penalty_charges_outstanding_derived, 0)) AS loanOutstanding, 
ounder.id AS officeNumber, ml.account_ [...]
+            <where>report_name = 'Loan payments received (Overdue 
Loans)'</where>
+        </update>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientAuditingIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientAuditingIntegrationTest.java
new file mode 100644
index 000000000..145c5b96b
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientAuditingIntegrationTest.java
@@ -0,0 +1,132 @@
+/**
+ * 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 static 
org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY;
+import static 
org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_DATE;
+import static 
org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_BY;
+import static 
org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_DATE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Map;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
+import 
org.apache.fineract.integrationtests.useradministration.users.UserHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClientAuditingIntegrationTest {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(ClientAuditingIntegrationTest.class);
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private ClientHelper clientHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+
+        this.responseSpec = new 
ResponseSpecBuilder().expectStatusCode(200).build();
+        this.clientHelper = new ClientHelper(this.requestSpec, 
this.responseSpec);
+    }
+
+    @Test
+    public void checkAuditDates() throws InterruptedException {
+        final Integer staffId = StaffHelper.createStaff(this.requestSpec, 
this.responseSpec);
+        String username = Utils.randomNameGenerator("user", 8);
+        final Integer userId = (Integer) 
UserHelper.createUser(this.requestSpec, this.responseSpec, 1, staffId, 
username, "resourceId");
+        OffsetDateTime now = OffsetDateTime.now(ZoneId.of("Asia/Kolkata"));
+        // Testing in minutes precision, but still need to take care around 
the end of the actual minute
+        if (now.getSecond() > 56) {
+            Thread.sleep(5000);
+            now = OffsetDateTime.now(ZoneId.of("Asia/Kolkata"));
+        }
+        LOG.info("-------------------------Creating 
Client---------------------------");
+
+        final Integer clientID = ClientHelper.createClientPending(requestSpec, 
responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(requestSpec, responseSpec, 
clientID);
+        Map<String, Object> auditFieldsResponse = 
ClientHelper.getClientAuditFields(requestSpec, responseSpec, clientID, "");
+
+        OffsetDateTime createdDate = OffsetDateTime.parse((String) 
auditFieldsResponse.get(CREATED_DATE),
+                DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+
+        OffsetDateTime lastModifiedDate = OffsetDateTime.parse((String) 
auditFieldsResponse.get(LAST_MODIFIED_DATE),
+                DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+
+        LOG.info("-------------------------Check Audit 
dates---------------------------");
+        assertEquals(1, auditFieldsResponse.get(CREATED_BY));
+        assertEquals(1, auditFieldsResponse.get(LAST_MODIFIED_BY));
+        assertEquals(now.getYear(), createdDate.getYear());
+        assertEquals(now.getMonth(), createdDate.getMonth());
+        assertEquals(now.getDayOfMonth(), createdDate.getDayOfMonth());
+        assertEquals(now.getHour(), createdDate.getHour());
+        assertEquals(now.getMinute(), createdDate.getMinute());
+
+        assertEquals(now.getYear(), lastModifiedDate.getYear());
+        assertEquals(now.getMonth(), lastModifiedDate.getMonth());
+        assertEquals(now.getDayOfMonth(), lastModifiedDate.getDayOfMonth());
+        assertEquals(now.getHour(), lastModifiedDate.getHour());
+        assertEquals(now.getMinute(), lastModifiedDate.getMinute());
+
+        LOG.info("-------------------------Modify Client with System 
user---------------------------");
+        this.requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization",
+                "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(username, 
"password"));
+
+        this.clientHelper = new ClientHelper(this.requestSpec, 
this.responseSpec);
+        this.clientHelper.activateClient(clientID);
+        auditFieldsResponse = ClientHelper.getClientAuditFields(requestSpec, 
responseSpec, clientID, "");
+
+        createdDate = OffsetDateTime.parse((String) 
auditFieldsResponse.get(CREATED_DATE), DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+
+        lastModifiedDate = OffsetDateTime.parse((String) 
auditFieldsResponse.get(LAST_MODIFIED_DATE),
+                DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+
+        LOG.info("-------------------------Check Audit 
dates---------------------------");
+        assertEquals(1, auditFieldsResponse.get(CREATED_BY));
+        assertEquals(now.getYear(), createdDate.getYear());
+        assertEquals(now.getMonth(), createdDate.getMonth());
+        assertEquals(now.getDayOfMonth(), createdDate.getDayOfMonth());
+        assertEquals(now.getHour(), createdDate.getHour());
+        assertEquals(now.getMinute(), createdDate.getMinute());
+
+        now = OffsetDateTime.now(ZoneId.of("Asia/Kolkata"));
+
+        assertEquals(userId, auditFieldsResponse.get(LAST_MODIFIED_BY));
+        assertEquals(now.getYear(), lastModifiedDate.getYear());
+        assertEquals(now.getMonth(), lastModifiedDate.getMonth());
+        assertEquals(now.getDayOfMonth(), lastModifiedDate.getDayOfMonth());
+        assertEquals(now.getHour(), lastModifiedDate.getHour());
+        assertEquals(now.getMinute(), lastModifiedDate.getMinute());
+    }
+
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java
index ba6ec8cf6..63d2bf5d3 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java
@@ -346,6 +346,13 @@ public class ClientHelper {
         return Utils.performServerGet(requestSpec, responseSpec, 
GET_CLIENT_URL, jsonReturn);
     }
 
+    public static HashMap<String, Object> getClientAuditFields(final 
RequestSpecification requestSpec,
+            final ResponseSpecification responseSpec, final Integer clientId, 
final String jsonReturn) {
+        final String GET_CLIENT_URL = 
"/fineract-provider/api/v1/internal/client/" + clientId + "/audit?" + 
Utils.TENANT_IDENTIFIER;
+        log.info("---------------------------------GET A CLIENT ENTITY AUDIT 
FIELDS---------------------------------------------");
+        return Utils.performServerGet(requestSpec, responseSpec, 
GET_CLIENT_URL, jsonReturn);
+    }
+
     /* Client status is a map.So adding SuppressWarnings */
     @SuppressWarnings("unchecked")
     public static HashMap<String, Object> getClientStatus(final 
RequestSpecification requestSpec, final ResponseSpecification responseSpec,
@@ -435,7 +442,7 @@ public class ClientHelper {
 
     }
 
-    private String getActivateClientAsJSON(String date) {
+    private static String getActivateClientAsJSON(String date) {
         final HashMap<String, String> map = new HashMap<>();
         map.put("locale", CommonConstants.LOCALE);
         map.put("dateFormat", CommonConstants.DATE_FORMAT);
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java
index 1a86dca53..e00851245 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/useradministration/users/UserHelper.java
@@ -86,7 +86,7 @@ public final class UserHelper {
     private static String getTestCreateUserAsJSON(int roleId, int staffId, 
String username) {
         return "{ \"username\": \"" + username + "\", \"firstname\": \"Test\", 
\"lastname\": \"User\", \"email\": \"[email protected]\","
                 + " \"officeId\": \"1\", \"staffId\": " + "\"" + staffId + 
"\",\"roles\": [\"" + roleId
-                + "\"], \"sendPasswordToEmail\": false}";
+                + "\"], \"sendPasswordToEmail\": false,     \"password\": 
\"password\"," + "    \"repeatPassword\": \"password\"}";
     }
 
     private static String getTestUpdateUserAsJSON(String username) {

Reply via email to