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

aleks 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 02fb166b5b FINERACT-2239: New command processing - resource notes
02fb166b5b is described below

commit 02fb166b5ba070cac5420637cfbce2c366dcc6ea
Author: Aleks <[email protected]>
AuthorDate: Sun Feb 1 02:26:44 2026 +0100

    FINERACT-2239: New command processing - resource notes
---
 config/docker/compose/fineract.yml                 |   2 -
 config/docker/env/fineract-common.env              |   2 +-
 .../note/service/AcmeNoteWritePlatformService.java |  25 +-
 .../note/starter/TestDefaultConfiguration.java     |  12 -
 .../note/starter/TestOverrideConfiguration.java    |  12 -
 docker-compose.yml                                 |   1 +
 .../infrastructure/cache/data/CacheData.java       |   7 +-
 .../portfolio/note/data/NoteCreateRequest.java     |  14 +-
 .../portfolio/note/data/NoteCreateResponse.java    |   8 +-
 .../fineract/portfolio/note/data/NoteData.java     |  48 ++-
 .../portfolio/note/data/NoteDeleteRequest.java     |  13 +-
 .../portfolio/note/data/NoteDeleteResponse.java    |   6 +-
 .../portfolio/note/data/NoteUpdateRequest.java     |  16 +-
 .../portfolio/note/data/NoteUpdateResponse.java    |   9 +-
 .../note/service/NoteWritePlatformService.java     |  19 +-
 .../BuyDownFeeWritePlatformServiceImpl.java        |  12 +-
 .../CapitalizedIncomeWritePlatformServiceImpl.java |  12 +-
 .../ProgressiveLoanAccountConfiguration.java       |  13 +-
 .../infrastructure/core/config/SecurityConfig.java |  44 +++
 .../starter/LoanAccountConfiguration.java          |  12 +-
 .../portfolio/note/api/NotesApiResource.java       | 238 +++++------
 .../note/api/NotesApiResourceSwagger.java          |  82 ----
 .../{NoteCommand.java => NoteCreateCommand.java}   |  19 +-
 .../{NoteCommand.java => NoteDeleteCommand.java}   |  19 +-
 .../{NoteCommand.java => NoteUpdateCommand.java}   |  19 +-
 .../fineract/portfolio/note/domain/Note.java       |  27 +-
 ...dHandler.java => NoteCreateCommandHandler.java} |  33 +-
 ...dHandler.java => NoteDeleteCommandHandler.java} |  35 +-
 ...dHandler.java => NoteUpdateCommandHandler.java} |  34 +-
 .../portfolio/note/listener/NoteListener.java      |  49 +++
 .../NoteCommandFromApiJsonDeserializer.java        | 102 -----
 .../NoteWritePlatformServiceJpaRepositoryImpl.java | 438 ++++-----------------
 .../note/starter/NoteAutoConfiguration.java        |   5 +-
 ...nsferWritePlatformServiceJpaRepositoryImpl.java |   9 +-
 .../transfer/starter/TransferConfiguration.java    |  11 +-
 .../src/main/resources/application.properties      |  32 ++
 .../main/resources/ValidationMessages.properties   |   5 +
 .../fineract/integrationtests/NotesTest.java       |   7 +-
 .../integrationtests/common/NotesHelper.java       |  10 +-
 39 files changed, 548 insertions(+), 913 deletions(-)

diff --git a/config/docker/compose/fineract.yml 
b/config/docker/compose/fineract.yml
index ae5323c41c..801b73fc65 100644
--- a/config/docker/compose/fineract.yml
+++ b/config/docker/compose/fineract.yml
@@ -16,8 +16,6 @@
 # under the License.
 #
 
-version: "3.8"
-
 services:
   fineract:
     # user: "${FINERACT_USER}:${FINERACT_GROUP}"
diff --git a/config/docker/env/fineract-common.env 
b/config/docker/env/fineract-common.env
index b77bb24b0e..4b0dc24650 100644
--- a/config/docker/env/fineract-common.env
+++ b/config/docker/env/fineract-common.env
@@ -56,4 +56,4 @@ FINERACT_REMOTE_JOB_MESSAGE_HANDLER_SPRING_EVENTS_ENABLED=true
 FINERACT_INSECURE_HTTP_CLIENT=true
 SPRING_PROFILES_ACTIVE=test,diagnostics
 OTEL_SERVICE_NAME=fineract
-JAVA_TOOL_OPTIONS="-Xmx1G -XX:MinRAMPercentage=25 -XX:MaxRAMPercentage=80 
-XX:TieredStopAtLevel=1 -XX:+UseContainerSupport -XX:+UseStringDeduplication 
--add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED 
--add-opens=java.base/java.lang=ALL-UNNAMED 
--add-opens=java.base/java.lang.invoke=ALL-UNNAMED 
--add-opens=java.base/java.io=ALL-UNNAMED 
--add-opens=java.base/java.security=ALL-UNNAMED 
--add-opens=java.base/java.util=ALL-UNNAMED 
--add-opens=java.management/javax.management=ALL-UNNAMED [...]
+JAVA_TOOL_OPTIONS=" 
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5000 -Xmx1G 
-XX:MinRAMPercentage=25 -XX:MaxRAMPercentage=80 -XX:TieredStopAtLevel=1 
-XX:+UseContainerSupport -XX:+UseStringDeduplication 
--add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED 
--add-opens=java.base/java.lang=ALL-UNNAMED 
--add-opens=java.base/java.lang.invoke=ALL-UNNAMED 
--add-opens=java.base/java.io=ALL-UNNAMED 
--add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.uti 
[...]
diff --git 
a/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java
 
b/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java
index 14b194f20a..ad15721bae 100644
--- 
a/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java
+++ 
b/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java
@@ -19,9 +19,12 @@
 package com.acme.fineract.portfolio.note.service;
 
 import lombok.extern.slf4j.Slf4j;
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
-import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.note.data.NoteCreateRequest;
+import org.apache.fineract.portfolio.note.data.NoteCreateResponse;
+import org.apache.fineract.portfolio.note.data.NoteDeleteRequest;
+import org.apache.fineract.portfolio.note.data.NoteDeleteResponse;
+import org.apache.fineract.portfolio.note.data.NoteUpdateRequest;
+import org.apache.fineract.portfolio.note.data.NoteUpdateResponse;
 import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -38,27 +41,17 @@ public class AcmeNoteWritePlatformService implements 
NoteWritePlatformService, I
     }
 
     @Override
