vidakovic commented on code in PR #6034:
URL: https://github.com/apache/fineract/pull/6034#discussion_r3471835388


##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResource.java:
##########
@@ -39,110 +39,129 @@
 import jakarta.ws.rs.core.UriInfo;
 import java.time.LocalDate;
 import java.util.Arrays;
-import java.util.Map;
 import java.util.Set;
+import java.util.function.Supplier;
 import lombok.RequiredArgsConstructor;
-import org.apache.fineract.batch.command.CommandHandlerRegistry;
-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.command.core.CommandDispatcher;
 import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import 
org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
-import 
org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
 import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
 import org.apache.fineract.infrastructure.core.service.Page;
 import org.apache.fineract.infrastructure.core.service.SearchParameters;
-import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.infrastructure.security.service.SqlValidator;
+import 
org.apache.fineract.portfolio.account.command.StandingInstructionCreateCommand;
+import 
org.apache.fineract.portfolio.account.command.StandingInstructionDeleteCommand;
+import 
org.apache.fineract.portfolio.account.command.StandingInstructionUpdateCommand;
 import org.apache.fineract.portfolio.account.data.AccountTransferData;
 import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
 import org.apache.fineract.portfolio.account.data.StandingInstructionData;
 import 
org.apache.fineract.portfolio.account.data.request.StandingInstructionCreationRequest;
+import 
org.apache.fineract.portfolio.account.data.request.StandingInstructionDeleteRequest;
 import 
org.apache.fineract.portfolio.account.data.request.StandingInstructionSearchParam;
 import 
org.apache.fineract.portfolio.account.data.request.StandingInstructionUpdatesRequest;
+import 
org.apache.fineract.portfolio.account.data.response.StandingInstructionCreateResponse;
+import 
org.apache.fineract.portfolio.account.data.response.StandingInstructionDeleteResponse;
+import 
org.apache.fineract.portfolio.account.data.response.StandingInstructionUpdateResponse;
+import 
org.apache.fineract.portfolio.account.data.response.StandingInstructionWriteResponse;
 import 
org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
-import 
org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
+import 
org.apache.fineract.portfolio.account.service.StandingInstructionReadService;
 import org.springframework.stereotype.Component;
 
 @Path("/v1/standinginstructions")
 @Component
-@Tag(name = "Standing Instructions", description = "Standing instructions (or 
standing orders) refer to instructions a bank account holder (\"the payer\") 
gives to his or her bank to pay a set amount at regular intervals to another's 
(\"the payee's\") account.\n"
-        + "\n" + "Note: At present only savings account to savings account and 
savings account to Loan account transfers are permitted.")
+@Produces({ MediaType.APPLICATION_JSON })
+@Tag(name = "Standing Instructions", description = """
+        Standing instructions (or standing orders) refer to instructions a 
bank account holder ("the payer") gives to his or her bank to pay a set amount 
at regular intervals to another's ("the payee's") account.
+
+        Note: At present only savings account to savings account and savings 
account to Loan account transfers are permitted.""")
 @RequiredArgsConstructor
 public class StandingInstructionApiResource {
 
-    private final PlatformSecurityContext context;
-    private final DefaultToApiJsonSerializer<StandingInstructionData> 
toApiJsonSerializer;
-    private final PortfolioCommandSourceWritePlatformService 
commandsSourceWritePlatformService;
-    private final StandingInstructionReadPlatformService 
standingInstructionReadPlatformService;
+    private final StandingInstructionReadService 
standingInstructionReadService;
     private final AccountTransfersReadPlatformService 
accountTransfersReadPlatformService;
     private final SqlValidator sqlValidator;
-
-    private static final CommandHandlerRegistry<String, Long, String, 
CommandWrapper> COMMAND_HANDLER_REGISTRY = new CommandHandlerRegistry<>(
-            Map.of(CommandParameterUtil.UPDATE_COMMAND_VALUE,
-                    (id, json) -> new 
CommandWrapperBuilder().updateStandingInstruction(id).withJson(json).build(),
-                    CommandParameterUtil.DELETE_COMMAND_VALUE,
-                    (id, json) -> new 
CommandWrapperBuilder().deleteStandingInstruction(id).withJson(json).build()));
+    private final CommandDispatcher dispatcher;
 
     @GET
     @Path("template")
-    @Produces({ MediaType.APPLICATION_JSON })
-    @Operation(summary = "Retrieve Standing Instruction Template", operationId 
= "retrieveTemplateStandingInstruction", description = "This is a convenience 
resource. "
-            + "It can be useful when building maintenance user interface 
screens for client applications. "
-            + "The template data returned consists of any or all of:\n" + "\n" 
+ "Field Defaults\n" + "Allowed Value Lists\n"
-            + "Example Requests:\n" + "\n" + 
"standinginstructions/template?fromAccountType=2&fromOfficeId=1\n" + "\n"
-            + 
"standinginstructions/template?fromAccountType=2&fromOfficeId=1&fromClientId=1&transferType=1\n"
 + "\n"
-            + 
"standinginstructions/template?fromClientId=1&fromAccountType=2&fromAccountId=1&transferType=1")
+    @Operation(summary = "Retrieve Standing Instruction Template", operationId 
= "retrieveTemplateStandingInstruction", description = """
+            This is a convenience resource. It can be useful when building 
maintenance user interface screens for client applications. The template data 
returned consists of any or all of:
+
+            Field Defaults
+            Allowed Value Lists
+            Example Requests:
+
+            standinginstructions/template?fromAccountType=2&fromOfficeId=1
+
+            
standinginstructions/template?fromAccountType=2&fromOfficeId=1&fromClientId=1&transferType=1
+
+            
standinginstructions/template?fromClientId=1&fromAccountType=2&fromAccountId=1&transferType=1""")
     @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
StandingInstructionApiResourceSwagger.GetStandingInstructionsTemplateResponse.class)))

