diff --git a/config/docker/compose/fineract.yml b/config/docker/compose/fineract.yml index ae5323c41..801b73fc6 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 b77bb24b0..4b0dc2465 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 --add-opens=java.naming/javax.naming=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.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED" 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 14b194f20..ad15721ba 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 8a2395af1..1706c8c4c 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 10f8d6cce..cc89614e0 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 f37fb6f2c..89723ea21 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 771b6d553..6d6eaea5f 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-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteCreateRequest.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteCreateRequest.java new file mode 100644 index 000000000..b5e7edfed --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteCreateRequest.java @@ -0,0 +1,48 @@ +/** + * 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.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 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-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteCreateResponse.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteCreateResponse.java new file mode 100644 index 000000000..403a50a6d --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteCreateResponse.java @@ -0,0 +1,40 @@ +/** + * 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.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 NoteCreateResponse implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + 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 091e76792..6f998732a 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 { - 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; + @Serial + private static final long serialVersionUID = 1L; + + 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-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteDeleteRequest.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteDeleteRequest.java new file mode 100644 index 000000000..6ef104958 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteDeleteRequest.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.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 NoteDeleteRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @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% 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/NoteDeleteResponse.java index 05a108208..cfae896ab 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-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteUpdateRequest.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteUpdateRequest.java new file mode 100644 index 000000000..bca0cec30 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteUpdateRequest.java @@ -0,0 +1,50 @@ +/** + * 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.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 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-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteUpdateResponse.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteUpdateResponse.java new file mode 100644 index 000000000..5ddf70056 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/data/NoteUpdateResponse.java @@ -0,0 +1,41 @@ +/** + * 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.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 NoteUpdateResponse implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long officeId; + private Long resourceId; + private Map 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 1d8afbd1f..6db5a9fc6 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 213265bd0..414c6e2ec 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 896c54c6d..2c804898c 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 6b7a543b0..936e0f8e1 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 c8849f003..438923e6f 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 5a0e5f013..3db392034 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 849997c0c..f18db73c5 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 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 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 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 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); - return commandsSourceWritePlatformService.logCommandSource(commandRequest); + final var command = new NoteCreateCommand(); + + command.setPayload(request); + + final Supplier response = commandPipeline.send(command); + + 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) { + @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); - final NoteType noteType = NoteType.fromApiUrl(resourceType); - - if (noteType == null) { + 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 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) { + @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); - final NoteType noteType = NoteType.fromApiUrl(resourceType); - - if (noteType == null) { + 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 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 e53cd454f..000000000 --- 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% 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/NoteCreateCommand.java index 6f2419d11..220610791 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 { +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.portfolio.note.data.NoteCreateRequest; - @SuppressWarnings("unused") - private final String note; - - public NoteCommand(final String note) { - this.note = note; - } - -} +@Data +@EqualsAndHashCode(callSuper = true) +public class NoteCreateCommand extends Command {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteDeleteCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteDeleteCommand.java new file mode 100644 index 000000000..ba97ea2f6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteDeleteCommand.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.note.command; + +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 {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteUpdateCommand.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteUpdateCommand.java new file mode 100644 index 000000000..2671bb2f9 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/command/NoteUpdateCommand.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.note.command; + +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 {} 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 ecc28837f..db7151717 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 { @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 { this.noteTypeId = NoteType.SHARE_ACCOUNT.getValue(); } - public Map update(final JsonCommand command) { - final Map 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 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 b6483c928..d5cd721a9 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 { private final NoteWritePlatformService writePlatformService; - @Autowired - public DeleteNoteCommandHandler(final NoteWritePlatformService writePlatformService) { - this.writePlatformService = writePlatformService; + @Retry(name = "commandNoteCreate", fallbackMethod = "fallback") + @Override + @Transactional + public NoteCreateResponse handle(Command command) { + return writePlatformService.createNote(command.getPayload()); } - @Transactional @Override - public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.deleteNote(command); + public NoteCreateResponse fallback(Command 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 69df2a0fd..7df6ecfb7 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 { private final NoteWritePlatformService writePlatformService; - @Autowired - public UpdateNoteCommandHandler(final NoteWritePlatformService noteWritePlatformService) { - this.writePlatformService = noteWritePlatformService; - } - - @Transactional + @Retry(name = "commandNoteDelete", fallbackMethod = "fallback") @Override - public CommandProcessingResult processCommand(final JsonCommand command) { - - return this.writePlatformService.updateNote(command); + @Transactional + public NoteDeleteResponse handle(Command command) { + return writePlatformService.deleteNote(command.getPayload()); } + @Override + public NoteDeleteResponse fallback(Command 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 351714e56..009f7539b 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 { private final NoteWritePlatformService writePlatformService; - @Autowired - public CreateNoteCommandHandler(final NoteWritePlatformService writePlatformService) { - this.writePlatformService = writePlatformService; - } - - @Transactional + @Retry(name = "commandNoteUpdate", fallbackMethod = "fallback") @Override - public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.createNote(command); + @Transactional + public NoteUpdateResponse handle(Command command) { + return writePlatformService.updateNote(command.getPayload()); } + @Override + public NoteUpdateResponse fallback(Command 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 000000000..e3ac4f2eb --- /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 a2c2b9408..000000000 --- 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 { - - public static final String NOTE = "note"; - /** - * The parameters supported for this command. - */ - private static final Set 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>() { - - }.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>() { - - }.getType(); - this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS); - final JsonElement element = this.fromApiJsonHelper.parse(json); - - final List 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 44500747e..62158bc44 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); - } - } + public NoteCreateResponse createNote(final NoteCreateRequest request) { + Note note; + Long officeId; - 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); - - 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)); + 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()); - 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 = ""; + if (!Strings.CI.equals(note.getNote(), request.getNote())) { + response.changes(note.update(request.getNote())); + noteRepository.saveAndFlush(note); } - 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 changes = noteForUpdate.update(command); - - if (!changes.isEmpty()) { - this.noteRepository.saveAndFlush(noteForUpdate); - } - - 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()); - } - - final Map 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 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 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 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) { + public NoteDeleteResponse deleteNote(final NoteDeleteRequest request) { + var note = getNote(request.getType(), request.getResourceId(), request.getId()); - this.fromApiJsonDeserializer.validateNote(command.json()); + noteRepository.delete(note.getLeft()); - final String resourceUrl = getResourceUrlFromCommand(command); // command.getSupportedEntityType(); - final NoteType type = NoteType.fromApiUrl(resourceUrl); + return NoteDeleteResponse.builder().resourceId(request.getId()).build(); + } + + private Pair getNote(NoteType type, Long resourceId, Long noteId) { + Note note = null; + Long officeId = null; 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); - } - } - - @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 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 8fab6f156..c61fd8f8d 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 cdd191aeb..c697bbd23 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 9a0c666dc..f516b0be4 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 b3d2a86ae..93c249625 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 40cbafc56..132065662 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 8f81fa897..a3a499dd3 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 bc3af6174..21efd202e 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,