-    public CommandProcessingResult createNote(JsonCommand command) {
+    public NoteCreateResponse createNote(NoteCreateRequest request) {
         throw new UnsupportedOperationException("createNote() is not yet 
implemented.");
     }
 
     @Override
-    public void createLoanTransactionNote(Long loanTransactionId, String note) 
{
-        throw new UnsupportedOperationException("createLoanTransactionNote() 
is not yet implemented.");
-    }
-
-    @Override
-    public CommandProcessingResult updateNote(JsonCommand command) {
+    public NoteUpdateResponse updateNote(NoteUpdateRequest request) {
         throw new UnsupportedOperationException("updateNote() is not yet 
implemented.");
     }
 
     @Override
-    public CommandProcessingResult deleteNote(JsonCommand command) {
+    public NoteDeleteResponse deleteNote(NoteDeleteRequest request) {
         throw new UnsupportedOperationException("deleteNote() is not yet 
implemented.");
     }
-
-    @Override
-    public void createAndPersistClientNote(Client client, JsonCommand command) 
{
-        log.warn("createAndPersistClientNote() is intentionally left empty and 
does nothing.");
-    }
 }
diff --git 
a/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestDefaultConfiguration.java
 
b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestDefaultConfiguration.java
index 8a2395af1b..1706c8c4c3 100644
--- 
a/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestDefaultConfiguration.java
+++ 
b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestDefaultConfiguration.java
@@ -21,7 +21,6 @@ package com.acme.fineract.portfolio.note.starter;
 import static org.mockito.Mockito.mock;
 
 import org.apache.fineract.infrastructure.core.config.FineractProperties;
-import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import 
org.apache.fineract.infrastructure.core.service.database.RoutingDataSource;
 import 
org.apache.fineract.infrastructure.core.service.database.RoutingDataSourceServiceFactory;
 import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
@@ -29,7 +28,6 @@ import 
org.apache.fineract.portfolio.group.domain.GroupRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.note.domain.NoteRepository;
-import 
org.apache.fineract.portfolio.note.serialization.NoteCommandFromApiJsonDeserializer;
 import 
org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -38,11 +36,6 @@ import org.springframework.jdbc.core.JdbcTemplate;
 public class TestDefaultConfiguration {
     // NOTE: unfortunately an abastract base class that contains all these 
mock functions won't work
 
-    @Bean
-    public FromJsonHelper fromJsonHelper() {
-        return mock(FromJsonHelper.class);
-    }
-
     @Bean
     public RoutingDataSourceServiceFactory routingDataSourceServiceFactory() {
         return mock(RoutingDataSourceServiceFactory.class);
@@ -82,9 +75,4 @@ public class TestDefaultConfiguration {
     public LoanTransactionRepository loanTransactionRepository() {
         return mock(LoanTransactionRepository.class);
     }
-
-    @Bean
-    public NoteCommandFromApiJsonDeserializer 
fromApiJsonDeserializer(FromJsonHelper fromJsonHelper) {
-        return new NoteCommandFromApiJsonDeserializer(fromJsonHelper);
-    }
 }
diff --git 
a/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestOverrideConfiguration.java
 
b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestOverrideConfiguration.java
index 10f8d6cce0..cc89614e01 100644
--- 
a/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestOverrideConfiguration.java
+++ 
b/custom/acme/note/starter/src/test/java/com/acme/fineract/portfolio/note/starter/TestOverrideConfiguration.java
@@ -20,7 +20,6 @@ package com.acme.fineract.portfolio.note.starter;
 
 import static org.mockito.Mockito.mock;
 
-import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import 
org.apache.fineract.infrastructure.core.service.database.RoutingDataSource;
 import 
org.apache.fineract.infrastructure.core.service.database.RoutingDataSourceServiceFactory;
 import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
@@ -28,7 +27,6 @@ import 
org.apache.fineract.portfolio.group.domain.GroupRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.note.domain.NoteRepository;
-import 
org.apache.fineract.portfolio.note.serialization.NoteCommandFromApiJsonDeserializer;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -37,11 +35,6 @@ import org.springframework.jdbc.core.JdbcTemplate;
 public class TestOverrideConfiguration {
     // NOTE: unfortunately an abastract base class that contains all these 
mock functions won't work
 
-    @Bean
-    public FromJsonHelper fromJsonHelper() {
-        return mock(FromJsonHelper.class);
-    }
-
     @Bean
     public RoutingDataSourceServiceFactory routingDataSourceServiceFactory() {
         return mock(RoutingDataSourceServiceFactory.class);
@@ -81,9 +74,4 @@ public class TestOverrideConfiguration {
     public LoanTransactionRepository loanTransactionRepository() {
         return mock(LoanTransactionRepository.class);
     }
-
-    @Bean
-    public NoteCommandFromApiJsonDeserializer 
fromApiJsonDeserializer(FromJsonHelper fromJsonHelper) {
-        return new NoteCommandFromApiJsonDeserializer(fromJsonHelper);
-    }
 }
diff --git a/docker-compose.yml b/docker-compose.yml
index f37fb6f2ca..89723ea21c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -29,6 +29,7 @@ services:
       service: fineract
     ports:
       - "8443:8443"
+      - "5000:5000"
     depends_on:
       db:
         condition: service_healthy
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/cache/data/CacheData.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/cache/data/CacheData.java
index 771b6d553b..6d6eaea5fc 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/cache/data/CacheData.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/cache/data/CacheData.java
@@ -18,6 +18,8 @@
  */
 package org.apache.fineract.infrastructure.cache.data;
 
+import java.io.Serial;
+import java.io.Serializable;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -28,7 +30,10 @@ import 
org.apache.fineract.infrastructure.core.data.EnumOptionData;
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
-public final class CacheData {
+public final class CacheData implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
 
     @SuppressWarnings("unused")
     private EnumOptionData cacheType;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteCreateRequest.java
similarity index 68%
copy from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
copy to 
fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteCreateRequest.java
index 05a1082086..b5e7edfed9 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteCreateRequest.java
@@ -18,19 +18,31 @@
  */
 package org.apache.fineract.portfolio.note.data;
 
+import io.swagger.v3.oas.annotations.Hidden;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
 import java.io.Serial;
 import java.io.Serializable;
 import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
+import org.apache.fineract.portfolio.note.domain.NoteType;
 
+@Builder
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
-public class NoteRequest implements Serializable {
+public class NoteCreateRequest implements Serializable {
 
     @Serial
     private static final long serialVersionUID = 1L;
 
+    @Hidden
+    private Long resourceId;
+    @Hidden
+    private NoteType type;
+    @Size(max = 1000, message = 
"{org.apache.fineract.portfolio.note.note.size}")
+    @NotNull(message = "{org.apache.fineract.portfolio.note.note.not-null}")
     private String note;
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteCreateResponse.java
similarity index 86%
copy from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
copy to 
fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteCreateResponse.java
index 05a1082086..403a50a6db 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteCreateResponse.java
@@ -21,16 +21,20 @@ package org.apache.fineract.portfolio.note.data;
 import java.io.Serial;
 import java.io.Serializable;
 import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+@Builder
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
-public class NoteRequest implements Serializable {
+public class NoteCreateResponse implements Serializable {
 
     @Serial
     private static final long serialVersionUID = 1L;
 
-    private String note;
+    private Long entityId;
+    private Long officeId;
+    private Long resourceId;
 }
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteData.java
 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteData.java
index 091e76792e..6f998732a8 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteData.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteData.java
@@ -18,31 +18,37 @@
  */
 package org.apache.fineract.portfolio.note.data;
 
+import java.io.Serial;
+import java.io.Serializable;
 import java.time.OffsetDateTime;
+import lombok.AllArgsConstructor;
 import lombok.Builder;
-import lombok.Getter;
+import lombok.Data;
+import lombok.NoArgsConstructor;
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 
-/**
- * Immutable data object represent note or case information about a client, 
loan or loan transaction.
- */
-@Getter
 @Builder
-public class NoteData {
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class NoteData implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
 
-    private final Long id;
-    private final Long clientId;
-    private final Long groupId;
-    private final Long loanId;
-    private final Long loanTransactionId;
-    private final Long depositAccountId;
-    private final Long savingAccountId;
-    private final EnumOptionData noteType;
-    private final String note;
-    private final Long createdById;
-    private final String createdByUsername;
-    private final OffsetDateTime createdOn;
-    private final Long updatedById;
-    private final String updatedByUsername;
-    private final OffsetDateTime updatedOn;
+    private Long id;
+    private Long clientId;
+    private Long groupId;
+    private Long loanId;
+    private Long loanTransactionId;
+    private Long depositAccountId;
+    private Long savingAccountId;
+    private EnumOptionData noteType;
+    private String note;
+    private Long createdById;
+    private String createdByUsername;
+    private OffsetDateTime createdOn;
+    private Long updatedById;
+    private String updatedByUsername;
+    private OffsetDateTime updatedOn;
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteDeleteRequest.java
similarity index 78%
copy from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
copy to 
fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteDeleteRequest.java
index 05a1082086..6ef1049587 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteDeleteRequest.java
@@ -18,19 +18,28 @@
  */
 package org.apache.fineract.portfolio.note.data;
 
+import io.swagger.v3.oas.annotations.Hidden;
 import java.io.Serial;
 import java.io.Serializable;
 import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
+import org.apache.fineract.portfolio.note.domain.NoteType;
 
+@Builder
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
-public class NoteRequest implements Serializable {
+public class NoteDeleteRequest implements Serializable {
 
     @Serial
     private static final long serialVersionUID = 1L;
 
-    private String note;
+    @Hidden
+    private Long id;
+    @Hidden
+    private Long resourceId;
+    @Hidden
+    private NoteType type;
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteDeleteResponse.java
similarity index 90%
copy from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
copy to 
fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteDeleteResponse.java
index 05a1082086..cfae896ab0 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteDeleteResponse.java
@@ -21,16 +21,18 @@ package org.apache.fineract.portfolio.note.data;
 import java.io.Serial;
 import java.io.Serializable;
 import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+@Builder
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
-public class NoteRequest implements Serializable {
+public class NoteDeleteResponse implements Serializable {
 
     @Serial
     private static final long serialVersionUID = 1L;
 
-    private String note;
+    private Long resourceId;
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteUpdateRequest.java
similarity index 67%
copy from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
copy to 
fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteUpdateRequest.java
index 05a1082086..bca0cec30f 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteUpdateRequest.java
@@ -18,19 +18,33 @@
  */
 package org.apache.fineract.portfolio.note.data;
 
+import io.swagger.v3.oas.annotations.Hidden;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
 import java.io.Serial;
 import java.io.Serializable;
 import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
+import org.apache.fineract.portfolio.note.domain.NoteType;
 
+@Builder
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
-public class NoteRequest implements Serializable {
+public class NoteUpdateRequest implements Serializable {
 
     @Serial
     private static final long serialVersionUID = 1L;
 
+    @Hidden
+    private Long id;
+    @Hidden
+    private Long resourceId;
+    @Hidden
+    private NoteType type;
+    @Size(max = 1000, message = 
"{org.apache.fineract.portfolio.note.note.size}")
+    @NotNull(message = "{org.apache.fineract.portfolio.note.note.not-null}")
     private String note;
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteUpdateResponse.java
similarity index 84%
rename from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
rename to 
fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteUpdateResponse.java
index 05a1082086..5ddf70056b 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/data/NoteRequest.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteUpdateResponse.java
@@ -20,17 +20,22 @@ package org.apache.fineract.portfolio.note.data;
 
 import java.io.Serial;
 import java.io.Serializable;
+import java.util.Map;
 import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+@Builder
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
-public class NoteRequest implements Serializable {
+public class NoteUpdateResponse implements Serializable {
 
     @Serial
     private static final long serialVersionUID = 1L;
 
-    private String note;
+    private Long officeId;
+    private Long resourceId;
+    private Map<String, Object> changes;
 }
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformService.java
 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformService.java
index 1d8afbd1f4..6db5a9fc62 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformService.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformService.java
@@ -18,19 +18,18 @@
  */
 package org.apache.fineract.portfolio.note.service;
 
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
-import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.note.data.NoteCreateRequest;
+import org.apache.fineract.portfolio.note.data.NoteCreateResponse;
+import org.apache.fineract.portfolio.note.data.NoteDeleteRequest;
+import org.apache.fineract.portfolio.note.data.NoteDeleteResponse;
+import org.apache.fineract.portfolio.note.data.NoteUpdateRequest;
+import org.apache.fineract.portfolio.note.data.NoteUpdateResponse;
 
 public interface NoteWritePlatformService {
 
-    CommandProcessingResult createNote(JsonCommand command);
+    NoteCreateResponse createNote(NoteCreateRequest request);
 
-    void createLoanTransactionNote(Long loanTransactionId, String note);
+    NoteUpdateResponse updateNote(NoteUpdateRequest request);
 
-    CommandProcessingResult updateNote(JsonCommand command);
-
-    CommandProcessingResult deleteNote(JsonCommand command);
-
-    void createAndPersistClientNote(Client client, JsonCommand command);
+    NoteDeleteResponse deleteNote(NoteDeleteRequest request);
 }
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
index 213265bd0b..414c6e2ec2 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
@@ -49,9 +49,11 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import 
org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeeBalanceRepository;
-import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
+import org.apache.fineract.portfolio.note.data.NoteCreateRequest;
+import org.apache.fineract.portfolio.note.domain.NoteType;
 import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
 import 
org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
+import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.transaction.annotation.Transactional;
 
 @Slf4j
@@ -63,11 +65,11 @@ public class BuyDownFeeWritePlatformServiceImpl implements 
BuyDownFeePlatformSer
     private final LoanTransactionRepository loanTransactionRepository;
     private final PaymentDetailWritePlatformService 
paymentDetailWritePlatformService;
     private final LoanJournalEntryPoster loanJournalEntryPoster;
-    private final NoteWritePlatformService noteWritePlatformService;
     private final ExternalIdFactory externalIdFactory;
     private final LoanBuyDownFeeBalanceRepository 
loanBuyDownFeeBalanceRepository;
     private final BusinessEventNotifierService businessEventNotifierService;
     private final CodeValueRepository codeValueRepository;
+    private final ApplicationEventPublisher eventPublisher;
 
     @Transactional
     @Override
@@ -108,7 +110,8 @@ public class BuyDownFeeWritePlatformServiceImpl implements 
BuyDownFeePlatformSer
         // Add note if provided
         final String noteText = command.stringValueOfParameterNamed("note");
         if (StringUtils.isNotBlank(noteText)) {
-            
noteWritePlatformService.createLoanTransactionNote(buyDownFeeTransaction.getId(),
 noteText);
+            
eventPublisher.publishEvent(NoteCreateRequest.builder().type(NoteType.LOAN_TRANSACTION)
+                    
.resourceId(buyDownFeeTransaction.getId()).note(noteText).build());
         }
 
         
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(buyDownFeeTransaction,
 false, false);
@@ -177,7 +180,8 @@ public class BuyDownFeeWritePlatformServiceImpl implements 
BuyDownFeePlatformSer
         // Create a note if provided
         final String noteText = command.stringValueOfParameterNamed("note");
         if (StringUtils.isNotBlank(noteText)) {
-            
noteWritePlatformService.createLoanTransactionNote(savedBuyDownFeeAdjustment.getId(),
 noteText);
+            
eventPublisher.publishEvent(NoteCreateRequest.builder().type(NoteType.LOAN_TRANSACTION)
+                    
.resourceId(savedBuyDownFeeAdjustment.getId()).note(noteText).build());
         }
 
         
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(savedBuyDownFeeAdjustment,
 false, false);
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java
index 896c54c6df..2c804898ca 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java
@@ -48,9 +48,11 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import 
org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
-import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
+import org.apache.fineract.portfolio.note.data.NoteCreateRequest;
+import org.apache.fineract.portfolio.note.domain.NoteType;
 import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
 import 
org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
+import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.transaction.annotation.Transactional;
 
 @Slf4j
@@ -62,7 +64,6 @@ public class CapitalizedIncomeWritePlatformServiceImpl 
implements CapitalizedInc
     private final LoanTransactionRepository loanTransactionRepository;
     private final PaymentDetailWritePlatformService 
paymentDetailWritePlatformService;
     private final LoanJournalEntryPoster journalEntryPoster;
-    private final NoteWritePlatformService noteWritePlatformService;
     private final ExternalIdFactory externalIdFactory;
     private final LoanCapitalizedIncomeBalanceRepository 
capitalizedIncomeBalanceRepository;
     private final ReprocessLoanTransactionsService 
reprocessLoanTransactionsService;
@@ -71,6 +72,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl 
implements CapitalizedInc
     private final BusinessEventNotifierService businessEventNotifierService;
     private final CodeValueRepository codeValueRepository;
     private final LoanScheduleService loanScheduleService;
+    private final ApplicationEventPublisher eventPublisher;
 
     @Transactional
     @Override
@@ -107,7 +109,8 @@ public class CapitalizedIncomeWritePlatformServiceImpl 
implements CapitalizedInc
         // Create a note if provided
         final String noteText = command.stringValueOfParameterNamed("note");
         if (noteText != null && !noteText.isEmpty()) {
-            
noteWritePlatformService.createLoanTransactionNote(capitalizedIncomeTransaction.getId(),
 noteText);
+            
eventPublisher.publishEvent(NoteCreateRequest.builder().type(NoteType.LOAN_TRANSACTION)
+                    
.resourceId(capitalizedIncomeTransaction.getId()).note(noteText).build());
         }
 
         // Create journal entries immediately for this transaction
@@ -156,7 +159,8 @@ public class CapitalizedIncomeWritePlatformServiceImpl 
implements CapitalizedInc
         // Create a note if provided
         final String noteText = command.stringValueOfParameterNamed("note");
         if (noteText != null && !noteText.isEmpty()) {
-            
noteWritePlatformService.createLoanTransactionNote(savedCapitalizedIncomeAdjustment.getId(),
 noteText);
+            
eventPublisher.publishEvent(NoteCreateRequest.builder().type(NoteType.LOAN_TRANSACTION)
+                    
.resourceId(savedCapitalizedIncomeAdjustment.getId()).note(noteText).build());
         }
         // Create journal entries immediately for this transaction
         
journalEntryPoster.postJournalEntriesForLoanTransaction(savedCapitalizedIncomeAdjustment,
 false, false);
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java
index 6b7a543b0d..936e0f8e1e 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java
@@ -42,9 +42,9 @@ import 
org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService;
 import 
org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanTransactionValidator;
 import 
org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanTransactionValidatorImpl;
 import 
org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
-import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
 import 
org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
 import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -56,15 +56,14 @@ public class ProgressiveLoanAccountConfiguration {
     public CapitalizedIncomePlatformService 
capitalizedIncomePlatformService(ProgressiveLoanTransactionValidator 
loanTransactionValidator,
             LoanAssembler loanAssembler, LoanTransactionRepository 
loanTransactionRepository,
             PaymentDetailWritePlatformService 
paymentDetailWritePlatformService, LoanJournalEntryPoster journalEntryPoster,
-            NoteWritePlatformService noteWritePlatformService, 
ExternalIdFactory externalIdFactory,
-            LoanCapitalizedIncomeBalanceRepository 
capitalizedIncomeBalanceRepository,
+            ExternalIdFactory externalIdFactory, 
LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository,
             ReprocessLoanTransactionsService reprocessLoanTransactionsService, 
LoanBalanceService loanBalanceService,
             LoanLifecycleStateMachine loanLifecycleStateMachine, 
BusinessEventNotifierService businessEventNotifierService,
-            CodeValueRepository codeValueRepository, LoanScheduleService 
loanScheduleService) {
+            CodeValueRepository codeValueRepository, LoanScheduleService 
loanScheduleService, ApplicationEventPublisher eventPublisher) {
         return new 
CapitalizedIncomeWritePlatformServiceImpl(loanTransactionValidator, 
loanAssembler, loanTransactionRepository,
-                paymentDetailWritePlatformService, journalEntryPoster, 
noteWritePlatformService, externalIdFactory,
-                capitalizedIncomeBalanceRepository, 
reprocessLoanTransactionsService, loanBalanceService, loanLifecycleStateMachine,
-                businessEventNotifierService, codeValueRepository, 
loanScheduleService);
+                paymentDetailWritePlatformService, journalEntryPoster, 
externalIdFactory, capitalizedIncomeBalanceRepository,
+                reprocessLoanTransactionsService, loanBalanceService, 
loanLifecycleStateMachine, businessEventNotifierService,
+                codeValueRepository, loanScheduleService, eventPublisher);
     }
 
     @Bean
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
index c8849f0039..438923e6f4 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
@@ -157,6 +157,50 @@ public class SecurityConfig {
                     .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, 
"READ_CURRENCY")
                     .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, 
"/api/*/currencies"))
                     .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"UPDATE_CURRENCY")
+                    // notes: read
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, 
"/api/*/clients/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, 
"READ_CLIENTNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, 
"/api/*/loans/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, 
"READ_LOANNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, 
"/api/*/loanTransactions/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, 
"READ_LOANTRANSACTIONNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, 
"/api/*/savings/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, 
"READ_SAVINGNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, 
"/api/*/groups/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, 
"READ_GROUPNOTE")
+                    // notes: create
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, 
"/api/*/clients/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"CREATE_CLIENTNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, 
"/api/*/loans/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"CREATE_LOANNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, 
"/api/*/loanTransactions/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"CREATE_LOANTRANSACTIONNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, 
"/api/*/savings/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"CREATE_SAVINGNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, 
"/api/*/groups/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"CREATE_GROUPNOTE")
+                    // notes: update
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, 
"/api/*/clients/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"UPDATE_CLIENTNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, 
"/api/*/loans/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"UPDATE_LOANNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, 
"/api/*/loanTransactions/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"UPDATE_LOANTRANSACTIONNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, 
"/api/*/savings/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"UPDATE_SAVINGNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, 
"/api/*/groups/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"UPDATE_GROUPNOTE")
+                    // notes: delete
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, 
"/api/*/clients/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"DELETE_CLIENTNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, 
"/api/*/loans/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"DELETE_LOANNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, 
"/api/*/loanTransactions/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"DELETE_LOANTRANSACTIONNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, 
"/api/*/savings/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"DELETE_SAVINGNOTE")
+                    .requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, 
"/api/*/groups/*/notes"))
+                    .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, 
"DELETE_GROUPNOTE")
 
                     .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, 
"/api/*/twofactor/validate")).fullyAuthenticated()
                     
.requestMatchers(API_MATCHER.matcher("/api/*/twofactor")).fullyAuthenticated()
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
index 5a0e5f0139..3db3920349 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
@@ -175,7 +175,6 @@ import 
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
 import 
org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
 import 
org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
 import org.apache.fineract.portfolio.note.domain.NoteRepository;
-import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
 import 
org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
 import 
org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
 import org.apache.fineract.portfolio.rate.service.RateAssembler;
@@ -184,6 +183,7 @@ import 
org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.Repaym
 import 
org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
 import org.apache.fineract.portfolio.savings.service.GSIMReadPlatformService;
 import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Lazy;
@@ -397,12 +397,12 @@ public class LoanAccountConfiguration {
     public BuyDownFeePlatformService 
buyDownFeePlatformService(ProgressiveLoanTransactionValidator 
loanTransactionValidator,
             LoanAssembler loanAssembler, LoanTransactionRepository 
loanTransactionRepository,
             PaymentDetailWritePlatformService 
paymentDetailWritePlatformService, LoanJournalEntryPoster 
loanJournalEntryPoster,
-            NoteWritePlatformService noteWritePlatformService, 
ExternalIdFactory externalIdFactory,
-            LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository, 
BusinessEventNotifierService businessEventNotifierService,
-            CodeValueRepository codeValueRepository) {
+            ExternalIdFactory externalIdFactory, 
LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository,
+            BusinessEventNotifierService businessEventNotifierService, 
CodeValueRepository codeValueRepository,
+            ApplicationEventPublisher eventPublisher) {
         return new 
BuyDownFeeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler, 
loanTransactionRepository,
-                paymentDetailWritePlatformService, loanJournalEntryPoster, 
noteWritePlatformService, externalIdFactory,
-                loanBuyDownFeeBalanceRepository, businessEventNotifierService, 
codeValueRepository);
+                paymentDetailWritePlatformService, loanJournalEntryPoster, 
externalIdFactory, loanBuyDownFeeBalanceRepository,
+                businessEventNotifierService, codeValueRepository, 
eventPublisher);
     }
 
     @Bean
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResource.java
index 849997c0c9..f18db73c58 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResource.java
@@ -19,13 +19,8 @@
 package org.apache.fineract.portfolio.note.api;
 
 import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Content;
-import io.swagger.v3.oas.annotations.media.Schema;
-import io.swagger.v3.oas.annotations.parameters.RequestBody;
-import io.swagger.v3.oas.annotations.responses.ApiResponse;
-import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
 import jakarta.ws.rs.Consumes;
 import jakarta.ws.rs.DELETE;
 import jakarta.ws.rs.GET;
@@ -35,201 +30,160 @@ import jakarta.ws.rs.Path;
 import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.Produces;
 import jakarta.ws.rs.core.MediaType;
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
+import java.util.function.Supplier;
 import lombok.RequiredArgsConstructor;
-import org.apache.fineract.commands.domain.CommandWrapper;
-import org.apache.fineract.commands.service.CommandWrapperBuilder;
-import 
org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
-import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
-import 
org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
-import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.command.core.CommandPipeline;
+import org.apache.fineract.portfolio.note.command.NoteCreateCommand;
+import org.apache.fineract.portfolio.note.command.NoteDeleteCommand;
+import org.apache.fineract.portfolio.note.command.NoteUpdateCommand;
+import org.apache.fineract.portfolio.note.data.NoteCreateRequest;
+import org.apache.fineract.portfolio.note.data.NoteCreateResponse;
 import org.apache.fineract.portfolio.note.data.NoteData;
-import org.apache.fineract.portfolio.note.data.NoteRequest;
+import org.apache.fineract.portfolio.note.data.NoteDeleteRequest;
+import org.apache.fineract.portfolio.note.data.NoteDeleteResponse;
+import org.apache.fineract.portfolio.note.data.NoteUpdateRequest;
+import org.apache.fineract.portfolio.note.data.NoteUpdateResponse;
 import org.apache.fineract.portfolio.note.domain.NoteType;
 import 
org.apache.fineract.portfolio.note.exception.NoteResourceNotSupportedException;
 import org.apache.fineract.portfolio.note.service.NoteReadPlatformService;
 import org.springframework.stereotype.Component;
 
 @Path("/v1/{resourceType}/{resourceId}/notes")
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
 @Component
-@Tag(name = "Notes", description = "Notes API allows to enter notes for 
supported resources.")
+@Tag(name = "Notes", description = """
+        Notes API allows to enter notes for supported resources.
+        """)
 @RequiredArgsConstructor
 public class NotesApiResource {
 
-    public static final String CLIENTNOTE = "CLIENTNOTE";
-    public static final String LOANNOTE = "LOANNOTE";
-    public static final String LOANTRANSACTIONNOTE = "LOANTRANSACTIONNOTE";
-    public static final String SAVINGNOTE = "SAVINGNOTE";
-    public static final String GROUPNOTE = "GROUPNOTE";
-    public static final String INVALIDNOTE = "INVALIDNOTE";
-    private static final Set<String> NOTE_DATA_PARAMETERS = new HashSet<>(
-            Arrays.asList("id", "resourceId", "clientId", "groupId", "loanId", 
"loanTransactionId", "depositAccountId", "savingAccountId",
-                    "noteType", "note", "createdById", "createdByUsername", 
"createdOn", "updatedById", "updatedByUsername", "updatedOn"));
-    private final PlatformSecurityContext context;
     private final NoteReadPlatformService readPlatformService;
-    private final DefaultToApiJsonSerializer<NoteData> toApiJsonSerializer;
-    private final ApiRequestParameterHelper apiRequestParameterHelper;
-    private final PortfolioCommandSourceWritePlatformService 
commandsSourceWritePlatformService;
+    private final CommandPipeline commandPipeline;
 
     @GET
-    @Consumes({ MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_JSON })
-    @Operation(summary = "Retrieve a Resource's description", description = 
"Retrieves a Resource's Notes\n\n"
-            + "Note: Notes are returned in descending createOn order.\n" + 
"\n" + "Example Requests:\n" + "\n" + "clients/2/notes\n" + "\n"
-            + "\n" + "groups/2/notes?fields=note,createdOn,createdByUsername")
-    public List<NoteData> retrieveNotesByResource(
-            @PathParam("resourceType") @Parameter(description = 
"resourceType") final String resourceType,
-            @PathParam("resourceId") @Parameter(description = "resourceId") 
final Long resourceId) {
+    @Operation(summary = "Retrieve a Resource's description", description = """
+            Retrieves a resource's notes
 
-        final NoteType noteType = NoteType.fromApiUrl(resourceType);
+            Note: results are returned in descending createOn order.
+
+            Example Requests:
+
+            - clients/2/notes
+            - groups/2/notes?fields=note,createdOn,createdByUsername
+            """)
+    public List<NoteData> retrieveNotesByResource(@PathParam("resourceType") 
final String resourceType,
+            @PathParam("resourceId") final Long resourceId) {
+        final var noteType = NoteType.fromApiUrl(resourceType);
 
         if (noteType == null) {
             throw new NoteResourceNotSupportedException(resourceType);
         }
 
-        
this.context.authenticatedUser().validateHasReadPermission(getResourceDetails(noteType,
 resourceId).entityName());
-
-        final Integer noteTypeId = noteType.getValue();
-
-        return readPlatformService.retrieveNotesByResource(resourceId, 
noteTypeId);
+        return readPlatformService.retrieveNotesByResource(resourceId, 
noteType.getValue());
     }
 
     @GET
     @Path("{noteId}")
-    @Consumes({ MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_JSON })
-    @Operation(summary = "Retrieve a Resource Note", description = "Retrieves 
a Resource Note\n\n" + "Example Requests:\n" + "\n"
-            + "clients/1/notes/76\n" + "\n" + "\n" + "groups/1/notes/20\n" + 
"\n" + "\n"
-            + "clients/1/notes/76?fields=note,createdOn,createdByUsername\n" + 
"\n" + "\n"
-            + "groups/1/notes/20?fields=note,createdOn,createdByUsername")
-    public NoteData retrieveNote(@PathParam("resourceType") 
@Parameter(description = "resourceType") final String resourceType,
-            @PathParam("resourceId") @Parameter(description = "resourceId") 
final Long resourceId,
-            @PathParam("noteId") @Parameter(description = "noteId") final Long 
noteId) {
-
+    @Operation(summary = "Retrieve a Resource Note", description = """
+            Retrieves a resource Note
+
+            Example Requests:
+
+            - clients/1/notes/76
+            - groups/1/notes/20
+            - clients/1/notes/76?fields=note,createdOn,createdByUsername
+            - groups/1/notes/20?fields=note,createdOn,createdByUsername
+            """)
+    public NoteData retrieveNote(@PathParam("resourceType") final String 
resourceType, @PathParam("resourceId") final Long resourceId,
+            @PathParam("noteId") final Long noteId) {
         final NoteType noteType = NoteType.fromApiUrl(resourceType);
 
         if (noteType == null) {
             throw new NoteResourceNotSupportedException(resourceType);
         }
 
-        
this.context.authenticatedUser().validateHasReadPermission(getResourceDetails(noteType,
 resourceId).entityName());
-
-        final Integer noteTypeId = noteType.getValue();
-
-        final NoteData note = this.readPlatformService.retrieveNote(noteId, 
resourceId, noteTypeId);
-        return note;
+        return readPlatformService.retrieveNote(noteId, resourceId, 
noteType.getValue());
     }
 
     @POST
-    @Consumes({ MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_JSON })
-    @Operation(summary = "Add a Resource Note", description = "Adds a new note 
to a supported resource.\n\n" + "Example Requests:\n" + "\n"
-            + "clients/1/notes\n" + "\n" + "\n" + "groups/1/notes")
-    @RequestBody(required = true, content = @Content(schema = 
@Schema(implementation = NoteRequest.class)))
-    @ApiResponses({
-            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
NotesApiResourceSwagger.PostResourceTypeResourceIdNotesResponse.class))) })
-    public CommandProcessingResult addNewNote(@PathParam("resourceType") 
@Parameter(description = "resourceType") final String resourceType,
-            @PathParam("resourceId") @Parameter(description = "resourceId") 
final Long resourceId,
-            @Parameter(hidden = true) final NoteRequest noteRequest) {
+    @Operation(summary = "Add a Resource Note", description = """
+            Adds a new note to a supported resource.
 
-        final NoteType noteType = NoteType.fromApiUrl(resourceType);
+            Example Requests:
 
-        if (noteType == null) {
+            - clients/1/notes
+            - groups/1/notes
+            """)
+    public NoteCreateResponse addNewNote(@PathParam("resourceType") final 
String resourceType,
+            @PathParam("resourceId") final Long resourceId, @Valid final 
NoteCreateRequest request) {
+        final var type = NoteType.fromApiUrl(resourceType);
+
+        if (type == null) {
             throw new NoteResourceNotSupportedException(resourceType);
         }
 
-        final CommandWrapper resourceDetails = getResourceDetails(noteType, 
resourceId);
-        final CommandWrapper commandRequest = new 
CommandWrapperBuilder().createNote(resourceDetails, resourceType, resourceId)
-                .withJson(toApiJsonSerializer.serialize(noteRequest)).build();
+        request.setResourceId(resourceId);
+        request.setType(type);
+
+        final var command = new NoteCreateCommand();
+
+        command.setPayload(request);
+
+        final Supplier<NoteCreateResponse> response = 
commandPipeline.send(command);
 
-        return 
commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return response.get();
     }
 
     @PUT
     @Path("{noteId}")
-    @Consumes({ MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_JSON })
-    @Operation(summary = "Update a Resource Note", description = "Updates a 
Resource Note")
-    @RequestBody(required = true, content = @Content(schema = 
@Schema(implementation = NoteRequest.class)))
-    @ApiResponses({
-            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
NotesApiResourceSwagger.PutResourceTypeResourceIdNotesNoteIdResponse.class))) })
-    public CommandProcessingResult updateNote(@PathParam("resourceType") 
@Parameter(description = "resourceType") final String resourceType,
-            @PathParam("resourceId") @Parameter(description = "resourceId") 
final Long resourceId,
-            @PathParam("noteId") @Parameter(description = "noteId") final Long 
noteId,
-            @Parameter(hidden = true) final NoteRequest noteRequest) {
-
-        final NoteType noteType = NoteType.fromApiUrl(resourceType);
-
-        if (noteType == null) {
+    @Operation(summary = "Update a Resource Note", description = """
+            Updates a Resource Note
+            """)
+    public NoteUpdateResponse updateNote(@PathParam("resourceType") final 
String resourceType,
+            @PathParam("resourceId") final Long resourceId, 
@PathParam("noteId") final Long noteId,
+            @Valid final NoteUpdateRequest request) {
+        final var type = NoteType.fromApiUrl(resourceType);
+
+        if (type == null) {
             throw new NoteResourceNotSupportedException(resourceType);
         }
 
-        final CommandWrapper resourceDetails = getResourceDetails(noteType, 
resourceId);
+        request.setId(noteId);
+        request.setResourceId(resourceId);
+        request.setType(type);
 
-        final CommandWrapper commandRequest = new 
CommandWrapperBuilder().updateNote(resourceDetails, resourceType, resourceId, 
noteId)
-                .withJson(toApiJsonSerializer.serialize(noteRequest)).build();
+        final var command = new NoteUpdateCommand();
 
-        return 
commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        command.setPayload(request);
+
+        final Supplier<NoteUpdateResponse> response = 
commandPipeline.send(command);
+
+        return response.get();
     }
 
     @DELETE
     @Path("{noteId}")
-    @Consumes({ MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_JSON })
-    @Operation(summary = "Delete a Resource Note", description = "Deletes a 
Resource Note")
-    @ApiResponses({
-            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
NotesApiResourceSwagger.DeleteResourceTypeResourceIdNotesNoteIdResponse.class)))
 })
-    public CommandProcessingResult deleteNote(@PathParam("resourceType") 
@Parameter(description = "resourceType") final String resourceType,
-            @PathParam("resourceId") @Parameter(description = "resourceId") 
final Long resourceId,
-            @PathParam("noteId") @Parameter(description = "noteId") final Long 
noteId) {
-
-        final NoteType noteType = NoteType.fromApiUrl(resourceType);
-
-        if (noteType == null) {
+    @Operation(summary = "Delete a Resource Note", description = """
+            Deletes a Resource Note
+            """)
+    public NoteDeleteResponse deleteNote(@PathParam("resourceType") final 
String resourceType,
+            @PathParam("resourceId") final Long resourceId, 
@PathParam("noteId") final Long noteId) {
+        final var type = NoteType.fromApiUrl(resourceType);
+
+        if (type == null) {
             throw new NoteResourceNotSupportedException(resourceType);
         }
 
-        final CommandWrapper resourceDetails = getResourceDetails(noteType, 
resourceId);
+        var request = 
NoteDeleteRequest.builder().id(noteId).resourceId(resourceId).type(type).build();
 
-        final CommandWrapper commandRequest = new 
CommandWrapperBuilder().deleteNote(resourceDetails, resourceType, resourceId, 
noteId)
-                .build();
+        final var command = new NoteDeleteCommand();
 
-        return 
commandsSourceWritePlatformService.logCommandSource(commandRequest);
-    }
+        command.setPayload(request);
 
-    private CommandWrapper getResourceDetails(final NoteType type, final Long 
resourceId) {
-        CommandWrapperBuilder resourceDetails = new CommandWrapperBuilder();
-        String resourceNameForPermissions;
-        switch (type) {
-            case CLIENT -> {
-                resourceNameForPermissions = CLIENTNOTE;
-                resourceDetails.withClientId(resourceId);
-            }
-            case LOAN -> {
-                resourceNameForPermissions = LOANNOTE;
-                resourceDetails.withLoanId(resourceId);
-            }
-            case LOAN_TRANSACTION -> {
-                resourceNameForPermissions = LOANTRANSACTIONNOTE;
-                // updating loanId, to distinguish saving transaction note and
-                // loan transaction note as we are using subEntityId for both.
-                resourceDetails.withLoanId(resourceId);
-                resourceDetails.withSubEntityId(resourceId);
-            }
-            case SAVING_ACCOUNT -> {
-                resourceNameForPermissions = SAVINGNOTE;
-                resourceDetails.withSavingsId(resourceId);
-            }
-            case GROUP -> {
-                resourceNameForPermissions = GROUPNOTE;
-                resourceDetails.withGroupId(resourceId);
-            }
-            default -> resourceNameForPermissions = INVALIDNOTE;
-        }
-        return 
resourceDetails.withEntityName(resourceNameForPermissions).build();
+        final Supplier<NoteDeleteResponse> response = 
commandPipeline.send(command);
+
+        return response.get();
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResourceSwagger.java
deleted file mode 100644
index e53cd454f8..0000000000
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/api/NotesApiResourceSwagger.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * 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.note.api;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-
-/**
- * Created by Chirag Gupta on 12/29/17.
- */
-public final class NotesApiResourceSwagger {
-
-    private NotesApiResourceSwagger() {}
-
-    @Schema(description = "PostResourceTypeResourceIdNotesRequest")
-    public static final class PostResourceTypeResourceIdNotesRequest {
-
-        private PostResourceTypeResourceIdNotesRequest() {}
-
-        @Schema(example = "a note about the client")
-        public String note;
-    }
-
-    @Schema(description = "PostResourceTypeResourceIdNotesResponse")
-    public static final class PostResourceTypeResourceIdNotesResponse {
-
-        private PostResourceTypeResourceIdNotesResponse() {}
-
-        @Schema(example = "1")
-        public Long officeId;
-        @Schema(example = "1")
-        public Long clientId;
-        @Schema(example = "76")
-        public Long resourceId;
-    }
-
-    @Schema(description = "PutResourceTypeResourceIdNotesNoteIdResponse")
-    public static final class PutResourceTypeResourceIdNotesNoteIdResponse {
-
-        private PutResourceTypeResourceIdNotesNoteIdResponse() {}
-
-        static final class PutNotesChanges {
-
-            private PutNotesChanges() {}
-
-            @Schema(example = "a note about the client")
-            public String note;
-        }
-
-        @Schema(example = "1")
-        public Long officeId;
-        @Schema(example = "1")
-        public Long clientId;
-        @Schema(example = "76")
-        public Long resourceId;
-        public PutNotesChanges changes;
-    }
-
-    @Schema(description = "DeleteResourceTypeResourceIdNotesNoteIdResponse")
-    public static final class DeleteResourceTypeResourceIdNotesNoteIdResponse {
-
-        private DeleteResourceTypeResourceIdNotesNoteIdResponse() {}
-
-        @Schema(example = "76")
-        public Long resourceId;
-    }
-}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCommand.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCreateCommand.java
similarity index 75%
copy from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCommand.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCreateCommand.java
index 6f2419d11d..2206107914 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCommand.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCreateCommand.java
@@ -18,16 +18,11 @@
  */
 package org.apache.fineract.portfolio.note.command;
 
-/**
- * Immutable command used for create or update of notes.
- */
-public class NoteCommand {
-
-    @SuppressWarnings("unused")
-    private final String note;
-
-    public NoteCommand(final String note) {
-        this.note = note;
-    }
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.fineract.command.core.Command;
+import org.apache.fineract.portfolio.note.data.NoteCreateRequest;
 
-}
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class NoteCreateCommand extends Command<NoteCreateRequest> {}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCommand.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteDeleteCommand.java
similarity index 75%
copy from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCommand.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteDeleteCommand.java
index 6f2419d11d..ba97ea2f6a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCommand.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteDeleteCommand.java
@@ -18,16 +18,11 @@
  */
 package org.apache.fineract.portfolio.note.command;
 
-/**
- * Immutable command used for create or update of notes.
- */
-public class NoteCommand {
-
-    @SuppressWarnings("unused")
-    private final String note;
-
-    public NoteCommand(final String note) {
-        this.note = note;
-    }
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.fineract.command.core.Command;
+import org.apache.fineract.portfolio.note.data.NoteDeleteRequest;
 
-}
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class NoteDeleteCommand extends Command<NoteDeleteRequest> {}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCommand.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteUpdateCommand.java
similarity index 75%
rename from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCommand.java
rename to 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteUpdateCommand.java
index 6f2419d11d..2671bb2f92 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteCommand.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteUpdateCommand.java
@@ -18,16 +18,11 @@
  */
 package org.apache.fineract.portfolio.note.command;
 
-/**
- * Immutable command used for create or update of notes.
- */
-public class NoteCommand {
-
-    @SuppressWarnings("unused")
-    private final String note;
-
-    public NoteCommand(final String note) {
-        this.note = note;
-    }
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.fineract.command.core.Command;
+import org.apache.fineract.portfolio.note.data.NoteUpdateRequest;
 
-}
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class NoteUpdateCommand extends Command<NoteUpdateRequest> {}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java
index ecc28837fc..db71517173 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java
@@ -23,10 +23,9 @@ import jakarta.persistence.Entity;
 import jakarta.persistence.JoinColumn;
 import jakarta.persistence.ManyToOne;
 import jakarta.persistence.Table;
-import java.util.LinkedHashMap;
 import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.commons.lang3.Strings;
 import 
org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
 import org.apache.fineract.portfolio.client.domain.Client;
 import org.apache.fineract.portfolio.group.domain.Group;
@@ -74,13 +73,11 @@ public class Note extends 
AbstractAuditableWithUTCDateTimeCustom<Long> {
     @JoinColumn(name = "share_account_id", nullable = true)
     private ShareAccount shareAccount;
 
-    public static Note clientNoteFromJson(final Client client, final 
JsonCommand command) {
-        final String note = command.stringValueOfParameterNamed("note");
+    public static Note clientNote(final Client client, final String note) {
         return new Note(client, note);
     }
 
-    public static Note groupNoteFromJson(final Group group, final JsonCommand 
command) {
-        final String note = command.stringValueOfParameterNamed("note");
+    public static Note groupNote(final Group group, final String note) {
         return new Note(group, note);
     }
 
@@ -164,20 +161,12 @@ public class Note extends 
AbstractAuditableWithUTCDateTimeCustom<Long> {
         this.noteTypeId = NoteType.SHARE_ACCOUNT.getValue();
     }
 
-    public Map<String, Object> update(final JsonCommand command) {
-        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
-
-        final String noteParamName = "note";
-        if (command.isChangeInStringParameterNamed(noteParamName, this.note)) {
-            final String newValue = 
command.stringValueOfParameterNamed(noteParamName);
-            actualChanges.put(noteParamName, newValue);
-            this.note = StringUtils.defaultIfEmpty(newValue, null);
+    public Map<String, Object> update(final String note) {
+        if (!Strings.CI.equals(note, this.note)) {
+            this.note = StringUtils.defaultIfEmpty(note, null);
+            return Map.of("note", note);
         }
-        return actualChanges;
-    }
-
-    public boolean isNotAgainstClientWithIdOf(final Long clientId) {
-        return !this.client.identifiedBy(clientId);
+        return Map.of();
     }
 
     public String getNote() {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/DeleteNoteCommandHandler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/NoteCreateCommandHandler.java
similarity index 52%
rename from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/DeleteNoteCommandHandler.java
rename to 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/NoteCreateCommandHandler.java
index b6483c928e..d5cd721a9d 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/DeleteNoteCommandHandler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/NoteCreateCommandHandler.java
@@ -18,27 +18,34 @@
  */
 package org.apache.fineract.portfolio.note.handler;
 
-import org.apache.fineract.commands.handler.NewCommandSourceHandler;
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import io.github.resilience4j.retry.annotation.Retry;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.command.core.Command;
+import org.apache.fineract.command.core.CommandHandler;
+import org.apache.fineract.portfolio.note.data.NoteCreateRequest;
+import org.apache.fineract.portfolio.note.data.NoteCreateResponse;
 import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
+import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
-@Service
-public class DeleteNoteCommandHandler implements NewCommandSourceHandler {
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class NoteCreateCommandHandler implements 
CommandHandler<NoteCreateRequest, NoteCreateResponse> {
 
     private final NoteWritePlatformService writePlatformService;
 
-    @Autowired
-    public DeleteNoteCommandHandler(final NoteWritePlatformService 
writePlatformService) {
-        this.writePlatformService = writePlatformService;
+    @Retry(name = "commandNoteCreate", fallbackMethod = "fallback")
+    @Override
+    @Transactional
+    public NoteCreateResponse handle(Command<NoteCreateRequest> command) {
+        return writePlatformService.createNote(command.getPayload());
     }
 
-    @Transactional
     @Override
-    public CommandProcessingResult processCommand(final JsonCommand command) {
-        return this.writePlatformService.deleteNote(command);
+    public NoteCreateResponse fallback(Command<NoteCreateRequest> command, 
Throwable t) {
+        // NOTE: fallback method needs to be in the same class
+        return CommandHandler.super.fallback(command, t);
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/UpdateNoteCommandHandler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/NoteDeleteCommandHandler.java
similarity index 52%
rename from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/UpdateNoteCommandHandler.java
rename to 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/NoteDeleteCommandHandler.java
index 69df2a0fd8..7df6ecfb75 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/UpdateNoteCommandHandler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/NoteDeleteCommandHandler.java
@@ -18,29 +18,34 @@
  */
 package org.apache.fineract.portfolio.note.handler;
 
-import org.apache.fineract.commands.handler.NewCommandSourceHandler;
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import io.github.resilience4j.retry.annotation.Retry;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.command.core.Command;
+import org.apache.fineract.command.core.CommandHandler;
+import org.apache.fineract.portfolio.note.data.NoteDeleteRequest;
+import org.apache.fineract.portfolio.note.data.NoteDeleteResponse;
 import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
+import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
-@Service
-public class UpdateNoteCommandHandler implements NewCommandSourceHandler {
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class NoteDeleteCommandHandler implements 
CommandHandler<NoteDeleteRequest, NoteDeleteResponse> {
 
     private final NoteWritePlatformService writePlatformService;
 
-    @Autowired
-    public UpdateNoteCommandHandler(final NoteWritePlatformService 
noteWritePlatformService) {
-        this.writePlatformService = noteWritePlatformService;
+    @Retry(name = "commandNoteDelete", fallbackMethod = "fallback")
+    @Override
+    @Transactional
+    public NoteDeleteResponse handle(Command<NoteDeleteRequest> command) {
+        return writePlatformService.deleteNote(command.getPayload());
     }
 
-    @Transactional
     @Override
-    public CommandProcessingResult processCommand(final JsonCommand command) {
-
-        return this.writePlatformService.updateNote(command);
+    public NoteDeleteResponse fallback(Command<NoteDeleteRequest> command, 
Throwable t) {
+        // NOTE: fallback method needs to be in the same class
+        return CommandHandler.super.fallback(command, t);
     }
-
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/CreateNoteCommandHandler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/NoteUpdateCommandHandler.java
similarity index 52%
rename from 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/CreateNoteCommandHandler.java
rename to 
fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/NoteUpdateCommandHandler.java
index 351714e564..009f7539b8 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/CreateNoteCommandHandler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/handler/NoteUpdateCommandHandler.java
@@ -18,28 +18,34 @@
  */
 package org.apache.fineract.portfolio.note.handler;
 
-import org.apache.fineract.commands.handler.NewCommandSourceHandler;
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import io.github.resilience4j.retry.annotation.Retry;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.command.core.Command;
+import org.apache.fineract.command.core.CommandHandler;
+import org.apache.fineract.portfolio.note.data.NoteUpdateRequest;
+import org.apache.fineract.portfolio.note.data.NoteUpdateResponse;
 import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
+import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
-@Service
-public class CreateNoteCommandHandler implements NewCommandSourceHandler {
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class NoteUpdateCommandHandler implements 
CommandHandler<NoteUpdateRequest, NoteUpdateResponse> {
 
     private final NoteWritePlatformService writePlatformService;
 
-    @Autowired
-    public CreateNoteCommandHandler(final NoteWritePlatformService 
writePlatformService) {
-        this.writePlatformService = writePlatformService;
+    @Retry(name = "commandNoteUpdate", fallbackMethod = "fallback")
+    @Override
+    @Transactional
+    public NoteUpdateResponse handle(Command<NoteUpdateRequest> command) {
+        return writePlatformService.updateNote(command.getPayload());
     }
 
-    @Transactional
     @Override
-    public CommandProcessingResult processCommand(final JsonCommand command) {
-        return this.writePlatformService.createNote(command);
+    public NoteUpdateResponse fallback(Command<NoteUpdateRequest> command, 
Throwable t) {
+        // NOTE: fallback method needs to be in the same class
+        return CommandHandler.super.fallback(command, t);
     }
-
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/listener/NoteListener.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/listener/NoteListener.java
new file mode 100644
index 0000000000..e3ac4f2eb6
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/listener/NoteListener.java
@@ -0,0 +1,49 @@
+/**
+ * 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.note.listener;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.portfolio.note.data.NoteCreateRequest;
+import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@RequiredArgsConstructor
+@Component
+class NoteListener {
+
+    private final NoteWritePlatformService noteWritePlatformService;
+
+    @EventListener
+    void onCreate(NoteCreateRequest request) {
+        noteWritePlatformService.createNote(request);
+    }
+
+    @EventListener
+    void onUpdate(NoteCreateRequest request) {
+        noteWritePlatformService.createNote(request);
+    }
+
+    @EventListener
+    void onDelete(NoteCreateRequest request) {
+        noteWritePlatformService.createNote(request);
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/serialization/NoteCommandFromApiJsonDeserializer.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/serialization/NoteCommandFromApiJsonDeserializer.java
deleted file mode 100644
index a2c2b9408c..0000000000
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/serialization/NoteCommandFromApiJsonDeserializer.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/**
- * 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.note.serialization;
-
-import com.google.gson.JsonElement;
-import com.google.gson.reflect.TypeToken;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.fineract.infrastructure.core.data.ApiParameterError;
-import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
-import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
-import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
-import 
org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
-import 
org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
-import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
-import org.apache.fineract.portfolio.note.command.NoteCommand;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-/**
- * Implementation of {@link FromApiJsonDeserializer} for {@link NoteCommand} 
's.
- */
-@Component
-public final class NoteCommandFromApiJsonDeserializer extends 
AbstractFromApiJsonDeserializer<NoteCommand> {
-
-    public static final String NOTE = "note";
-    /**
-     * The parameters supported for this command.
-     */
-    private static final Set<String> SUPPORTED_PARAMETERS = new 
HashSet<>(List.of(NOTE));
-
-    private final FromJsonHelper fromApiJsonHelper;
-
-    @Autowired
-    public NoteCommandFromApiJsonDeserializer(final FromJsonHelper 
fromApiJsonHelper) {
-        this.fromApiJsonHelper = fromApiJsonHelper;
-    }
-
-    @Override
-    public NoteCommand commandFromApiJson(final String json) {
-
-        if (StringUtils.isBlank(json)) {
-            throw new InvalidJsonException();
-        }
-
-        final Type typeOfMap = new TypeToken<Map<String, Object>>() {
-
-        }.getType();
-        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, 
SUPPORTED_PARAMETERS);
-
-        final JsonElement element = this.fromApiJsonHelper.parse(json);
-        final String note = this.fromApiJsonHelper.extractStringNamed(NOTE, 
element);
-
-        return new NoteCommand(note);
-    }
-
-    public void validateNote(final String json) {
-        if (StringUtils.isBlank(json)) {
-            throw new InvalidJsonException();
-        }
-
-        final Type typeOfMap = new TypeToken<Map<String, Object>>() {
-
-        }.getType();
-        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, 
SUPPORTED_PARAMETERS);
-        final JsonElement element = this.fromApiJsonHelper.parse(json);
-
-        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
-
-        final DataValidatorBuilder baseDataValidator = new 
DataValidatorBuilder(dataValidationErrors).resource(NOTE);
-
-        final String note = this.fromApiJsonHelper.extractStringNamed(NOTE, 
element);
-        
baseDataValidator.reset().parameter(NOTE).value(note).notBlank().notExceedingLengthOf(1000);
-
-        if (!dataValidationErrors.isEmpty()) {
-            throw new 
PlatformApiDataValidationException("validation.msg.validation.errors.exist", 
"Validation errors exist.",
-                    dataValidationErrors);
-        }
-
-    }
-}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java
index 44500747ef..62158bc44b 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java
@@ -18,30 +18,26 @@
  */
 package org.apache.fineract.portfolio.note.service;
 
-import java.util.Map;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
-import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
-import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.commons.lang3.Strings;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
-import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
-import org.apache.fineract.portfolio.group.domain.Group;
 import org.apache.fineract.portfolio.group.domain.GroupRepository;
 import org.apache.fineract.portfolio.group.exception.GroupNotFoundException;
-import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
+import org.apache.fineract.portfolio.note.data.NoteCreateRequest;
+import org.apache.fineract.portfolio.note.data.NoteCreateResponse;
+import org.apache.fineract.portfolio.note.data.NoteDeleteRequest;
+import org.apache.fineract.portfolio.note.data.NoteDeleteResponse;
+import org.apache.fineract.portfolio.note.data.NoteUpdateRequest;
+import org.apache.fineract.portfolio.note.data.NoteUpdateResponse;
 import org.apache.fineract.portfolio.note.domain.Note;
 import org.apache.fineract.portfolio.note.domain.NoteRepository;
 import org.apache.fineract.portfolio.note.domain.NoteType;
 import org.apache.fineract.portfolio.note.exception.NoteNotFoundException;
 import 
org.apache.fineract.portfolio.note.exception.NoteResourceNotSupportedException;
-import 
org.apache.fineract.portfolio.note.serialization.NoteCommandFromApiJsonDeserializer;
-import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
 import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
 import 
org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
 
@@ -53,419 +49,135 @@ public class NoteWritePlatformServiceJpaRepositoryImpl 
implements NoteWritePlatf
     private final GroupRepository groupRepository;
     private final LoanRepositoryWrapper loanRepository;
     private final LoanTransactionRepository loanTransactionRepository;
-    private final NoteCommandFromApiJsonDeserializer fromApiJsonDeserializer;
     private final SavingsAccountRepository savingsAccountRepository;
 
     public NoteWritePlatformServiceJpaRepositoryImpl(final NoteRepository 
noteRepository, final ClientRepositoryWrapper clientRepository,
             final GroupRepository groupRepository, final LoanRepositoryWrapper 
loanRepository,
-            final LoanTransactionRepository loanTransactionRepository, final 
NoteCommandFromApiJsonDeserializer fromApiJsonDeserializer,
-            final SavingsAccountRepository savingsAccountRepository) {
+            final LoanTransactionRepository loanTransactionRepository, final 
SavingsAccountRepository savingsAccountRepository) {
         this.noteRepository = noteRepository;
         this.clientRepository = clientRepository;
         this.groupRepository = groupRepository;
         this.loanRepository = loanRepository;
         this.loanTransactionRepository = loanTransactionRepository;
-        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
         this.savingsAccountRepository = savingsAccountRepository;
     }
 
-    private CommandProcessingResult createClientNote(final JsonCommand 
command) {
-
-        final Long resourceId = command.getClientId();
-
-        final Client client = 
this.clientRepository.findOneWithNotFoundDetection(resourceId);
-        if (client == null) {
-            throw new ClientNotFoundException(resourceId);
-        }
-        final Note newNote = Note.clientNoteFromJson(client, command);
-
-        this.noteRepository.saveAndFlush(newNote);
-
-        return new CommandProcessingResultBuilder() //
-                .withCommandId(command.commandId()) //
-                .withEntityId(newNote.getId()) //
-                .withClientId(client.getId()) //
-                .withOfficeId(client.officeId()) //
-                .build();
-
-    }
-
     @Override
-    public void createAndPersistClientNote(final Client client, final 
JsonCommand command) {
-        final String noteText = command.stringValueOfParameterNamed("note");
-        if (StringUtils.isNotBlank(noteText)) {
-            final Note newNote = new Note(client, noteText);
-            this.noteRepository.save(newNote);
-        }
-    }
-
-    private CommandProcessingResult createGroupNote(final JsonCommand command) 
{
-
-        final Long resourceId = command.getGroupId();
-
-        final Group group = 
this.groupRepository.findById(resourceId).orElseThrow(() -> new 
GroupNotFoundException(resourceId));
-        final Note newNote = Note.groupNoteFromJson(group, command);
-
-        this.noteRepository.saveAndFlush(newNote);
-
-        return new CommandProcessingResultBuilder() //
-                .withCommandId(command.commandId()) //
-                .withEntityId(newNote.getId()) //
-                .withGroupId(group.getId()) //
-                .withOfficeId(group.officeId()) //
-                .build();
-    }
-
-    private CommandProcessingResult createLoanNote(final JsonCommand command) {
-
-        final Long resourceId = command.getLoanId();
-
-        final Loan loan = 
this.loanRepository.findOneWithNotFoundDetection(resourceId);
-        final String note = command.stringValueOfParameterNamed("note");
-        final Note newNote = Note.loanNote(loan, note);
-
-        this.noteRepository.saveAndFlush(newNote);
-
-        return new CommandProcessingResultBuilder() //
-                .withCommandId(command.commandId()) //
-                .withEntityId(newNote.getId()) //
-                .withOfficeId(loan.getOfficeId()) //
-                .withLoanId(loan.getId()) //
-                .build();
-    }
-
-    private CommandProcessingResult createLoanTransactionNote(final 
JsonCommand command) {
-
-        final Long resourceId = command.subentityId();
-
-        final LoanTransaction loanTransaction = 
this.loanTransactionRepository.findById(resourceId)
-                .orElseThrow(() -> new 
LoanTransactionNotFoundException(resourceId));
-
-        final Loan loan = loanTransaction.getLoan();
-
-        final String note = command.stringValueOfParameterNamed("note");
-        final Note newNote = Note.loanTransactionNote(loan, loanTransaction, 
note);
-
-        this.noteRepository.saveAndFlush(newNote);
-
-        return new CommandProcessingResultBuilder() //
-                .withCommandId(command.commandId()) //
-                .withEntityId(newNote.getId()) //
-                .withOfficeId(loan.getOfficeId())//
-                .withLoanId(loan.getId())// Loan can be associated
-                .build();
-    }
-
-    private CommandProcessingResult createSavingAccountNote(final JsonCommand 
command) {
-        final Long resourceId = command.getSavingsId();
-        final SavingsAccount savingAccount = 
this.savingsAccountRepository.findById(resourceId)
-                .orElseThrow(() -> new 
SavingsAccountNotFoundException(resourceId));
-
-        final String note = command.stringValueOfParameterNamed("note");
-        final Note newNote = Note.savingNote(savingAccount, note);
-
-        this.noteRepository.saveAndFlush(newNote);
+    public NoteCreateResponse createNote(final NoteCreateRequest request) {
+        Note note;
+        Long officeId;
 
-        return new CommandProcessingResultBuilder() //
-                .withCommandId(command.commandId()) //
-                .withEntityId(newNote.getId()) //
-                .withOfficeId(savingAccount.getClient().getOffice().getId()) //
-                .withSavingsId(savingAccount.getId()) //
-                .build();
-    }
-
-    @Override
-    public CommandProcessingResult createNote(final JsonCommand command) {
-
-        this.fromApiJsonDeserializer.validateNote(command.json());
-
-        final String resourceUrl = getResourceUrlFromCommand(command); // 
command.getSupportedEntityType();
-        final NoteType type = NoteType.fromApiUrl(resourceUrl);
-        switch (type) {
+        switch (request.getType()) {
             case CLIENT: {
-                return createClientNote(command);
+                final var client = 
this.clientRepository.findOneWithNotFoundDetection(request.getResourceId());
+                note = noteRepository.saveAndFlush(Note.clientNote(client, 
request.getNote()));
+                officeId = client.officeId();
             }
+            break;
             case GROUP: {
-                return createGroupNote(command);
+                final var group = 
groupRepository.findById(request.getResourceId())
+                        .orElseThrow(() -> new 
GroupNotFoundException(request.getResourceId()));
+                note = noteRepository.saveAndFlush(Note.groupNote(group, 
request.getNote()));
+                officeId = group.officeId();
             }
+            break;
             case LOAN: {
-                return createLoanNote(command);
+                final var loan = 
loanRepository.findOneWithNotFoundDetection(request.getResourceId());
+                note = noteRepository.saveAndFlush(Note.loanNote(loan, 
request.getNote()));
+                officeId = loan.getOfficeId();
             }
+            break;
             case LOAN_TRANSACTION: {
-                return createLoanTransactionNote(command);
+                final var loanTransaction = 
this.loanTransactionRepository.findById(request.getResourceId())
+                        .orElseThrow(() -> new 
LoanTransactionNotFoundException(request.getResourceId()));
+                note = 
noteRepository.saveAndFlush(Note.loanTransactionNote(loanTransaction.getLoan(), 
loanTransaction, request.getNote()));
+                officeId = loanTransaction.getLoan().getOfficeId();
             }
+            break;
             case SAVING_ACCOUNT: {
-                return createSavingAccountNote(command);
+                final var savingAccount = 
savingsAccountRepository.findById(request.getResourceId())
+                        .orElseThrow(() -> new 
SavingsAccountNotFoundException(request.getResourceId()));
+                note = 
noteRepository.saveAndFlush(Note.savingNote(savingAccount, request.getNote()));
+                officeId = savingAccount.getClient().getOffice().getId();
             }
+            break;
             default:
-                throw new NoteResourceNotSupportedException(resourceUrl);
+                throw new 
NoteResourceNotSupportedException(request.getType().getApiUrl());
         }
 
+        return 
NoteCreateResponse.builder().entityId(note.getId()).resourceId(note.getId()).officeId(officeId).build();
     }
 
     @Override
-    public void createLoanTransactionNote(final Long loanTransactionId, final 
String note) {
-        final LoanTransaction loanTransaction = 
this.loanTransactionRepository.findById(loanTransactionId)
-                .orElseThrow(() -> new 
LoanTransactionNotFoundException(loanTransactionId));
-
-        final Loan loan = loanTransaction.getLoan();
-
-        final Note newNote = Note.loanTransactionNote(loan, loanTransaction, 
note);
-
-        this.noteRepository.save(newNote);
-    }
-
-    private String getResourceUrlFromCommand(JsonCommand command) {
-
-        final String resourceUrl;
-
-        if (command.getClientId() != null) {
-            resourceUrl = NoteType.CLIENT.getApiUrl();
-        } else if (command.getGroupId() != null) {
-            resourceUrl = NoteType.GROUP.getApiUrl();
-        } else if (command.getLoanId() != null) {
-            if (command.subentityId() != null) {
-                resourceUrl = NoteType.LOAN_TRANSACTION.getApiUrl();
-            } else {
-                resourceUrl = NoteType.LOAN.getApiUrl();
-            }
-        } else if (command.getSavingsId() != null) {
-            // TODO: SAVING_TRANSACTION type need to be add.
-            resourceUrl = NoteType.SAVING_ACCOUNT.getApiUrl();
-        } else {
-            resourceUrl = "";
-        }
-
-        return resourceUrl;
-    }
-
-    private CommandProcessingResult updateClientNote(final JsonCommand 
command) {
-
-        final Long resourceId = command.getClientId();
-        final Long noteId = command.entityId();
-
-        final NoteType type = NoteType.CLIENT;
-
-        final Client client = 
this.clientRepository.findOneWithNotFoundDetection(resourceId);
-
-        final Note noteForUpdate = 
this.noteRepository.findByClientAndId(client, noteId);
-        if (noteForUpdate == null) {
-            throw new NoteNotFoundException(noteId, resourceId, 
type.name().toLowerCase());
-        }
-
-        final Map<String, Object> changes = noteForUpdate.update(command);
-
-        if (!changes.isEmpty()) {
-            this.noteRepository.saveAndFlush(noteForUpdate);
-        }
+    public NoteUpdateResponse updateNote(final NoteUpdateRequest request) {
+        final var result = getNote(request.getType(), request.getResourceId(), 
request.getId());
+        final var note = result.getLeft();
+        final var response = 
NoteUpdateResponse.builder().officeId(result.getRight()).resourceId(request.getResourceId());
 
-        return new CommandProcessingResultBuilder() //
-                .withCommandId(command.commandId()) //
-                .withEntityId(noteForUpdate.getId()) //
-                .withClientId(client.getId()) //
-                .withOfficeId(client.officeId()) //
-                .with(changes) //
-                .build();
-    }
-
-    private CommandProcessingResult updateGroupNote(final JsonCommand command) 
{
-
-        final Long resourceId = command.getGroupId();
-        final Long noteId = command.entityId();
-
-        final NoteType type = NoteType.GROUP;
-
-        final Group group = 
this.groupRepository.findById(resourceId).orElseThrow(() -> new 
GroupNotFoundException(resourceId));
-
-        final Note noteForUpdate = this.noteRepository.findByGroupAndId(group, 
noteId);
-
-        if (noteForUpdate == null) {
-            throw new NoteNotFoundException(noteId, resourceId, 
type.name().toLowerCase());
+        if (!Strings.CI.equals(note.getNote(), request.getNote())) {
+            response.changes(note.update(request.getNote()));
+            noteRepository.saveAndFlush(note);
         }
 
-        final Map<String, Object> changes = noteForUpdate.update(command);
-
-        if (!changes.isEmpty()) {
-            this.noteRepository.saveAndFlush(noteForUpdate);
-        }
-
-        return new CommandProcessingResultBuilder() //
-                .withCommandId(command.commandId()) //
-                .withEntityId(noteForUpdate.getId()) //
-                .withGroupId(group.getId()) //
-                .withOfficeId(group.officeId()) //
-                .with(changes).build();
-    }
-
-    private CommandProcessingResult updateLoanNote(final JsonCommand command) {
-
-        final Long resourceId = command.getLoanId();
-        final Long noteId = command.entityId();
-
-        final NoteType type = NoteType.LOAN;
-
-        final Loan loan = 
this.loanRepository.findOneWithNotFoundDetection(resourceId);
-        final Note noteForUpdate = this.noteRepository.findByLoanAndId(loan, 
noteId);
-        if (noteForUpdate == null) {
-            throw new NoteNotFoundException(noteId, resourceId, 
type.name().toLowerCase());
-        }
-
-        final Map<String, Object> changes = noteForUpdate.update(command);
-
-        if (!changes.isEmpty()) {
-            this.noteRepository.saveAndFlush(noteForUpdate);
-        }
-
-        return new 
CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(noteForUpdate.getId())
-                
.withLoanId(loan.getId()).withOfficeId(loan.getOfficeId()).with(changes).build();
-    }
-
-    private CommandProcessingResult updateLoanTransactionNote(final 
JsonCommand command) {
-
-        final Long resourceId = command.subentityId();
-        final Long noteId = command.entityId();
-
-        final NoteType type = NoteType.LOAN_TRANSACTION;
-
-        final LoanTransaction loanTransaction = 
this.loanTransactionRepository.findById(resourceId)
-                .orElseThrow(() -> new 
LoanTransactionNotFoundException(resourceId));
-        final Loan loan = loanTransaction.getLoan();
-
-        final Note noteForUpdate = 
this.noteRepository.findByLoanTransactionAndId(loanTransaction, noteId);
-
-        if (noteForUpdate == null) {
-            throw new NoteNotFoundException(noteId, resourceId, 
type.name().toLowerCase());
-        }
-
-        final Map<String, Object> changes = noteForUpdate.update(command);
-
-        if (!changes.isEmpty()) {
-            this.noteRepository.saveAndFlush(noteForUpdate);
-        }
-
-        return new 
CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(noteForUpdate.getId())
-                
.withLoanId(loan.getId()).withOfficeId(loan.getOfficeId()).with(changes).build();
-    }
-
-    private CommandProcessingResult updateSavingAccountNote(final JsonCommand 
command) {
-        final Long resourceId = command.getSavingsId();
-        final Long noteId = command.entityId();
-        final NoteType type = NoteType.SAVING_ACCOUNT;
-        final SavingsAccount savingAccount = 
this.savingsAccountRepository.findById(resourceId)
-                .orElseThrow(() -> new 
SavingsAccountNotFoundException(resourceId));
-
-        final Note noteForUpdate = 
this.noteRepository.findBySavingsAccountAndId(savingAccount, noteId);
-        if (noteForUpdate == null) {
-            throw new NoteNotFoundException(noteId, resourceId, 
type.name().toLowerCase());
-        }
-        final Map<String, Object> changes = noteForUpdate.update(command);
-        if (!changes.isEmpty()) {
-            this.noteRepository.saveAndFlush(noteForUpdate);
-        }
-
-        return new CommandProcessingResultBuilder() //
-                .withCommandId(command.commandId()) //
-                .withEntityId(noteForUpdate.getId()) //
-                .withOfficeId(savingAccount.getClient().getOffice().getId()) //
-                .withSavingsId(savingAccount.getId()) //
-                .with(changes) //
-                .build();
+        return response.build();
     }
 
     @Override
-    public CommandProcessingResult updateNote(final JsonCommand command) {
-
-        this.fromApiJsonDeserializer.validateNote(command.json());
+    public NoteDeleteResponse deleteNote(final NoteDeleteRequest request) {
+        var note = getNote(request.getType(), request.getResourceId(), 
request.getId());
 
-        final String resourceUrl = getResourceUrlFromCommand(command); // 
command.getSupportedEntityType();
-        final NoteType type = NoteType.fromApiUrl(resourceUrl);
+        noteRepository.delete(note.getLeft());
 
-        switch (type) {
-            case CLIENT: {
-                return updateClientNote(command);
-            }
-            case GROUP: {
-                return updateGroupNote(command);
-            }
-            case LOAN: {
-                return updateLoanNote(command);
-            }
-            case LOAN_TRANSACTION: {
-                return updateLoanTransactionNote(command);
-            }
-            case SAVING_ACCOUNT: {
-                return updateSavingAccountNote(command);
-            }
-            default:
-                throw new NoteResourceNotSupportedException(resourceUrl);
-        }
+        return 
NoteDeleteResponse.builder().resourceId(request.getId()).build();
     }
 
-    @Override
-    public CommandProcessingResult deleteNote(final JsonCommand command) {
-
-        final Note noteForDelete = getNoteForDelete(command);
-
-        this.noteRepository.delete(noteForDelete);
-        return new CommandProcessingResultBuilder() //
-                .withCommandId(null) //
-                .withEntityId(command.entityId()) //
-                .build();
-    }
+    private Pair<Note, Long> getNote(NoteType type, Long resourceId, Long 
noteId) {
+        Note note = null;
+        Long officeId = null;
 
-    private Note getNoteForDelete(final JsonCommand command) {
-        final String resourceUrl = getResourceUrlFromCommand(command);// 
command.getSupportedEntityType();
-        final Long noteId = command.entityId();
-        final NoteType type = NoteType.fromApiUrl(resourceUrl);
-        Long resourceId = null;
-        Note noteForUpdate = null;
         switch (type) {
             case CLIENT: {
-                resourceId = command.getClientId();
-                final Client client = 
this.clientRepository.findOneWithNotFoundDetection(resourceId);
-                noteForUpdate = this.noteRepository.findByClientAndId(client, 
noteId);
+                final var client = 
clientRepository.findOneWithNotFoundDetection(resourceId);
+                note = noteRepository.findByClientAndId(client, noteId);
+                officeId = client.officeId();
             }
             break;
             case GROUP: {
-                final Long groupId = command.getGroupId();
-                resourceId = groupId;
-                Group group = 
this.groupRepository.findById(groupId).orElseThrow(() -> new 
GroupNotFoundException(groupId));
-                noteForUpdate = this.noteRepository.findByGroupAndId(group, 
noteId);
+                final var group = 
groupRepository.findById(resourceId).orElseThrow(() -> new 
GroupNotFoundException(resourceId));
+                note = noteRepository.findByGroupAndId(group, noteId);
+                officeId = group.officeId();
             }
             break;
             case LOAN: {
-                resourceId = command.getLoanId();
-                final Loan loan = 
this.loanRepository.findOneWithNotFoundDetection(resourceId);
-                noteForUpdate = this.noteRepository.findByLoanAndId(loan, 
noteId);
+                final var loan = 
loanRepository.findOneWithNotFoundDetection(resourceId);
+                note = noteRepository.findByLoanAndId(loan, noteId);
+                officeId = loan.getOfficeId();
             }
             break;
             case LOAN_TRANSACTION: {
-                resourceId = command.subentityId();
-                final Long loanTransactionId = resourceId;
-                final LoanTransaction loanTransaction = 
this.loanTransactionRepository.findById(loanTransactionId)
-                        .orElseThrow(() -> new 
LoanTransactionNotFoundException(loanTransactionId));
-                noteForUpdate = 
this.noteRepository.findByLoanTransactionAndId(loanTransaction, noteId);
+                final var loanTransaction = 
loanTransactionRepository.findById(resourceId)
+                        .orElseThrow(() -> new 
LoanTransactionNotFoundException(resourceId));
+                note = 
noteRepository.findByLoanTransactionAndId(loanTransaction, noteId);
+                officeId = loanTransaction.getLoan().getOfficeId();
             }
             break;
             case SAVING_ACCOUNT: {
-                final Long savinsAccountId = command.getSavingsId();
-                final SavingsAccount savingAccount = 
this.savingsAccountRepository.findById(savinsAccountId)
-                        .orElseThrow(() -> new 
SavingsAccountNotFoundException(savinsAccountId));
-
-                noteForUpdate = 
this.noteRepository.findBySavingsAccountAndId(savingAccount, noteId);
+                final var savingAccount = 
savingsAccountRepository.findById(resourceId)
+                        .orElseThrow(() -> new 
SavingsAccountNotFoundException(resourceId));
+                note = noteRepository.findBySavingsAccountAndId(savingAccount, 
noteId);
+                officeId = savingAccount.getClient().getOffice().getId();
             }
             break;
             case SHARE_ACCOUNT:
-                log.error("TODO Implement getNoteForDelete for SHARE_ACCOUNT");
-            break;
             case SAVINGS_TRANSACTION:
-                log.error("TODO Implement getNoteForDelete for 
SAVINGS_TRANSACTION");
+                log.error("Not yet implemented: {}", type);
             break;
         }
-        if (noteForUpdate == null) {
+
+        if (note == null) {
             throw new NoteNotFoundException(noteId, resourceId, 
type.name().toLowerCase());
         }
-        return noteForUpdate;
-    }
 
+        return Pair.of(note, officeId);
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/starter/NoteAutoConfiguration.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/starter/NoteAutoConfiguration.java
index 8fab6f156e..c61fd8f8de 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/starter/NoteAutoConfiguration.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/starter/NoteAutoConfiguration.java
@@ -23,7 +23,6 @@ import 
org.apache.fineract.portfolio.group.domain.GroupRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.note.domain.NoteRepository;
-import 
org.apache.fineract.portfolio.note.serialization.NoteCommandFromApiJsonDeserializer;
 import org.apache.fineract.portfolio.note.service.NoteReadPlatformService;
 import org.apache.fineract.portfolio.note.service.NoteReadPlatformServiceImpl;
 import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
@@ -47,8 +46,8 @@ public class NoteAutoConfiguration {
     @ConditionalOnMissingBean
     public NoteWritePlatformService noteWritePlatformService(NoteRepository 
noteRepository, ClientRepositoryWrapper clientRepository,
             GroupRepository groupRepository, LoanRepositoryWrapper 
loanRepository, LoanTransactionRepository loanTransactionRepository,
-            NoteCommandFromApiJsonDeserializer fromApiJsonDeserializer, 
SavingsAccountRepository savingsAccountRepository) {
+            SavingsAccountRepository savingsAccountRepository) {
         return new NoteWritePlatformServiceJpaRepositoryImpl(noteRepository, 
clientRepository, groupRepository, loanRepository,
-                loanTransactionRepository, fromApiJsonDeserializer, 
savingsAccountRepository);
+                loanTransactionRepository, savingsAccountRepository);
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java
index cdd191aebc..c697bbd238 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java
@@ -57,7 +57,8 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
 import org.apache.fineract.portfolio.loanaccount.service.LoanOfficerService;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
-import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
+import org.apache.fineract.portfolio.note.data.NoteCreateRequest;
+import org.apache.fineract.portfolio.note.domain.NoteType;
 import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
 import 
org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
 import 
org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
@@ -67,6 +68,7 @@ import 
org.apache.fineract.portfolio.transfer.exception.ClientNotAwaitingTransfe
 import 
org.apache.fineract.portfolio.transfer.exception.ClientNotAwaitingTransferApprovalOrOnHoldException;
 import 
org.apache.fineract.portfolio.transfer.exception.TransferNotSupportedException;
 import 
org.apache.fineract.portfolio.transfer.exception.TransferNotSupportedException.TransferNotSupportedReason;
+import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.transaction.annotation.Transactional;
 
 @RequiredArgsConstructor
@@ -81,11 +83,11 @@ public class TransferWritePlatformServiceJpaRepositoryImpl 
implements TransferWr
     private final LoanRepositoryWrapper loanRepositoryWrapper;
     private final SavingsAccountRepositoryWrapper 
savingsAccountRepositoryWrapper;
     private final TransfersDataValidator transfersDataValidator;
-    private final NoteWritePlatformService noteWritePlatformService;
     private final StaffRepositoryWrapper staffRepositoryWrapper;
     private final ClientTransferDetailsRepositoryWrapper 
clientTransferDetailsRepositoryWrapper;
     private final PlatformSecurityContext context;
     private final LoanOfficerService loanOfficerService;
+    private final ApplicationEventPublisher eventPublisher;
 
     @Override
     @Transactional
@@ -466,7 +468,8 @@ public class TransferWritePlatformServiceJpaRepositoryImpl 
implements TransferWr
                 client.updateProposedTransferDate(null);
         }
 
-        this.noteWritePlatformService.createAndPersistClientNote(client, 
jsonCommand);
+        
this.eventPublisher.publishEvent(NoteCreateRequest.builder().type(NoteType.CLIENT).resourceId(client.getId())
+                
.note(jsonCommand.stringValueOfParameterNamed("note")).build());
         this.clientTransferDetailsRepositoryWrapper
                 .save(ClientTransferDetails.instance(client.getId(), 
client.getOffice().getId(), destinationOffice.getId(), transferDate,
                         transferEventType.getValue(), 
DateUtils.getBusinessLocalDate(), this.context.authenticatedUser().getId()));
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/starter/TransferConfiguration.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/starter/TransferConfiguration.java
index 9a0c666dc5..f516b0be40 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/starter/TransferConfiguration.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/transfer/starter/TransferConfiguration.java
@@ -28,13 +28,13 @@ import 
org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
 import org.apache.fineract.portfolio.loanaccount.service.LoanOfficerService;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
-import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
 import 
org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
 import 
org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
 import org.apache.fineract.portfolio.transfer.data.TransfersDataValidator;
 import 
org.apache.fineract.portfolio.transfer.service.TransferWritePlatformService;
 import 
org.apache.fineract.portfolio.transfer.service.TransferWritePlatformServiceJpaRepositoryImpl;
 import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -47,14 +47,13 @@ public class TransferConfiguration {
             OfficeRepositoryWrapper officeRepository, 
CalendarInstanceRepository calendarInstanceRepository,
             LoanWritePlatformService loanWritePlatformService, 
GroupRepositoryWrapper groupRepository,
             LoanRepositoryWrapper loanRepositoryWrapper, 
TransfersDataValidator transfersDataValidator,
-            NoteWritePlatformService noteWritePlatformService, 
StaffRepositoryWrapper staffRepositoryWrapper,
-            SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper,
+            StaffRepositoryWrapper staffRepositoryWrapper, 
SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper,
             SavingsAccountWritePlatformService 
savingsAccountWritePlatformService,
             ClientTransferDetailsRepositoryWrapper 
clientTransferDetailsRepositoryWrapper, PlatformSecurityContext context,
-            LoanOfficerService loanOfficerService) {
+            LoanOfficerService loanOfficerService, ApplicationEventPublisher 
eventPublisher) {
         return new 
TransferWritePlatformServiceJpaRepositoryImpl(clientRepositoryWrapper, 
officeRepository, calendarInstanceRepository,
                 groupRepository, loanWritePlatformService, 
savingsAccountWritePlatformService, loanRepositoryWrapper,
-                savingsAccountRepositoryWrapper, transfersDataValidator, 
noteWritePlatformService, staffRepositoryWrapper,
-                clientTransferDetailsRepositoryWrapper, context, 
loanOfficerService);
+                savingsAccountRepositoryWrapper, transfersDataValidator, 
staffRepositoryWrapper, clientTransferDetailsRepositoryWrapper,
+                context, loanOfficerService, eventPublisher);
     }
 }
diff --git a/fineract-provider/src/main/resources/application.properties 
b/fineract-provider/src/main/resources/application.properties
index b3d2a86aeb..93c249625c 100644
--- a/fineract-provider/src/main/resources/application.properties
+++ b/fineract-provider/src/main/resources/application.properties
@@ -487,30 +487,58 @@ 
resilience4j.retry.instances.postInterest.enable-exponential-backoff=${FINERACT_
 
resilience4j.retry.instances.postInterest.exponential-backoff-multiplier=${FINERACT_PROCESS_POST_INTEREST_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
 
resilience4j.retry.instances.postInterest.retryExceptions=${FINERACT_PROCESS_POST_INTEREST_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
 
+# business date
+
 
resilience4j.retry.instances.commandBusinessDateUpdate.max-attempts=${FINERACT_PROCESS_COMMAND_BUSINESS_DATE_UPDATE_RETRY_MAX_ATTEMPTS:3}
 
resilience4j.retry.instances.commandBusinessDateUpdate.wait-duration=${FINERACT_PROCESS_COMMAND_BUSINESS_DATE_UPDATE_RETRY_WAIT_DURATION:1s}
 
resilience4j.retry.instances.commandBusinessDateUpdate.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_BUSINESS_DATE_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
 
resilience4j.retry.instances.commandBusinessDateUpdate.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_BUSINESS_DATE_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
 
resilience4j.retry.instances.commandBusinessDateUpdate.retryExceptions=${FINERACT_PROCESS_COMMAND_BUSINESS_DATE_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
 
+# cache
+
 
resilience4j.retry.instances.commandCacheSwitch.max-attempts=${FINERACT_PROCESS_COMMAND_CACHE_SWITCH_RETRY_MAX_ATTEMPTS:3}
 
resilience4j.retry.instances.commandCacheSwitch.wait-duration=${FINERACT_PROCESS_COMMAND_CACHE_SWITCH_RETRY_WAIT_DURATION:1s}
 
resilience4j.retry.instances.commandCacheSwitch.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_CACHE_SWITCH_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
 
resilience4j.retry.instances.commandCacheSwitch.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_CACHE_SWITCH_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
 
resilience4j.retry.instances.commandCacheSwitch.retryExceptions=${FINERACT_PROCESS_COMMAND_CACHE_SWITCH_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
 
+# currency
+
 
resilience4j.retry.instances.commandCurrencyUpdate.max-attempts=${FINERACT_PROCESS_COMMAND_CURRENCY_UPDATE_RETRY_MAX_ATTEMPTS:3}
 
resilience4j.retry.instances.commandCurrencyUpdate.wait-duration=${FINERACT_PROCESS_COMMAND_CURRENCY_UPDATE_RETRY_WAIT_DURATION:1s}
 
resilience4j.retry.instances.commandCurrencyUpdate.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_CURRENCY_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
 
resilience4j.retry.instances.commandCurrencyUpdate.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_CURRENCY_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
 
resilience4j.retry.instances.commandCurrencyUpdate.retryExceptions=${FINERACT_PROCESS_COMMAND_CURRENCY_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
 
+# external event configuration
+
 
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.max-attempts=${FINERACT_PROCESS_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_MAX_ATTEMPTS:3}
 
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.wait-duration=${FINERACT_PROCESS_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_WAIT_DURATION:1s}
 
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
 
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
 
resilience4j.retry.instances.commandExternalEventConfigurationUpdate.retryExceptions=${FINERACT_PROCESS_COMMAND_EXTERNAL_EVENT_CONFIGURATION_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
 
+# note
+
+resilience4j.retry.instances.commandNoteCreate.max-attempts=${FINERACT_PROCESS_COMMAND_NOTE_CREATE_RETRY_MAX_ATTEMPTS:3}
+resilience4j.retry.instances.commandNoteCreate.wait-duration=${FINERACT_PROCESS_COMMAND_NOTE_CREATE_RETRY_WAIT_DURATION:1s}
+resilience4j.retry.instances.commandNoteCreate.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_NOTE_CREATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
+resilience4j.retry.instances.commandNoteCreate.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_NOTE_CREATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
+resilience4j.retry.instances.commandNoteCreate.retryExceptions=${FINERACT_PROCESS_COMMAND_NOTE_CREATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
+
+resilience4j.retry.instances.commandNoteUpdate.max-attempts=${FINERACT_PROCESS_COMMAND_NOTE_UPDATE_RETRY_MAX_ATTEMPTS:3}
+resilience4j.retry.instances.commandNoteUpdate.wait-duration=${FINERACT_PROCESS_COMMAND_NOTE_UPDATE_RETRY_WAIT_DURATION:1s}
+resilience4j.retry.instances.commandNoteUpdate.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_NOTE_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
+resilience4j.retry.instances.commandNoteUpdate.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_NOTE_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
+resilience4j.retry.instances.commandNoteUpdate.retryExceptions=${FINERACT_PROCESS_COMMAND_NOTE_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
+
+resilience4j.retry.instances.commandNoteDelete.max-attempts=${FINERACT_PROCESS_COMMAND_NOTE_DELETE_RETRY_MAX_ATTEMPTS:3}
+resilience4j.retry.instances.commandNoteDelete.wait-duration=${FINERACT_PROCESS_COMMAND_NOTE_DELETE_RETRY_WAIT_DURATION:1s}
+resilience4j.retry.instances.commandNoteDelete.enable-exponential-backoff=${FINERACT_PROCESS_COMMAND_NOTE_DELETE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true}
+resilience4j.retry.instances.commandNoteDelete.exponential-backoff-multiplier=${FINERACT_PROCESS_COMMAND_NOTE_DELETE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2}
+resilience4j.retry.instances.commandNoteDelete.retryExceptions=${FINERACT_PROCESS_COMMAND_NOTE_DELETE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException}
+
 fineract.command.enabled=true
 fineract.command.executor=${FINERACT_COMMAND_EXECUTOR:sync}
 fineract.command.ring-buffer-size=${FINERACT_COMMAND_RING_BUFFER_SIZE:1024}
@@ -518,6 +546,10 @@ 
fineract.command.producer-type=${FINERACT_COMMAND_PRODUCER_TYPE:single}
 fineract.command.auditable=${FINERACT_COMMAND_AUDITABLE:true}
 # this is duplicated on purpose to keep fineract-command independent; once all 
modules are migrated we clean up
 
fineract.command.idempotency-key-header-name=${FINERACT_IDEMPOTENCY_KEY_HEADER_NAME:Idempotency-Key}
+fineract.command.file-dead-letter-queue-enabled=${FINERACT_FILE_DEAD_LETTER_QUEUE_ENABLED:false}
+fineract.command.file-dead-letter-queue-path=${FINERACT_FILE_DEAD_LETTER_QUEUE_PATH:/tmp/fineract-command-audit}
+
+# command
 
 
resilience4j.retry.instances.commandAuditProcessing.max-attempts=${FINERACT_PROCESS_COMMAND_AUDIT_PROCESSING_RETRY_MAX_ATTEMPTS:3}
 
resilience4j.retry.instances.commandAuditProcessing.wait-duration=${FINERACT_PROCESS_COMMAND_AUDIT_PROCESSING_RETRY_WAIT_DURATION:1s}
diff --git 
a/fineract-validation/src/main/resources/ValidationMessages.properties 
b/fineract-validation/src/main/resources/ValidationMessages.properties
index 40cbafc564..1320656623 100644
--- a/fineract-validation/src/main/resources/ValidationMessages.properties
+++ b/fineract-validation/src/main/resources/ValidationMessages.properties
@@ -53,3 +53,8 @@ org.apache.fineract.reage.start-date.not-blank=The parameter 
'startDate' is mand
 org.apache.fineract.reage.number-of-installments.not-blank=The parameter 
'numberOfInstallments' is mandatory.
 org.apache.fineract.reage.number-of-installments.min=The parameter 
'numberOfInstallments' must be at least 1.
 org.apache.fineract.frequency-type.invalid=The parameter 'frequencyType' must 
be valid PeriodFrequencyType value. Provided value: '${validatedValue}'.
+
+# Note
+
+org.apache.fineract.portfolio.note.note.size=The parameter 'note' cannot 
exceed 1000 characters
+org.apache.fineract.portfolio.note.note.not-null=The parameter 'note' cannot 
be empty
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/NotesTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/NotesTest.java
index 8f81fa897f..a3a499dd3a 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/NotesTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/NotesTest.java
@@ -28,8 +28,6 @@ import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
-import org.apache.fineract.client.models.NoteData;
-import 
org.apache.fineract.client.models.PostResourceTypeResourceIdNotesResponse;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
 import org.apache.fineract.integrationtests.common.GroupHelper;
@@ -41,6 +39,8 @@ import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtens
 import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
 import 
org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
 import 
org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.portfolio.note.data.NoteCreateResponse;
+import org.apache.fineract.portfolio.note.data.NoteData;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -217,8 +217,7 @@ public class NotesTest {
 
         // Notes
         final String payload = "{\"note\": \"" + noteText + "\"}";
-        final PostResourceTypeResourceIdNotesResponse postNoteResponse = 
NotesHelper.createSavingsNote(requestSpec, responseSpec, savingsId,
-                payload);
+        final NoteCreateResponse postNoteResponse = 
NotesHelper.createSavingsNote(requestSpec, responseSpec, savingsId, payload);
         Assertions.assertNotNull(postNoteResponse);
         Assertions.assertNotNull(postNoteResponse.getResourceId());
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/NotesHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/NotesHelper.java
index bc3af6174a..21efd202eb 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/NotesHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/NotesHelper.java
@@ -21,9 +21,9 @@ package org.apache.fineract.integrationtests.common;
 import com.google.gson.Gson;
 import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
-import org.apache.fineract.client.models.NoteData;
-import 
org.apache.fineract.client.models.PostResourceTypeResourceIdNotesResponse;
 import org.apache.fineract.client.util.JSON;
+import org.apache.fineract.portfolio.note.data.NoteCreateResponse;
+import org.apache.fineract.portfolio.note.data.NoteData;
 
 @SuppressWarnings({ "rawtypes", "unchecked" })
 public final class NotesHelper {
@@ -210,11 +210,11 @@ public final class NotesHelper {
     // Example: 
org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
     // org.apache.fineract.client.models.PostLoansLoanIdRequest)
     @Deprecated(forRemoval = true)
-    public static PostResourceTypeResourceIdNotesResponse 
createSavingsNote(RequestSpecification requestSpec,
-            ResponseSpecification responseSpec, Integer savingsId, String 
request) {
+    public static NoteCreateResponse createSavingsNote(RequestSpecification 
requestSpec, ResponseSpecification responseSpec,
+            Integer savingsId, String request) {
         final String noteURL = SAVINGS_URL + "/" + savingsId + "/notes?" + 
Utils.TENANT_IDENTIFIER;
         final String response = Utils.performServerPost(requestSpec, 
responseSpec, noteURL, request);
-        return GSON.fromJson(response, 
PostResourceTypeResourceIdNotesResponse.class);
+        return GSON.fromJson(response, NoteCreateResponse.class);
     }
 
     public static NoteData retrieveSavingsNote(RequestSpecification 
requestSpec, ResponseSpecification responseSpec, Integer savingsId,

Reply via email to