Review Comment:
   1@Schema annotation not necessary when we have proper return types... All 
these manually maintained dummy classes need to go.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResource.java:
##########
@@ -169,14 +186,15 @@ public Page<StandingInstructionData> retrieveAll(
         StandingInstructionDTO standingInstructionDTO = new 
StandingInstructionDTO(searchParameters, transferType, clientName, clientId,

Review Comment:
   Let's call this StandingInstructionData... Suffix data... That's what we use 
for DTO classes.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/StandingInstructionApiResource.java:
##########
@@ -39,110 +39,129 @@
 import jakarta.ws.rs.core.UriInfo;
 import java.time.LocalDate;
 import java.util.Arrays;
-import java.util.Map;
 import java.util.Set;
+import java.util.function.Supplier;
 import lombok.RequiredArgsConstructor;
-import org.apache.fineract.batch.command.CommandHandlerRegistry;
-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.command.core.CommandDispatcher;
 import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import 
org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
-import 
org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
 import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
 import org.apache.fineract.infrastructure.core.service.Page;
 import org.apache.fineract.infrastructure.core.service.SearchParameters;
-import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.infrastructure.security.service.SqlValidator;
+import 
org.apache.fineract.portfolio.account.command.StandingInstructionCreateCommand;
+import 
org.apache.fineract.portfolio.account.command.StandingInstructionDeleteCommand;
+import 
org.apache.fineract.portfolio.account.command.StandingInstructionUpdateCommand;
 import org.apache.fineract.portfolio.account.data.AccountTransferData;
 import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
 import org.apache.fineract.portfolio.account.data.StandingInstructionData;
 import 
org.apache.fineract.portfolio.account.data.request.StandingInstructionCreationRequest;
+import 
org.apache.fineract.portfolio.account.data.request.StandingInstructionDeleteRequest;
 import 
org.apache.fineract.portfolio.account.data.request.StandingInstructionSearchParam;
 import 
org.apache.fineract.portfolio.account.data.request.StandingInstructionUpdatesRequest;
+import 
org.apache.fineract.portfolio.account.data.response.StandingInstructionCreateResponse;
+import 
org.apache.fineract.portfolio.account.data.response.StandingInstructionDeleteResponse;
+import 
org.apache.fineract.portfolio.account.data.response.StandingInstructionUpdateResponse;
+import 
org.apache.fineract.portfolio.account.data.response.StandingInstructionWriteResponse;
 import 
org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
-import 
org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
+import 
org.apache.fineract.portfolio.account.service.StandingInstructionReadService;
 import org.springframework.stereotype.Component;
 
 @Path("/v1/standinginstructions")
 @Component
-@Tag(name = "Standing Instructions", description = "Standing instructions (or 
standing orders) refer to instructions a bank account holder (\"the payer\") 
gives to his or her bank to pay a set amount at regular intervals to another's 
(\"the payee's\") account.\n"
-        + "\n" + "Note: At present only savings account to savings account and 
savings account to Loan account transfers are permitted.")
+@Produces({ MediaType.APPLICATION_JSON })
+@Tag(name = "Standing Instructions", description = """
+        Standing instructions (or standing orders) refer to instructions a 
bank account holder ("the payer") gives to his or her bank to pay a set amount 
at regular intervals to another's ("the payee's") account.
+
+        Note: At present only savings account to savings account and savings 
account to Loan account transfers are permitted.""")
 @RequiredArgsConstructor
 public class StandingInstructionApiResource {
 
-    private final PlatformSecurityContext context;
-    private final DefaultToApiJsonSerializer<StandingInstructionData> 
toApiJsonSerializer;
-    private final PortfolioCommandSourceWritePlatformService 
commandsSourceWritePlatformService;
-    private final StandingInstructionReadPlatformService 
standingInstructionReadPlatformService;
+    private final StandingInstructionReadService 
standingInstructionReadService;
     private final AccountTransfersReadPlatformService 
accountTransfersReadPlatformService;
     private final SqlValidator sqlValidator;
-
-    private static final CommandHandlerRegistry<String, Long, String, 
CommandWrapper> COMMAND_HANDLER_REGISTRY = new CommandHandlerRegistry<>(
-            Map.of(CommandParameterUtil.UPDATE_COMMAND_VALUE,
-                    (id, json) -> new 
CommandWrapperBuilder().updateStandingInstruction(id).withJson(json).build(),
-                    CommandParameterUtil.DELETE_COMMAND_VALUE,
-                    (id, json) -> new 
CommandWrapperBuilder().deleteStandingInstruction(id).withJson(json).build()));
+    private final CommandDispatcher dispatcher;
 
     @GET
     @Path("template")
-    @Produces({ MediaType.APPLICATION_JSON })
-    @Operation(summary = "Retrieve Standing Instruction Template", operationId 
= "retrieveTemplateStandingInstruction", description = "This is a convenience 
resource. "
-            + "It can be useful when building maintenance user interface 
screens for client applications. "
-            + "The template data returned consists of any or all of:\n" + "\n" 
+ "Field Defaults\n" + "Allowed Value Lists\n"
-            + "Example Requests:\n" + "\n" + 
"standinginstructions/template?fromAccountType=2&fromOfficeId=1\n" + "\n"
-            + 
"standinginstructions/template?fromAccountType=2&fromOfficeId=1&fromClientId=1&transferType=1\n"
 + "\n"
-            + 
"standinginstructions/template?fromClientId=1&fromAccountType=2&fromAccountId=1&transferType=1")
+    @Operation(summary = "Retrieve Standing Instruction Template", operationId 
= "retrieveTemplateStandingInstruction", description = """
+            This is a convenience resource. It can be useful when building 
maintenance user interface screens for client applications. The template data 
returned consists of any or all of:
+
+            Field Defaults
+            Allowed Value Lists
+            Example Requests:
+
+            standinginstructions/template?fromAccountType=2&fromOfficeId=1
+
+            
standinginstructions/template?fromAccountType=2&fromOfficeId=1&fromClientId=1&transferType=1
+
+            
standinginstructions/template?fromClientId=1&fromAccountType=2&fromAccountId=1&transferType=1""")
     @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
StandingInstructionApiResourceSwagger.GetStandingInstructionsTemplateResponse.class)))
     public StandingInstructionData template(@BeanParam 
StandingInstructionSearchParam instructionParam) {
-        
context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
-
-        return 
standingInstructionReadPlatformService.retrieveTemplate(instructionParam.getFromOfficeId(),
-                instructionParam.getFromClientId(), 
instructionParam.getFromAccountId(), instructionParam.getFromAccountType(),
-                instructionParam.getToOfficeId(), 
instructionParam.getToClientId(), instructionParam.getToAccountId(),
-                instructionParam.getToAccountType(), 
instructionParam.getTransferType());
+        return 
standingInstructionReadService.retrieveTemplate(instructionParam.getFromOfficeId(),
 instructionParam.getFromClientId(),
+                instructionParam.getFromAccountId(), 
instructionParam.getFromAccountType(), instructionParam.getToOfficeId(),
+                instructionParam.getToClientId(), 
instructionParam.getToAccountId(), instructionParam.getToAccountType(),
+                instructionParam.getTransferType());
     }
 
     @POST
     @Consumes({ MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_JSON })
     @Operation(summary = "Create new Standing Instruction", operationId = 
"createStandingInstruction", description = "Ability to create new instruction 
for transfer of monetary funds from one account to another")
     @RequestBody(required = true, content = @Content(schema = 
@Schema(implementation = StandingInstructionCreationRequest.class)))
     @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
StandingInstructionApiResourceSwagger.PostStandingInstructionsResponse.class)))
-    public CommandProcessingResult create(@Parameter(hidden = true) 
StandingInstructionCreationRequest creationRequest) {
-        final CommandWrapper commandRequest = new 
CommandWrapperBuilder().createStandingInstruction()
-                
.withJson(toApiJsonSerializer.serialize(creationRequest)).build();
+    public StandingInstructionCreateResponse create(@Parameter(hidden = true) 
StandingInstructionCreationRequest creationRequest) {
+        final StandingInstructionCreateCommand command = new 
StandingInstructionCreateCommand();
+        command.setPayload(creationRequest);
+
+        final Supplier<StandingInstructionCreateResponse> response = 
dispatcher.dispatch(command);
 
-        return 
commandsSourceWritePlatformService.logCommandSource(commandRequest);
+        return response.get();
     }
 
     @PUT
     @Path("{standingInstructionId}")
     @Consumes({ MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_JSON })
-    @Operation(summary = "Update Standing Instruction | Delete Standing 
Instruction", operationId = "updateStandingInstruction", description = "Ability 
to modify existing instruction for transfer of monetary funds from one account 
to another.\n"
-            + "\n" + "PUT 
https://DomainName/api/v1/standinginstructions/1?command=update\n"; + "\n\n"
-            + "Ability to modify existing instruction for transfer of monetary 
funds from one account to another.\n" + "\n"
-            + "PUT 
https://DomainName/api/v1/standinginstructions/1?command=delete";)
+    @Operation(summary = "Update Standing Instruction | Delete Standing 
Instruction", operationId = "updateStandingInstruction", description = """
+            Ability to modify existing instruction for transfer of monetary 
funds from one account to another.
+
+            PUT https://DomainName/api/v1/standinginstructions/1?command=update
+
+
+            Ability to modify existing instruction for transfer of monetary 
funds from one account to another.
+
+            PUT 
https://DomainName/api/v1/standinginstructions/1?command=delete""";)
     @RequestBody(content = @Content(schema = @Schema(implementation = 
StandingInstructionUpdatesRequest.class)))
     @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
StandingInstructionApiResourceSwagger.PutStandingInstructionsStandingInstructionIdResponse.class)))
-    public CommandProcessingResult update(
+    public StandingInstructionWriteResponse update(

Review Comment:
   Shouldn't this be StandingInstructionUpdateResponse?



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/request/StandingInstructionUpdatesRequest.java:
##########
@@ -18,30 +18,38 @@
  */
 package org.apache.fineract.portfolio.account.data.request;
 
+import io.swagger.v3.oas.annotations.Hidden;
 import java.io.Serial;
 import java.io.Serializable;
+import java.math.BigDecimal;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+@Builder
 @Data
 @NoArgsConstructor
+@AllArgsConstructor
 public class StandingInstructionUpdatesRequest implements Serializable {
 
     @Serial
     private static final long serialVersionUID = 1L;
 
-    private String amount;
-    private String validTill;
-    private String dateFormat;
+    @Hidden

Review Comment:
   Why hidden?



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/request/StandingInstructionUpdatesRequest.java:
##########
@@ -18,30 +18,38 @@
  */
 package org.apache.fineract.portfolio.account.data.request;
 
+import io.swagger.v3.oas.annotations.Hidden;
 import java.io.Serial;
 import java.io.Serializable;
+import java.math.BigDecimal;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+@Builder
 @Data
 @NoArgsConstructor
+@AllArgsConstructor
 public class StandingInstructionUpdatesRequest implements Serializable {

Review Comment:
   StandingInstructionUpdateRequest... Singular, not "Updates"... Let's be 
consistent.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWriteServiceImpl.java:
##########
@@ -0,0 +1,332 @@
+/**
+ * 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.account.service;
+
+import static 
org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
+import static 
org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.amountParamName;
+import static 
org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.instructionTypeParamName;
+import static 
org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.nameParamName;
+import static 
org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.priorityParamName;
+import static 
org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceFrequencyParamName;
+import static 
org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceIntervalParamName;
+import static 
org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceOnMonthDayParamName;
+import static 
org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceTypeParamName;
+import static 
org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
+import static 
org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validFromParamName;
+import static 
org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validTillParamName;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.MonthDay;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+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.ErrorHandler;
+import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import 
org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import 
org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants;
+import 
org.apache.fineract.portfolio.account.data.request.StandingInstructionCreationRequest;
+import 
org.apache.fineract.portfolio.account.data.request.StandingInstructionDeleteRequest;
+import 
org.apache.fineract.portfolio.account.data.request.StandingInstructionUpdatesRequest;
+import 
org.apache.fineract.portfolio.account.data.response.StandingInstructionCreateResponse;
+import 
org.apache.fineract.portfolio.account.data.response.StandingInstructionDeleteResponse;
+import 
org.apache.fineract.portfolio.account.data.response.StandingInstructionUpdateResponse;
+import 
org.apache.fineract.portfolio.account.domain.AccountTransferDetailAssembler;
+import 
org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
+import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
+import 
org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
+import 
org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction;
+import org.apache.fineract.portfolio.account.domain.AccountTransferType;
+import 
org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
+import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
+import 
org.apache.fineract.portfolio.account.exception.StandingInstructionNotFoundException;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.dao.NonTransientDataAccessException;
+import org.springframework.orm.jpa.JpaSystemException;
+import org.springframework.transaction.annotation.Transactional;
+
+@Slf4j
+@RequiredArgsConstructor
+public class StandingInstructionWriteServiceImpl implements 
StandingInstructionWriteService {
+
+    private final AccountTransferDetailAssembler 
accountTransferDetailAssembler;
+    private final AccountTransferDetailRepository 
accountTransferDetailRepository;
+    private final StandingInstructionRepository standingInstructionRepository;
+    private final SavingsAccountAssembler savingsAccountAssembler;
+    private final LoanAssembler loanAccountAssembler;
+
+    @Transactional
+    @Override
+    public StandingInstructionCreateResponse create(final 
StandingInstructionCreationRequest request) {
+        final LocalDate validFrom = parseDate(request.getValidFrom(), 
request.getDateFormat(), request.getLocale());
+        final LocalDate validTill = parseDate(request.getValidTill(), 
request.getDateFormat(), request.getLocale());
+        final MonthDay recurrenceOnMonthDay = 
parseMonthDay(request.getRecurrenceOnMonthDay(), request.getMonthDayFormat(),
+                request.getLocale());
+
+        validateForCreate(request, validFrom, validTill, recurrenceOnMonthDay);
+
+        final PortfolioAccountType fromAccountType = 
PortfolioAccountType.fromInt(request.getFromAccountType());
+        final PortfolioAccountType toAccountType = 
PortfolioAccountType.fromInt(request.getToAccountType());
+
+        Long standingInstructionId = null;
+        try {
+            final AccountTransferDetails details = 
assembleAccountTransferDetails(request, fromAccountType, toAccountType);
+
+            BigDecimal amount = request.getAmount();
+            if (amount != null && details.fromSavingsAccount() != null) {
+                amount = Money.of(details.fromSavingsAccount().getCurrency(), 
amount).getAmount();
+            }
+
+            final AccountTransferStandingInstruction standingInstruction = 
AccountTransferStandingInstruction.create(details,
+                    request.getName(), request.getPriority(), 
request.getInstructionType(), request.getStatus(), amount, validFrom,
+                    validTill, request.getRecurrenceType(), 
request.getRecurrenceFrequency(), request.getRecurrenceInterval(),
+                    recurrenceOnMonthDay);
+            
details.updateAccountTransferStandingInstruction(standingInstruction);
+
+            this.accountTransferDetailRepository.saveAndFlush(details);
+            standingInstructionId = 
details.accountTransferStandingInstruction().getId();
+        } catch (final JpaSystemException | DataIntegrityViolationException 
dve) {
+            handleDataIntegrityIssues(request.getName(), 
dve.getMostSpecificCause(), dve);
+        }
+
+        return 
StandingInstructionCreateResponse.builder().resourceId(standingInstructionId).clientId(request.getFromClientId()).build();
+    }
+
+    @Transactional
+    @Override
+    public StandingInstructionUpdateResponse update(final 
StandingInstructionUpdatesRequest request) {
+        final LocalDate validFrom = parseDate(request.getValidFrom(), 
request.getDateFormat(), request.getLocale());
+        final LocalDate validTill = parseDate(request.getValidTill(), 
request.getDateFormat(), request.getLocale());
+        final MonthDay recurrenceOnMonthDay = 
parseMonthDay(request.getRecurrenceOnMonthDay(), request.getMonthDayFormat(),
+                request.getLocale());
+
+        validateForUpdate(request, validFrom, validTill);
+
+        final AccountTransferStandingInstruction standingInstruction = 
this.standingInstructionRepository.findById(request.getId())
+                .orElseThrow(() -> new 
StandingInstructionNotFoundException(request.getId()));
+
+        final Map<String, Object> changes = 
standingInstruction.update(request.getAmount(), validFrom, validTill, 
request.getStatus(),
+                request.getPriority(), request.getInstructionType(), 
request.getRecurrenceType(), request.getRecurrenceFrequency(),
+                request.getRecurrenceInterval(), recurrenceOnMonthDay);
+
+        if (!changes.isEmpty()) {
+            this.standingInstructionRepository.save(standingInstruction);
+        }
+
+        return 
StandingInstructionUpdateResponse.builder().resourceId(request.getId()).changes(changes).build();
+    }
+
+    @Transactional
+    @Override
+    public StandingInstructionDeleteResponse delete(final 
StandingInstructionDeleteRequest request) {
+        final AccountTransferStandingInstruction standingInstruction = 
this.standingInstructionRepository.findById(request.getId())
+                .orElseThrow(() -> new 
StandingInstructionNotFoundException(request.getId()));
+        standingInstruction.delete();
+        this.standingInstructionRepository.save(standingInstruction);
+        return 
StandingInstructionDeleteResponse.builder().resourceId(request.getId()).build();
+    }
+
+    private AccountTransferDetails assembleAccountTransferDetails(final 
StandingInstructionCreationRequest request,
+            final PortfolioAccountType fromAccountType, final 
PortfolioAccountType toAccountType) {
+        if (PortfolioAccountType.SAVINGS.equals(fromAccountType) && 
PortfolioAccountType.SAVINGS.equals(toAccountType)) {
+            final SavingsAccount fromSavingsAccount = 
this.savingsAccountAssembler.assembleFrom(request.getFromAccountId(), false);
+            final SavingsAccount toSavingsAccount = 
this.savingsAccountAssembler.assembleFrom(request.getToAccountId(), false);
+            return 
this.accountTransferDetailAssembler.assembleSavingsToSavingsTransfer(fromSavingsAccount,
 toSavingsAccount,
+                    request.getTransferType());
+        } else if (PortfolioAccountType.SAVINGS.equals(fromAccountType) && 
PortfolioAccountType.LOAN.equals(toAccountType)) {
+            final SavingsAccount fromSavingsAccount = 
this.savingsAccountAssembler.assembleFrom(request.getFromAccountId(), false);
+            final Loan toLoanAccount = 
this.loanAccountAssembler.assembleFrom(request.getToAccountId());
+            return 
this.accountTransferDetailAssembler.assembleSavingsToLoanTransfer(fromSavingsAccount,
 toLoanAccount,
+                    request.getTransferType());
+        } else if (PortfolioAccountType.LOAN.equals(fromAccountType) && 
PortfolioAccountType.SAVINGS.equals(toAccountType)) {
+            final Loan fromLoanAccount = 
this.loanAccountAssembler.assembleFrom(request.getFromAccountId());
+            final SavingsAccount toSavingsAccount = 
this.savingsAccountAssembler.assembleFrom(request.getToAccountId(), false);
+            return 
this.accountTransferDetailAssembler.assembleLoanToSavingsTransfer(fromLoanAccount,
 toSavingsAccount,
+                    request.getTransferType());
+        }
+        throw new 
PlatformDataIntegrityException("error.msg.standinginstruction.transfer.type.not.supported",
+                "Transfer between the given account types is not supported for 
standing instructions");
+    }
+
+    private void validateForCreate(final StandingInstructionCreationRequest 
request, final LocalDate validFrom, final LocalDate validTill,

Review Comment:
   Could this be done using Jakarta Validation?



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

To unsubscribe, e-mail: [email protected]

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

Reply via email to