Merge self-hosted/feature/account-charges into feature/Backend-DSC-MFA
This commit is contained in:
+79
-71
@@ -1,71 +1,79 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
public enum PortfolioAccountType {
|
||||
|
||||
INVALID(0, "accountType.invalid"), //
|
||||
LOAN(1, "accountType.loan"), //
|
||||
SAVINGS(2, "accountType.savings"), //
|
||||
;
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
|
||||
PortfolioAccountType(final Integer value, final String code) {
|
||||
this.value = value;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static PortfolioAccountType fromInt(final Integer type) {
|
||||
|
||||
PortfolioAccountType enumType = INVALID;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
enumType = LOAN;
|
||||
break;
|
||||
case 2:
|
||||
enumType = SAVINGS;
|
||||
break;
|
||||
default:
|
||||
enumType = INVALID;
|
||||
}
|
||||
}
|
||||
return enumType;
|
||||
}
|
||||
|
||||
// TODO: bad practice and unnecessary code! why not just use the enum values themselves!?!
|
||||
public boolean isSavingsAccount() {
|
||||
return this.equals(SAVINGS);
|
||||
}
|
||||
|
||||
// TODO: bad practice and unnecessary code! why not just use the enum values themselves!?!
|
||||
public boolean isLoanAccount() {
|
||||
return this.equals(LOAN);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
|
||||
public enum PortfolioAccountType {
|
||||
|
||||
INVALID(0, "accountType.invalid"), //
|
||||
LOAN(1, "accountType.loan"), //
|
||||
SAVINGS(2, "accountType.savings"), //
|
||||
CURRENT(3, "accountType.current"), //
|
||||
;
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
|
||||
PortfolioAccountType(final Integer value, final String code) {
|
||||
this.value = value;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static PortfolioAccountType fromInt(final Integer type) {
|
||||
|
||||
PortfolioAccountType enumType = INVALID;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
enumType = LOAN;
|
||||
break;
|
||||
case 2:
|
||||
enumType = SAVINGS;
|
||||
break;
|
||||
case 3:
|
||||
enumType = CURRENT;
|
||||
break;
|
||||
default:
|
||||
enumType = INVALID;
|
||||
}
|
||||
}
|
||||
return enumType;
|
||||
}
|
||||
|
||||
// TODO: bad practice and unnecessary code! why not just use the enum values themselves!?!
|
||||
public boolean isSavingsAccount() {
|
||||
return this.equals(SAVINGS);
|
||||
}
|
||||
|
||||
// TODO: bad practice and unnecessary code! why not just use the enum values themselves!?!
|
||||
public boolean isLoanAccount() {
|
||||
return this.equals(LOAN);
|
||||
}
|
||||
|
||||
public boolean isCurrentAccount() {
|
||||
return this.equals(CURRENT);
|
||||
}
|
||||
}
|
||||
|
||||
+694
-688
File diff suppressed because it is too large
Load Diff
+21
@@ -600,6 +600,27 @@ public class AccountingProcessorHelper {
|
||||
createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a direct transfer journal entry between two savings-type accounts (savings or current),
|
||||
* each with their own product GL mapping and entity ID so the correct account number appears per entry.
|
||||
*
|
||||
* @param debitProductId product ID whose SAVINGS_CONTROL GL is debited
|
||||
* @param debitEntityId entity (account) ID shown on the debit entry
|
||||
* @param creditProductId product ID whose SAVINGS_CONTROL GL is credited
|
||||
* @param creditEntityId entity (account) ID shown on the credit entry
|
||||
*/
|
||||
public void createDirectTransferJournalEntriesForSavings(final Office office, final String currencyCode,
|
||||
final Long debitProductId, final Long debitEntityId,
|
||||
final Long creditProductId, final Long creditEntityId,
|
||||
final String transactionId, final LocalDate transactionDate, final BigDecimal amount) {
|
||||
final GLAccount debitAccount = getLinkedGLAccountForSavingsProduct(debitProductId,
|
||||
CashAccountsForSavings.SAVINGS_CONTROL.getValue(), null);
|
||||
final GLAccount creditAccount = getLinkedGLAccountForSavingsProduct(creditProductId,
|
||||
CashAccountsForSavings.SAVINGS_CONTROL.getValue(), null);
|
||||
createDebitJournalEntryForSavings(office, currencyCode, debitAccount, debitEntityId, transactionId, transactionDate, amount);
|
||||
createCreditJournalEntryForSavings(office, currencyCode, creditAccount, creditEntityId, transactionId, transactionDate, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that creates a pair of related Debits and Credits for Cash Based accounting.
|
||||
*
|
||||
|
||||
+3
-4
@@ -106,8 +106,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
.append(" curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ")
|
||||
.append(" eao.external_id as externalAssetOwner, ")
|
||||
.append(" CASE ")
|
||||
.append(" WHEN journalEntry.entity_type_enum = 2 AND journalEntry.transaction_id LIKE 'CA%' THEN ca.account_no ")
|
||||
.append(" WHEN journalEntry.entity_type_enum = 2 THEN sa.account_no ")
|
||||
.append(" WHEN journalEntry.entity_type_enum = 2 THEN COALESCE(ca.account_no, sa.account_no) ")
|
||||
.append(" ELSE NULL ")
|
||||
.append(" END as savingsAccountNo ");
|
||||
if (associationParametersData.isRunningBalanceRequired()) {
|
||||
@@ -130,8 +129,8 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
.append(" join m_currency curr on curr.code = journalEntry.currency_code ")
|
||||
.append(" left join m_external_asset_owner_journal_entry_mapping eajem on eajem.journal_entry_id = journalEntry.id ")
|
||||
.append(" left join m_external_asset_owner eao on eao.id = eajem.owner_id ")
|
||||
.append(" left join m_savings_account sa on sa.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 AND journalEntry.transaction_id NOT LIKE 'CA%' ")
|
||||
.append(" left join m_current_account ca on ca.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 AND journalEntry.transaction_id LIKE 'CA%' ");
|
||||
.append(" left join m_savings_account sa on sa.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 ")
|
||||
.append(" left join m_current_account ca on ca.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 ");
|
||||
if (associationParametersData.isTransactionDetailsRequired()) {
|
||||
sb.append(" left join m_loan_transaction as lt on journalEntry.loan_transaction_id = lt.id ")
|
||||
.append(" left join m_savings_account_transaction as st on journalEntry.savings_transaction_id = st.id ")
|
||||
|
||||
+220
-220
@@ -1,220 +1,220 @@
|
||||
/**
|
||||
* 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.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.tags.Tag;
|
||||
import jakarta.ws.rs.BeanParam;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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.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.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.StandingInstructionSearchParam;
|
||||
import org.apache.fineract.portfolio.account.data.request.StandingInstructionUpdatesRequest;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
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.")
|
||||
@RequiredArgsConstructor
|
||||
public class StandingInstructionApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final DefaultToApiJsonSerializer<StandingInstructionData> toApiJsonSerializer;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||
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()));
|
||||
|
||||
@GET
|
||||
@Path("template")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve Standing Instruction Template", 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")
|
||||
@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());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Create new Standing Instruction", 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();
|
||||
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("{standingInstructionId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Update Standing Instruction | Delete Standing Instruction", 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")
|
||||
@RequestBody(content = @Content(schema = @Schema(implementation = StandingInstructionUpdatesRequest.class)))
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.PutStandingInstructionsStandingInstructionIdResponse.class)))
|
||||
public CommandProcessingResult update(
|
||||
@PathParam("standingInstructionId") @Parameter(description = "standingInstructionId") final Long standingInstructionId,
|
||||
@Parameter(hidden = true) StandingInstructionUpdatesRequest updatesRequest,
|
||||
@QueryParam("command") @Parameter(description = "command") final String commandParam) {
|
||||
|
||||
final String serializedUpdatesRequest = toApiJsonSerializer.serialize(updatesRequest);
|
||||
final CommandWrapper commandRequest = COMMAND_HANDLER_REGISTRY.execute(commandParam, standingInstructionId,
|
||||
serializedUpdatesRequest, new UnrecognizedQueryParamException("command", commandParam));
|
||||
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "List Standing Instructions", description = "Example Requests:\n" + "\n" + "standinginstructions")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsResponse.class)))
|
||||
public Page<StandingInstructionData> retrieveAll(
|
||||
@QueryParam("externalId") @Parameter(description = "externalId") final String externalId,
|
||||
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
|
||||
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
|
||||
@QueryParam("transferType") @Parameter(description = "transferType") final Integer transferType,
|
||||
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName,
|
||||
@QueryParam("clientId") @Parameter(description = "clientId") final Long clientId,
|
||||
@QueryParam("fromAccountId") @Parameter(description = "fromAccountId") final Long fromAccount,
|
||||
@QueryParam("fromAccountType") @Parameter(description = "fromAccountType") final Integer fromAccountType) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
sqlValidator.validate(orderBy);
|
||||
sqlValidator.validate(sortOrder);
|
||||
sqlValidator.validate(externalId);
|
||||
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
|
||||
.orderBy(orderBy).sortOrder(sortOrder).build();
|
||||
|
||||
final LocalDate startDateRange = null;
|
||||
final LocalDate endDateRange = null;
|
||||
StandingInstructionDTO standingInstructionDTO = new StandingInstructionDTO(searchParameters, transferType, clientName, clientId,
|
||||
fromAccount, fromAccountType, startDateRange, endDateRange);
|
||||
|
||||
return standingInstructionReadPlatformService.retrieveAll(standingInstructionDTO);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{standingInstructionId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve Standing Instruction", description = "Example Requests :\n" + "\n" + "standinginstructions/1")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsStandingInstructionIdResponse.class)))
|
||||
public StandingInstructionData retrieveOne(
|
||||
@PathParam("standingInstructionId") @Parameter(description = "standingInstructionId") final Long standingInstructionId,
|
||||
@Context final UriInfo uriInfo, @QueryParam("externalId") @Parameter(description = "externalId") final String externalId,
|
||||
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
|
||||
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
sqlValidator.validate(orderBy);
|
||||
sqlValidator.validate(sortOrder);
|
||||
StandingInstructionData standingInstructionData = standingInstructionReadPlatformService.retrieveOne(standingInstructionId);
|
||||
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
|
||||
.orderBy(orderBy).sortOrder(sortOrder).build();
|
||||
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||
if (!associationParameters.isEmpty()) {
|
||||
if (associationParameters.contains("all")) {
|
||||
associationParameters.addAll(Arrays.asList("transactions", "template"));
|
||||
}
|
||||
if (associationParameters.contains("transactions")) {
|
||||
Page<AccountTransferData> transfers = accountTransfersReadPlatformService
|
||||
.retrieveByStandingInstruction(standingInstructionId, searchParameters);
|
||||
standingInstructionData = StandingInstructionData.withTransferData(standingInstructionData, transfers);
|
||||
}
|
||||
if (associationParameters.contains("template")) {
|
||||
final StandingInstructionData templateData = standingInstructionReadPlatformService.retrieveTemplate(
|
||||
standingInstructionData.getFromClient().getOfficeId(), standingInstructionData.getFromClient().getId(),
|
||||
standingInstructionData.getFromAccount().getId(), standingInstructionData.getFromAccountType().getValue(),
|
||||
standingInstructionData.getToClient().getOfficeId(), standingInstructionData.getToClient().getId(),
|
||||
standingInstructionData.getToAccount().getId(), standingInstructionData.getToAccountType().getValue(),
|
||||
standingInstructionData.getTransferType().getValue());
|
||||
standingInstructionData = StandingInstructionData.withTemplateData(standingInstructionData, templateData);
|
||||
}
|
||||
}
|
||||
|
||||
return standingInstructionData;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.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.tags.Tag;
|
||||
import jakarta.ws.rs.BeanParam;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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.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.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.StandingInstructionSearchParam;
|
||||
import org.apache.fineract.portfolio.account.data.request.StandingInstructionUpdatesRequest;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
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.")
|
||||
@RequiredArgsConstructor
|
||||
public class StandingInstructionApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final DefaultToApiJsonSerializer<StandingInstructionData> toApiJsonSerializer;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||
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()));
|
||||
|
||||
@GET
|
||||
@Path("template")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve Standing Instruction Template", 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")
|
||||
@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());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Create new Standing Instruction", 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();
|
||||
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("{standingInstructionId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Update Standing Instruction | Delete Standing Instruction", 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")
|
||||
@RequestBody(content = @Content(schema = @Schema(implementation = StandingInstructionUpdatesRequest.class)))
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.PutStandingInstructionsStandingInstructionIdResponse.class)))
|
||||
public CommandProcessingResult update(
|
||||
@PathParam("standingInstructionId") @Parameter(description = "standingInstructionId") final Long standingInstructionId,
|
||||
@Parameter(hidden = true) StandingInstructionUpdatesRequest updatesRequest,
|
||||
@QueryParam("command") @Parameter(description = "command") final String commandParam) {
|
||||
|
||||
final String serializedUpdatesRequest = toApiJsonSerializer.serialize(updatesRequest);
|
||||
final CommandWrapper commandRequest = COMMAND_HANDLER_REGISTRY.execute(commandParam, standingInstructionId,
|
||||
serializedUpdatesRequest, new UnrecognizedQueryParamException("command", commandParam));
|
||||
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "List Standing Instructions", description = "Example Requests:\n" + "\n" + "standinginstructions")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsResponse.class)))
|
||||
public Page<StandingInstructionData> retrieveAll(
|
||||
@QueryParam("externalId") @Parameter(description = "externalId") final String externalId,
|
||||
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
|
||||
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
|
||||
@QueryParam("transferType") @Parameter(description = "transferType") final Integer transferType,
|
||||
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName,
|
||||
@QueryParam("clientId") @Parameter(description = "clientId") final Long clientId,
|
||||
@QueryParam("fromAccountId") @Parameter(description = "fromAccountId") final Long fromAccount,
|
||||
@QueryParam("fromAccountType") @Parameter(description = "fromAccountType") final Integer fromAccountType) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
sqlValidator.validate(orderBy);
|
||||
sqlValidator.validate(sortOrder);
|
||||
sqlValidator.validate(externalId);
|
||||
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
|
||||
.orderBy(orderBy).sortOrder(sortOrder).build();
|
||||
|
||||
final LocalDate startDateRange = null;
|
||||
final LocalDate endDateRange = null;
|
||||
StandingInstructionDTO standingInstructionDTO = new StandingInstructionDTO(searchParameters, transferType, clientName, clientId,
|
||||
fromAccount, fromAccountType, startDateRange, endDateRange);
|
||||
|
||||
return standingInstructionReadPlatformService.retrieveAll(standingInstructionDTO);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{standingInstructionId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve Standing Instruction", description = "Example Requests :\n" + "\n" + "standinginstructions/1")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsStandingInstructionIdResponse.class)))
|
||||
public StandingInstructionData retrieveOne(
|
||||
@PathParam("standingInstructionId") @Parameter(description = "standingInstructionId") final Long standingInstructionId,
|
||||
@Context final UriInfo uriInfo, @QueryParam("externalId") @Parameter(description = "externalId") final String externalId,
|
||||
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
|
||||
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
sqlValidator.validate(orderBy);
|
||||
sqlValidator.validate(sortOrder);
|
||||
StandingInstructionData standingInstructionData = standingInstructionReadPlatformService.retrieveOne(standingInstructionId);
|
||||
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
|
||||
.orderBy(orderBy).sortOrder(sortOrder).build();
|
||||
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||
if (!associationParameters.isEmpty()) {
|
||||
if (associationParameters.contains("all")) {
|
||||
associationParameters.addAll(Arrays.asList("transactions", "template"));
|
||||
}
|
||||
if (associationParameters.contains("transactions")) {
|
||||
Page<AccountTransferData> transfers = accountTransfersReadPlatformService
|
||||
.retrieveByStandingInstruction(standingInstructionId, searchParameters);
|
||||
standingInstructionData = StandingInstructionData.withTransferData(standingInstructionData, transfers);
|
||||
}
|
||||
if (associationParameters.contains("template")) {
|
||||
final StandingInstructionData templateData = standingInstructionReadPlatformService.retrieveTemplate(
|
||||
standingInstructionData.getFromClient().getOfficeId(), standingInstructionData.getFromClient().getId(),
|
||||
standingInstructionData.getFromAccount().getId(), standingInstructionData.fromAccountTypeEnum().getValue(),
|
||||
standingInstructionData.getToClient().getOfficeId(), standingInstructionData.getToClient().getId(),
|
||||
standingInstructionData.getToAccount().getId(), standingInstructionData.toAccountTypeEnum().getValue(),
|
||||
standingInstructionData.transferTypeEnum().getValue());
|
||||
standingInstructionData = StandingInstructionData.withTemplateData(standingInstructionData, templateData);
|
||||
}
|
||||
}
|
||||
|
||||
return standingInstructionData;
|
||||
}
|
||||
}
|
||||
|
||||
+79
-79
@@ -1,79 +1,79 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromOfficeIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class AccountTransfersDetailDataValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public AccountTransfersDetailDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validate(final JsonCommand command, final DataValidatorBuilder baseDataValidator) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fromOfficeIdParamName).value(fromOfficeId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fromClientIdParamName).value(fromClientId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long fromAccountId = this.fromApiJsonHelper.extractLongNamed(fromAccountIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fromAccountIdParamName).value(fromAccountId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Integer fromAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
|
||||
baseDataValidator.reset().parameter(fromAccountTypeParamName).value(fromAccountType).notNull()
|
||||
.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toOfficeIdParamName).value(toOfficeId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toClientIdParamName).value(toClientId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long toAccountId = this.fromApiJsonHelper.extractLongNamed(toAccountIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toAccountIdParamName).value(toAccountId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
|
||||
baseDataValidator.reset().parameter(toAccountTypeParamName).value(toAccountType).notNull().isOneOfTheseValues(Integer.valueOf(1),
|
||||
Integer.valueOf(2));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromOfficeIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class AccountTransfersDetailDataValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public AccountTransfersDetailDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validate(final JsonCommand command, final DataValidatorBuilder baseDataValidator) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fromOfficeIdParamName).value(fromOfficeId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fromClientIdParamName).value(fromClientId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long fromAccountId = this.fromApiJsonHelper.extractLongNamed(fromAccountIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fromAccountIdParamName).value(fromAccountId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Integer fromAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
|
||||
baseDataValidator.reset().parameter(fromAccountTypeParamName).value(fromAccountType).notNull()
|
||||
.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3));
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toOfficeIdParamName).value(toOfficeId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toClientIdParamName).value(toClientId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long toAccountId = this.fromApiJsonHelper.extractLongNamed(toAccountIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toAccountIdParamName).value(toAccountId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
|
||||
baseDataValidator.reset().parameter(toAccountTypeParamName).value(toAccountType).notNull().isOneOfTheseValues(Integer.valueOf(1),
|
||||
Integer.valueOf(2), Integer.valueOf(3));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+359
-329
@@ -1,329 +1,359 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.infrastructure.core.service.Page;
|
||||
import org.apache.fineract.organisation.office.data.OfficeData;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
import org.apache.fineract.portfolio.client.data.ClientData;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
|
||||
/**
|
||||
* Immutable data object representing a savings account.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class StandingInstructionData {
|
||||
|
||||
@Getter
|
||||
private final Long id;
|
||||
@Getter
|
||||
private final Long accountDetailId;
|
||||
@Getter
|
||||
private final String name;
|
||||
private final OfficeData fromOffice;
|
||||
@Getter
|
||||
private final ClientData fromClient;
|
||||
private final EnumOptionData fromAccountType;
|
||||
@Getter
|
||||
private final PortfolioAccountData fromAccount;
|
||||
private final OfficeData toOffice;
|
||||
@Getter
|
||||
private final ClientData toClient;
|
||||
private final EnumOptionData toAccountType;
|
||||
@Getter
|
||||
private final PortfolioAccountData toAccount;
|
||||
private final EnumOptionData transferType;
|
||||
private final EnumOptionData priority;
|
||||
private final EnumOptionData instructionType;
|
||||
@Getter
|
||||
private final EnumOptionData status;
|
||||
@Getter
|
||||
private final BigDecimal amount;
|
||||
@Getter
|
||||
private final LocalDate validFrom;
|
||||
private final LocalDate validTill;
|
||||
private final EnumOptionData recurrenceType;
|
||||
private final EnumOptionData recurrenceFrequency;
|
||||
@Getter
|
||||
private final Integer recurrenceInterval;
|
||||
private final MonthDay recurrenceOnMonthDay;
|
||||
private final Page<AccountTransferData> transactions;
|
||||
|
||||
private final Collection<OfficeData> fromOfficeOptions;
|
||||
private final Collection<ClientData> fromClientOptions;
|
||||
private final Collection<EnumOptionData> fromAccountTypeOptions;
|
||||
private final Collection<PortfolioAccountData> fromAccountOptions;
|
||||
private final Collection<OfficeData> toOfficeOptions;
|
||||
private final Collection<ClientData> toClientOptions;
|
||||
private final Collection<EnumOptionData> toAccountTypeOptions;
|
||||
private final Collection<PortfolioAccountData> toAccountOptions;
|
||||
private final Collection<EnumOptionData> transferTypeOptions;
|
||||
private final Collection<EnumOptionData> statusOptions;
|
||||
private final Collection<EnumOptionData> instructionTypeOptions;
|
||||
private final Collection<EnumOptionData> priorityOptions;
|
||||
private final Collection<EnumOptionData> recurrenceTypeOptions;
|
||||
private final Collection<EnumOptionData> recurrenceFrequencyOptions;
|
||||
|
||||
public static StandingInstructionData template(final Collection<OfficeData> fromOfficeOptions,
|
||||
final Collection<ClientData> fromClientOptions, final Collection<EnumOptionData> fromAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> fromAccountOptions, final Collection<OfficeData> toOfficeOptions,
|
||||
final Collection<ClientData> toClientOptions, final Collection<EnumOptionData> toAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> toAccountOptions, final Collection<EnumOptionData> transferTypeOptions,
|
||||
final Collection<EnumOptionData> statusOptions, final Collection<EnumOptionData> instructionTypeOptions,
|
||||
final Collection<EnumOptionData> priorityOptions, final Collection<EnumOptionData> recurrenceTypeOptions,
|
||||
final Collection<EnumOptionData> recurrenceFrequencyOptions) {
|
||||
|
||||
final Long id = null;
|
||||
final Long accountDetailId = null;
|
||||
final String name = null;
|
||||
final OfficeData fromOffice = null;
|
||||
final OfficeData toOffice = null;
|
||||
final ClientData fromClient = null;
|
||||
final ClientData toClient = null;
|
||||
final EnumOptionData fromAccountType = null;
|
||||
final PortfolioAccountData fromAccount = null;
|
||||
final EnumOptionData toAccountType = null;
|
||||
final PortfolioAccountData toAccount = null;
|
||||
final EnumOptionData transferType = null;
|
||||
final EnumOptionData priority = null;
|
||||
final EnumOptionData instructionType = null;
|
||||
final EnumOptionData status = null;
|
||||
final BigDecimal amount = null;
|
||||
final LocalDate validFrom = null;
|
||||
final LocalDate validTill = null;
|
||||
final EnumOptionData recurrenceType = null;
|
||||
final EnumOptionData recurrenceFrequency = null;
|
||||
final Integer recurrenceInterval = null;
|
||||
final MonthDay recurrenceOnMonthDay = null;
|
||||
final Page<AccountTransferData> transactions = null;
|
||||
|
||||
return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
|
||||
toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
|
||||
recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
|
||||
fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
|
||||
toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
|
||||
recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public static StandingInstructionData instance(final Long id, final Long accountDetailId, final String name,
|
||||
final OfficeData fromOffice, final OfficeData toOffice, final ClientData fromClient, final ClientData toClient,
|
||||
final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final EnumOptionData toAccountType,
|
||||
final PortfolioAccountData toAccount, final EnumOptionData transferType, final EnumOptionData priority,
|
||||
final EnumOptionData instructionType, final EnumOptionData status, final BigDecimal amount, final LocalDate validFrom,
|
||||
final LocalDate validTill, final EnumOptionData recurrenceType, final EnumOptionData recurrenceFrequency,
|
||||
final Integer recurrenceInterval, final MonthDay recurrenceOnMonthDay) {
|
||||
final Page<AccountTransferData> transactions = null;
|
||||
final Collection<OfficeData> fromOfficeOptions = null;
|
||||
final Collection<ClientData> fromClientOptions = null;
|
||||
final Collection<EnumOptionData> fromAccountTypeOptions = null;
|
||||
final Collection<PortfolioAccountData> fromAccountOptions = null;
|
||||
final Collection<OfficeData> toOfficeOptions = null;
|
||||
final Collection<ClientData> toClientOptions = null;
|
||||
final Collection<EnumOptionData> toAccountTypeOptions = null;
|
||||
final Collection<PortfolioAccountData> toAccountOptions = null;
|
||||
final Collection<EnumOptionData> transferTypeOptions = null;
|
||||
final Collection<EnumOptionData> statusOptions = null;
|
||||
final Collection<EnumOptionData> instructionTypeOptions = null;
|
||||
final Collection<EnumOptionData> priorityOptions = null;
|
||||
final Collection<EnumOptionData> recurrenceTypeOptions = null;
|
||||
final Collection<EnumOptionData> recurrenceFrequencyOptions = null;
|
||||
|
||||
return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
|
||||
toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
|
||||
recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
|
||||
fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
|
||||
toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
|
||||
recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public static StandingInstructionData withTemplateData(StandingInstructionData instructionData, StandingInstructionData templateData) {
|
||||
return new StandingInstructionData(instructionData.id, instructionData.accountDetailId, instructionData.name,
|
||||
instructionData.fromOffice, instructionData.fromClient, instructionData.fromAccountType, instructionData.fromAccount,
|
||||
instructionData.toOffice, instructionData.toClient, instructionData.toAccountType, instructionData.toAccount,
|
||||
instructionData.transferType, instructionData.priority, instructionData.instructionType, instructionData.status,
|
||||
instructionData.amount, instructionData.validFrom, instructionData.validTill, instructionData.recurrenceType,
|
||||
instructionData.recurrenceFrequency, instructionData.recurrenceInterval, instructionData.recurrenceOnMonthDay,
|
||||
instructionData.transactions, templateData.fromOfficeOptions, templateData.fromClientOptions,
|
||||
templateData.fromAccountTypeOptions, templateData.fromAccountOptions, templateData.toOfficeOptions,
|
||||
templateData.toClientOptions, templateData.toAccountTypeOptions, templateData.toAccountOptions,
|
||||
templateData.transferTypeOptions, templateData.statusOptions, templateData.instructionTypeOptions,
|
||||
templateData.priorityOptions, templateData.recurrenceTypeOptions, templateData.recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
private StandingInstructionData(final Long id, final Long accountDetailId, final String name, final OfficeData fromOffice,
|
||||
final ClientData fromClient, final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount,
|
||||
final OfficeData toOffice, final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
|
||||
final EnumOptionData transferType, final EnumOptionData priority, final EnumOptionData instructionType,
|
||||
final EnumOptionData status, final BigDecimal amount, final LocalDate validFrom, LocalDate validTill,
|
||||
final EnumOptionData recurrenceType, final EnumOptionData recurrenceFrequency, final Integer recurrenceInterval,
|
||||
final MonthDay recurrenceOnMonthDay, final Page<AccountTransferData> transactions,
|
||||
final Collection<OfficeData> fromOfficeOptions, final Collection<ClientData> fromClientOptions,
|
||||
final Collection<EnumOptionData> fromAccountTypeOptions, final Collection<PortfolioAccountData> fromAccountOptions,
|
||||
final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
|
||||
final Collection<EnumOptionData> toAccountTypeOptions, final Collection<PortfolioAccountData> toAccountOptions,
|
||||
final Collection<EnumOptionData> transferTypeOptions, final Collection<EnumOptionData> statusOptions,
|
||||
final Collection<EnumOptionData> instructionTypeOptions, final Collection<EnumOptionData> priorityOptions,
|
||||
final Collection<EnumOptionData> recurrenceTypeOptions, final Collection<EnumOptionData> recurrenceFrequencyOptions) {
|
||||
this.id = id;
|
||||
this.accountDetailId = accountDetailId;
|
||||
this.name = name;
|
||||
this.fromOffice = fromOffice;
|
||||
this.fromClient = fromClient;
|
||||
this.fromAccountType = fromAccountType;
|
||||
this.fromAccount = fromAccount;
|
||||
this.toOffice = toOffice;
|
||||
this.toClient = toClient;
|
||||
this.toAccountType = toAccountType;
|
||||
this.toAccount = toAccount;
|
||||
this.transferType = transferType;
|
||||
|
||||
this.priority = priority;
|
||||
this.instructionType = instructionType;
|
||||
this.status = status;
|
||||
this.amount = amount;
|
||||
this.validFrom = validFrom;
|
||||
this.validTill = validTill;
|
||||
this.recurrenceType = recurrenceType;
|
||||
this.recurrenceFrequency = recurrenceFrequency;
|
||||
this.recurrenceInterval = recurrenceInterval;
|
||||
this.recurrenceOnMonthDay = recurrenceOnMonthDay;
|
||||
|
||||
this.fromOfficeOptions = fromOfficeOptions;
|
||||
this.fromClientOptions = fromClientOptions;
|
||||
this.fromAccountTypeOptions = fromAccountTypeOptions;
|
||||
this.fromAccountOptions = fromAccountOptions;
|
||||
this.toOfficeOptions = toOfficeOptions;
|
||||
this.toClientOptions = toClientOptions;
|
||||
this.toAccountTypeOptions = toAccountTypeOptions;
|
||||
this.toAccountOptions = toAccountOptions;
|
||||
this.transferTypeOptions = transferTypeOptions;
|
||||
this.statusOptions = statusOptions;
|
||||
this.instructionTypeOptions = instructionTypeOptions;
|
||||
this.priorityOptions = priorityOptions;
|
||||
this.recurrenceTypeOptions = recurrenceTypeOptions;
|
||||
this.recurrenceFrequencyOptions = recurrenceFrequencyOptions;
|
||||
this.transactions = transactions;
|
||||
}
|
||||
|
||||
public static StandingInstructionData template(OfficeData fromOffice, ClientData fromClient, EnumOptionData fromAccountType,
|
||||
PortfolioAccountData fromAccount, LocalDate transferDate, OfficeData toOffice, ClientData toClient,
|
||||
EnumOptionData toAccountType, PortfolioAccountData toAccount, final Collection<OfficeData> fromOfficeOptions,
|
||||
final Collection<ClientData> fromClientOptions, final Collection<EnumOptionData> fromAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> fromAccountOptions, final Collection<OfficeData> toOfficeOptions,
|
||||
final Collection<ClientData> toClientOptions, final Collection<EnumOptionData> toAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> toAccountOptions, final Collection<EnumOptionData> transferTypeOptions,
|
||||
final Collection<EnumOptionData> statusOptions, final Collection<EnumOptionData> instructionTypeOptions,
|
||||
final Collection<EnumOptionData> priorityOptions, final Collection<EnumOptionData> recurrenceTypeOptions,
|
||||
final Collection<EnumOptionData> recurrenceFrequencyOptions) {
|
||||
final Long id = null;
|
||||
final Long accountDetailId = null;
|
||||
final String name = null;
|
||||
final EnumOptionData transferType = null;
|
||||
final EnumOptionData priority = null;
|
||||
final EnumOptionData instructionType = null;
|
||||
final EnumOptionData status = null;
|
||||
final BigDecimal amount = null;
|
||||
final LocalDate validFrom = null;
|
||||
final LocalDate validTill = null;
|
||||
final EnumOptionData recurrenceType = null;
|
||||
final EnumOptionData recurrenceFrequency = null;
|
||||
final Integer recurrenceInterval = null;
|
||||
final MonthDay recurrenceOnMonthDay = null;
|
||||
final Page<AccountTransferData> transactions = null;
|
||||
|
||||
return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
|
||||
toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
|
||||
recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
|
||||
fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
|
||||
toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
|
||||
recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public static StandingInstructionData withTransferData(StandingInstructionData instructionData,
|
||||
final Page<AccountTransferData> transactions) {
|
||||
return new StandingInstructionData(instructionData.id, instructionData.accountDetailId, instructionData.name,
|
||||
instructionData.fromOffice, instructionData.fromClient, instructionData.fromAccountType, instructionData.fromAccount,
|
||||
instructionData.toOffice, instructionData.toClient, instructionData.toAccountType, instructionData.toAccount,
|
||||
instructionData.transferType, instructionData.priority, instructionData.instructionType, instructionData.status,
|
||||
instructionData.amount, instructionData.validFrom, instructionData.validTill, instructionData.recurrenceType,
|
||||
instructionData.recurrenceFrequency, instructionData.recurrenceInterval, instructionData.recurrenceOnMonthDay, transactions,
|
||||
instructionData.fromOfficeOptions, instructionData.fromClientOptions, instructionData.fromAccountTypeOptions,
|
||||
instructionData.fromAccountOptions, instructionData.toOfficeOptions, instructionData.toClientOptions,
|
||||
instructionData.toAccountTypeOptions, instructionData.toAccountOptions, instructionData.transferTypeOptions,
|
||||
instructionData.statusOptions, instructionData.instructionTypeOptions, instructionData.priorityOptions,
|
||||
instructionData.recurrenceTypeOptions, instructionData.recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public StandingInstructionType getInstructionType() {
|
||||
return Optional.ofNullable(this.instructionType).map(e -> StandingInstructionType.fromInt(e.getId().intValue())).orElse(null);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferRecurrenceType getRecurrenceType() {
|
||||
return Optional.ofNullable(this.recurrenceType).map(e -> AccountTransferRecurrenceType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public PeriodFrequencyType getRecurrenceFrequency() {
|
||||
return Optional.ofNullable(this.recurrenceFrequency).map(e -> PeriodFrequencyType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public PortfolioAccountType getFromAccountType() {
|
||||
return Optional.ofNullable(this.fromAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public PortfolioAccountType getToAccountType() {
|
||||
return Optional.ofNullable(this.toAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public AccountTransferType getTransferType() {
|
||||
return Optional.ofNullable(this.transferType).map(e -> AccountTransferType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public Integer getRecurrenceOnDay() {
|
||||
return Optional.ofNullable(this.recurrenceOnMonthDay).map(monthDay -> monthDay.get(ChronoField.DAY_OF_MONTH)).orElse(0);
|
||||
}
|
||||
|
||||
public Integer getRecurrenceOnMonth() {
|
||||
return Optional.ofNullable(this.recurrenceOnMonthDay).map(monthDay -> monthDay.get(ChronoField.MONTH_OF_YEAR)).orElse(0);
|
||||
}
|
||||
|
||||
public Integer toTransferType() {
|
||||
Integer transferType = null;
|
||||
AccountTransferType accountTransferType = getTransferType();
|
||||
if (accountTransferType.isChargePayment()) {
|
||||
transferType = LoanTransactionType.CHARGE_PAYMENT.getValue();
|
||||
} else if (accountTransferType.isLoanRepayment()) {
|
||||
transferType = LoanTransactionType.REPAYMENT.getValue();
|
||||
}
|
||||
return transferType;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.infrastructure.core.service.Page;
|
||||
import org.apache.fineract.organisation.office.data.OfficeData;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
import org.apache.fineract.portfolio.client.data.ClientData;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
|
||||
/**
|
||||
* Immutable data object representing a savings account.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class StandingInstructionData {
|
||||
|
||||
@Getter
|
||||
private final Long id;
|
||||
@Getter
|
||||
private final Long accountDetailId;
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final OfficeData fromOffice;
|
||||
@Getter
|
||||
private final ClientData fromClient;
|
||||
@Getter
|
||||
private final EnumOptionData fromAccountType;
|
||||
@Getter
|
||||
private final PortfolioAccountData fromAccount;
|
||||
@Getter
|
||||
private final OfficeData toOffice;
|
||||
@Getter
|
||||
private final ClientData toClient;
|
||||
@Getter
|
||||
private final EnumOptionData toAccountType;
|
||||
@Getter
|
||||
private final PortfolioAccountData toAccount;
|
||||
@Getter
|
||||
private final EnumOptionData transferType;
|
||||
@Getter
|
||||
private final EnumOptionData priority;
|
||||
@Getter
|
||||
private final EnumOptionData instructionType;
|
||||
@Getter
|
||||
private final EnumOptionData status;
|
||||
@Getter
|
||||
private final BigDecimal amount;
|
||||
@Getter
|
||||
private final LocalDate validFrom;
|
||||
@Getter
|
||||
private final LocalDate validTill;
|
||||
@Getter
|
||||
private final EnumOptionData recurrenceType;
|
||||
@Getter
|
||||
private final EnumOptionData recurrenceFrequency;
|
||||
@Getter
|
||||
private final Integer recurrenceInterval;
|
||||
@Getter
|
||||
private final MonthDay recurrenceOnMonthDay;
|
||||
@Getter
|
||||
private final Page<AccountTransferData> transactions;
|
||||
|
||||
@Getter
|
||||
private final Collection<OfficeData> fromOfficeOptions;
|
||||
@Getter
|
||||
private final Collection<ClientData> fromClientOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> fromAccountTypeOptions;
|
||||
@Getter
|
||||
private final Collection<PortfolioAccountData> fromAccountOptions;
|
||||
@Getter
|
||||
private final Collection<OfficeData> toOfficeOptions;
|
||||
@Getter
|
||||
private final Collection<ClientData> toClientOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> toAccountTypeOptions;
|
||||
@Getter
|
||||
private final Collection<PortfolioAccountData> toAccountOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> transferTypeOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> statusOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> instructionTypeOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> priorityOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> recurrenceTypeOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> recurrenceFrequencyOptions;
|
||||
|
||||
public static StandingInstructionData template(final Collection<OfficeData> fromOfficeOptions,
|
||||
final Collection<ClientData> fromClientOptions, final Collection<EnumOptionData> fromAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> fromAccountOptions, final Collection<OfficeData> toOfficeOptions,
|
||||
final Collection<ClientData> toClientOptions, final Collection<EnumOptionData> toAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> toAccountOptions, final Collection<EnumOptionData> transferTypeOptions,
|
||||
final Collection<EnumOptionData> statusOptions, final Collection<EnumOptionData> instructionTypeOptions,
|
||||
final Collection<EnumOptionData> priorityOptions, final Collection<EnumOptionData> recurrenceTypeOptions,
|
||||
final Collection<EnumOptionData> recurrenceFrequencyOptions) {
|
||||
|
||||
final Long id = null;
|
||||
final Long accountDetailId = null;
|
||||
final String name = null;
|
||||
final OfficeData fromOffice = null;
|
||||
final OfficeData toOffice = null;
|
||||
final ClientData fromClient = null;
|
||||
final ClientData toClient = null;
|
||||
final EnumOptionData fromAccountType = null;
|
||||
final PortfolioAccountData fromAccount = null;
|
||||
final EnumOptionData toAccountType = null;
|
||||
final PortfolioAccountData toAccount = null;
|
||||
final EnumOptionData transferType = null;
|
||||
final EnumOptionData priority = null;
|
||||
final EnumOptionData instructionType = null;
|
||||
final EnumOptionData status = null;
|
||||
final BigDecimal amount = null;
|
||||
final LocalDate validFrom = null;
|
||||
final LocalDate validTill = null;
|
||||
final EnumOptionData recurrenceType = null;
|
||||
final EnumOptionData recurrenceFrequency = null;
|
||||
final Integer recurrenceInterval = null;
|
||||
final MonthDay recurrenceOnMonthDay = null;
|
||||
final Page<AccountTransferData> transactions = null;
|
||||
|
||||
return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
|
||||
toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
|
||||
recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
|
||||
fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
|
||||
toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
|
||||
recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public static StandingInstructionData instance(final Long id, final Long accountDetailId, final String name,
|
||||
final OfficeData fromOffice, final OfficeData toOffice, final ClientData fromClient, final ClientData toClient,
|
||||
final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final EnumOptionData toAccountType,
|
||||
final PortfolioAccountData toAccount, final EnumOptionData transferType, final EnumOptionData priority,
|
||||
final EnumOptionData instructionType, final EnumOptionData status, final BigDecimal amount, final LocalDate validFrom,
|
||||
final LocalDate validTill, final EnumOptionData recurrenceType, final EnumOptionData recurrenceFrequency,
|
||||
final Integer recurrenceInterval, final MonthDay recurrenceOnMonthDay) {
|
||||
final Page<AccountTransferData> transactions = null;
|
||||
final Collection<OfficeData> fromOfficeOptions = null;
|
||||
final Collection<ClientData> fromClientOptions = null;
|
||||
final Collection<EnumOptionData> fromAccountTypeOptions = null;
|
||||
final Collection<PortfolioAccountData> fromAccountOptions = null;
|
||||
final Collection<OfficeData> toOfficeOptions = null;
|
||||
final Collection<ClientData> toClientOptions = null;
|
||||
final Collection<EnumOptionData> toAccountTypeOptions = null;
|
||||
final Collection<PortfolioAccountData> toAccountOptions = null;
|
||||
final Collection<EnumOptionData> transferTypeOptions = null;
|
||||
final Collection<EnumOptionData> statusOptions = null;
|
||||
final Collection<EnumOptionData> instructionTypeOptions = null;
|
||||
final Collection<EnumOptionData> priorityOptions = null;
|
||||
final Collection<EnumOptionData> recurrenceTypeOptions = null;
|
||||
final Collection<EnumOptionData> recurrenceFrequencyOptions = null;
|
||||
|
||||
return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
|
||||
toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
|
||||
recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
|
||||
fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
|
||||
toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
|
||||
recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public static StandingInstructionData withTemplateData(StandingInstructionData instructionData, StandingInstructionData templateData) {
|
||||
return new StandingInstructionData(instructionData.id, instructionData.accountDetailId, instructionData.name,
|
||||
instructionData.fromOffice, instructionData.fromClient, instructionData.fromAccountType, instructionData.fromAccount,
|
||||
instructionData.toOffice, instructionData.toClient, instructionData.toAccountType, instructionData.toAccount,
|
||||
instructionData.transferType, instructionData.priority, instructionData.instructionType, instructionData.status,
|
||||
instructionData.amount, instructionData.validFrom, instructionData.validTill, instructionData.recurrenceType,
|
||||
instructionData.recurrenceFrequency, instructionData.recurrenceInterval, instructionData.recurrenceOnMonthDay,
|
||||
instructionData.transactions, templateData.fromOfficeOptions, templateData.fromClientOptions,
|
||||
templateData.fromAccountTypeOptions, templateData.fromAccountOptions, templateData.toOfficeOptions,
|
||||
templateData.toClientOptions, templateData.toAccountTypeOptions, templateData.toAccountOptions,
|
||||
templateData.transferTypeOptions, templateData.statusOptions, templateData.instructionTypeOptions,
|
||||
templateData.priorityOptions, templateData.recurrenceTypeOptions, templateData.recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
private StandingInstructionData(final Long id, final Long accountDetailId, final String name, final OfficeData fromOffice,
|
||||
final ClientData fromClient, final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount,
|
||||
final OfficeData toOffice, final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
|
||||
final EnumOptionData transferType, final EnumOptionData priority, final EnumOptionData instructionType,
|
||||
final EnumOptionData status, final BigDecimal amount, final LocalDate validFrom, LocalDate validTill,
|
||||
final EnumOptionData recurrenceType, final EnumOptionData recurrenceFrequency, final Integer recurrenceInterval,
|
||||
final MonthDay recurrenceOnMonthDay, final Page<AccountTransferData> transactions,
|
||||
final Collection<OfficeData> fromOfficeOptions, final Collection<ClientData> fromClientOptions,
|
||||
final Collection<EnumOptionData> fromAccountTypeOptions, final Collection<PortfolioAccountData> fromAccountOptions,
|
||||
final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
|
||||
final Collection<EnumOptionData> toAccountTypeOptions, final Collection<PortfolioAccountData> toAccountOptions,
|
||||
final Collection<EnumOptionData> transferTypeOptions, final Collection<EnumOptionData> statusOptions,
|
||||
final Collection<EnumOptionData> instructionTypeOptions, final Collection<EnumOptionData> priorityOptions,
|
||||
final Collection<EnumOptionData> recurrenceTypeOptions, final Collection<EnumOptionData> recurrenceFrequencyOptions) {
|
||||
this.id = id;
|
||||
this.accountDetailId = accountDetailId;
|
||||
this.name = name;
|
||||
this.fromOffice = fromOffice;
|
||||
this.fromClient = fromClient;
|
||||
this.fromAccountType = fromAccountType;
|
||||
this.fromAccount = fromAccount;
|
||||
this.toOffice = toOffice;
|
||||
this.toClient = toClient;
|
||||
this.toAccountType = toAccountType;
|
||||
this.toAccount = toAccount;
|
||||
this.transferType = transferType;
|
||||
|
||||
this.priority = priority;
|
||||
this.instructionType = instructionType;
|
||||
this.status = status;
|
||||
this.amount = amount;
|
||||
this.validFrom = validFrom;
|
||||
this.validTill = validTill;
|
||||
this.recurrenceType = recurrenceType;
|
||||
this.recurrenceFrequency = recurrenceFrequency;
|
||||
this.recurrenceInterval = recurrenceInterval;
|
||||
this.recurrenceOnMonthDay = recurrenceOnMonthDay;
|
||||
|
||||
this.fromOfficeOptions = fromOfficeOptions;
|
||||
this.fromClientOptions = fromClientOptions;
|
||||
this.fromAccountTypeOptions = fromAccountTypeOptions;
|
||||
this.fromAccountOptions = fromAccountOptions;
|
||||
this.toOfficeOptions = toOfficeOptions;
|
||||
this.toClientOptions = toClientOptions;
|
||||
this.toAccountTypeOptions = toAccountTypeOptions;
|
||||
this.toAccountOptions = toAccountOptions;
|
||||
this.transferTypeOptions = transferTypeOptions;
|
||||
this.statusOptions = statusOptions;
|
||||
this.instructionTypeOptions = instructionTypeOptions;
|
||||
this.priorityOptions = priorityOptions;
|
||||
this.recurrenceTypeOptions = recurrenceTypeOptions;
|
||||
this.recurrenceFrequencyOptions = recurrenceFrequencyOptions;
|
||||
this.transactions = transactions;
|
||||
}
|
||||
|
||||
public static StandingInstructionData template(OfficeData fromOffice, ClientData fromClient, EnumOptionData fromAccountType,
|
||||
PortfolioAccountData fromAccount, LocalDate transferDate, OfficeData toOffice, ClientData toClient,
|
||||
EnumOptionData toAccountType, PortfolioAccountData toAccount, final Collection<OfficeData> fromOfficeOptions,
|
||||
final Collection<ClientData> fromClientOptions, final Collection<EnumOptionData> fromAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> fromAccountOptions, final Collection<OfficeData> toOfficeOptions,
|
||||
final Collection<ClientData> toClientOptions, final Collection<EnumOptionData> toAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> toAccountOptions, final Collection<EnumOptionData> transferTypeOptions,
|
||||
final Collection<EnumOptionData> statusOptions, final Collection<EnumOptionData> instructionTypeOptions,
|
||||
final Collection<EnumOptionData> priorityOptions, final Collection<EnumOptionData> recurrenceTypeOptions,
|
||||
final Collection<EnumOptionData> recurrenceFrequencyOptions) {
|
||||
final Long id = null;
|
||||
final Long accountDetailId = null;
|
||||
final String name = null;
|
||||
final EnumOptionData transferType = null;
|
||||
final EnumOptionData priority = null;
|
||||
final EnumOptionData instructionType = null;
|
||||
final EnumOptionData status = null;
|
||||
final BigDecimal amount = null;
|
||||
final LocalDate validFrom = null;
|
||||
final LocalDate validTill = null;
|
||||
final EnumOptionData recurrenceType = null;
|
||||
final EnumOptionData recurrenceFrequency = null;
|
||||
final Integer recurrenceInterval = null;
|
||||
final MonthDay recurrenceOnMonthDay = null;
|
||||
final Page<AccountTransferData> transactions = null;
|
||||
|
||||
return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
|
||||
toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
|
||||
recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
|
||||
fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
|
||||
toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
|
||||
recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public static StandingInstructionData withTransferData(StandingInstructionData instructionData,
|
||||
final Page<AccountTransferData> transactions) {
|
||||
return new StandingInstructionData(instructionData.id, instructionData.accountDetailId, instructionData.name,
|
||||
instructionData.fromOffice, instructionData.fromClient, instructionData.fromAccountType, instructionData.fromAccount,
|
||||
instructionData.toOffice, instructionData.toClient, instructionData.toAccountType, instructionData.toAccount,
|
||||
instructionData.transferType, instructionData.priority, instructionData.instructionType, instructionData.status,
|
||||
instructionData.amount, instructionData.validFrom, instructionData.validTill, instructionData.recurrenceType,
|
||||
instructionData.recurrenceFrequency, instructionData.recurrenceInterval, instructionData.recurrenceOnMonthDay, transactions,
|
||||
instructionData.fromOfficeOptions, instructionData.fromClientOptions, instructionData.fromAccountTypeOptions,
|
||||
instructionData.fromAccountOptions, instructionData.toOfficeOptions, instructionData.toClientOptions,
|
||||
instructionData.toAccountTypeOptions, instructionData.toAccountOptions, instructionData.transferTypeOptions,
|
||||
instructionData.statusOptions, instructionData.instructionTypeOptions, instructionData.priorityOptions,
|
||||
instructionData.recurrenceTypeOptions, instructionData.recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the domain enum for instruction type — used internally by business logic.
|
||||
* NOTE: renamed from getInstructionType() to avoid overriding Lombok @Getter (which must
|
||||
* return EnumOptionData so Jackson serializes it as an object, not a plain string).
|
||||
*/
|
||||
public StandingInstructionType instructionTypeEnum() {
|
||||
return Optional.ofNullable(this.instructionType).map(e -> StandingInstructionType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public AccountTransferRecurrenceType recurrenceTypeEnum() {
|
||||
return Optional.ofNullable(this.recurrenceType).map(e -> AccountTransferRecurrenceType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public PeriodFrequencyType recurrenceFrequencyEnum() {
|
||||
return Optional.ofNullable(this.recurrenceFrequency).map(e -> PeriodFrequencyType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public PortfolioAccountType fromAccountTypeEnum() {
|
||||
return Optional.ofNullable(this.fromAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public PortfolioAccountType toAccountTypeEnum() {
|
||||
return Optional.ofNullable(this.toAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public AccountTransferType transferTypeEnum() {
|
||||
return Optional.ofNullable(this.transferType).map(e -> AccountTransferType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public Integer getRecurrenceOnDay() {
|
||||
return Optional.ofNullable(this.recurrenceOnMonthDay).map(monthDay -> monthDay.get(ChronoField.DAY_OF_MONTH)).orElse(0);
|
||||
}
|
||||
|
||||
public Integer getRecurrenceOnMonth() {
|
||||
return Optional.ofNullable(this.recurrenceOnMonthDay).map(monthDay -> monthDay.get(ChronoField.MONTH_OF_YEAR)).orElse(0);
|
||||
}
|
||||
|
||||
public Integer toTransferType() {
|
||||
Integer transferType = null;
|
||||
AccountTransferType accountTransferType = transferTypeEnum();
|
||||
if (accountTransferType != null && accountTransferType.isChargePayment()) {
|
||||
transferType = LoanTransactionType.CHARGE_PAYMENT.getValue();
|
||||
} else if (accountTransferType != null && accountTransferType.isLoanRepayment()) {
|
||||
transferType = LoanTransactionType.REPAYMENT.getValue();
|
||||
}
|
||||
return transferType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+296
-294
@@ -1,294 +1,296 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.portfolio.account.AccountDetailConstants;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class StandingInstructionDataValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final AccountTransfersDetailDataValidator accountTransfersDetailDataValidator;
|
||||
private static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(AccountDetailConstants.localeParamName,
|
||||
AccountDetailConstants.dateFormatParamName, AccountDetailConstants.fromOfficeIdParamName,
|
||||
AccountDetailConstants.fromClientIdParamName, AccountDetailConstants.fromAccountTypeParamName,
|
||||
AccountDetailConstants.fromAccountIdParamName, AccountDetailConstants.toOfficeIdParamName,
|
||||
AccountDetailConstants.toClientIdParamName, AccountDetailConstants.toAccountTypeParamName,
|
||||
AccountDetailConstants.toAccountIdParamName, AccountDetailConstants.transferTypeParamName,
|
||||
StandingInstructionApiConstants.priorityParamName, StandingInstructionApiConstants.instructionTypeParamName,
|
||||
StandingInstructionApiConstants.statusParamName, StandingInstructionApiConstants.amountParamName,
|
||||
StandingInstructionApiConstants.validFromParamName, StandingInstructionApiConstants.validTillParamName,
|
||||
StandingInstructionApiConstants.recurrenceTypeParamName, StandingInstructionApiConstants.recurrenceFrequencyParamName,
|
||||
StandingInstructionApiConstants.recurrenceIntervalParamName, StandingInstructionApiConstants.recurrenceOnMonthDayParamName,
|
||||
StandingInstructionApiConstants.nameParamName, StandingInstructionApiConstants.monthDayFormatParamName));
|
||||
|
||||
private static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(AccountDetailConstants.localeParamName,
|
||||
AccountDetailConstants.dateFormatParamName, StandingInstructionApiConstants.priorityParamName,
|
||||
StandingInstructionApiConstants.instructionTypeParamName, StandingInstructionApiConstants.statusParamName,
|
||||
StandingInstructionApiConstants.amountParamName, StandingInstructionApiConstants.validFromParamName,
|
||||
StandingInstructionApiConstants.validTillParamName, StandingInstructionApiConstants.recurrenceTypeParamName,
|
||||
StandingInstructionApiConstants.recurrenceFrequencyParamName, StandingInstructionApiConstants.recurrenceIntervalParamName,
|
||||
StandingInstructionApiConstants.recurrenceOnMonthDayParamName, StandingInstructionApiConstants.monthDayFormatParamName));
|
||||
|
||||
@Autowired
|
||||
public StandingInstructionDataValidator(final FromJsonHelper fromApiJsonHelper,
|
||||
final AccountTransfersDetailDataValidator accountTransfersDetailDataValidator) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.accountTransfersDetailDataValidator = accountTransfersDetailDataValidator;
|
||||
}
|
||||
|
||||
public void validateForCreate(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, CREATE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
this.accountTransfersDetailDataValidator.validate(command, baseDataValidator);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Integer status = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.statusParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.statusParamName).value(status).notNull().inMinMaxRange(1, 2);
|
||||
|
||||
final LocalDate validFrom = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validFromParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validFromParamName).value(validFrom).notNull();
|
||||
|
||||
final LocalDate validTill = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validTillParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validTillParamName).value(validTill)
|
||||
.validateDateAfter(validFrom);
|
||||
|
||||
final BigDecimal transferAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(StandingInstructionApiConstants.amountParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.amountParamName).value(transferAmount).positiveAmount();
|
||||
|
||||
final Integer transferType = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(transferTypeParamName).value(transferType).notNull().inMinMaxRange(1, 3);
|
||||
|
||||
final Integer priority = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.priorityParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.priorityParamName).value(priority).notNull().inMinMaxRange(1,
|
||||
4);
|
||||
|
||||
final Integer standingInstructionType = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.instructionTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||
.notNull().inMinMaxRange(1, 2);
|
||||
|
||||
final Integer recurrenceType = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.recurrenceTypeParamName,
|
||||
element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||
.inMinMaxRange(1, 2);
|
||||
boolean isPeriodic = false;
|
||||
if (recurrenceType != null) {
|
||||
isPeriodic = AccountTransferRecurrenceType.fromInt(recurrenceType).isPeriodicRecurrence();
|
||||
}
|
||||
|
||||
final Integer recurrenceFrequency = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceFrequencyParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceFrequencyParamName).value(recurrenceFrequency)
|
||||
.inMinMaxRange(0, 3);
|
||||
|
||||
if (recurrenceFrequency != null) {
|
||||
PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(recurrenceFrequency);
|
||||
if (frequencyType.isMonthly() || frequencyType.isYearly()) {
|
||||
final MonthDay monthDay = this.fromApiJsonHelper
|
||||
.extractMonthDayNamed(StandingInstructionApiConstants.recurrenceOnMonthDayParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceOnMonthDayParamName).value(monthDay)
|
||||
.notNull();
|
||||
}
|
||||
}
|
||||
|
||||
final Integer recurrenceInterval = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceIntervalParamName, element, Locale.getDefault());
|
||||
if (isPeriodic) {
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceIntervalParamName).value(recurrenceInterval)
|
||||
.notNull();
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceFrequencyParamName).value(recurrenceFrequency)
|
||||
.notNull();
|
||||
}
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceIntervalParamName).value(recurrenceInterval)
|
||||
.integerGreaterThanZero();
|
||||
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed(StandingInstructionApiConstants.nameParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.nameParamName).value(name).notNull();
|
||||
|
||||
final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
|
||||
if (toAccountType != null && PortfolioAccountType.fromInt(toAccountType).isSavingsAccount()) {
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||
.notNull().inMinMaxRange(1, 1);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||
.inMinMaxRange(1, 1);
|
||||
|
||||
}
|
||||
if (standingInstructionType != null && StandingInstructionType.fromInt(standingInstructionType).isFixedAmoutTransfer()) {
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.amountParamName).value(transferAmount).notNull();
|
||||
}
|
||||
|
||||
String errorCode = null;
|
||||
AccountTransferType accountTransferType = AccountTransferType.fromInt(transferType);
|
||||
final Integer fromAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
|
||||
if (fromAccountType != null && toAccountType != null) {
|
||||
PortfolioAccountType fromPortfolioAccountType = PortfolioAccountType.fromInt(fromAccountType);
|
||||
PortfolioAccountType toPortfolioAccountType = PortfolioAccountType.fromInt(toAccountType);
|
||||
if (accountTransferType.isAccountTransfer()
|
||||
&& (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isLoanAccount())) {
|
||||
errorCode = "not.account.transfer";
|
||||
} else if (accountTransferType.isLoanRepayment()
|
||||
&& (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isSavingsAccount())) {
|
||||
errorCode = "not.loan.repayment";
|
||||
}
|
||||
if (errorCode != null) {
|
||||
baseDataValidator.reset().parameter(transferTypeParamName).failWithCode(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateForUpdate(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, UPDATE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.validFromParamName, element)) {
|
||||
final LocalDate validFrom = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validFromParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validFromParamName).value(validFrom).notNull();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.validTillParamName, element)) {
|
||||
final LocalDate validTill = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validTillParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validTillParamName).value(validTill).notNull();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.amountParamName, element)) {
|
||||
final BigDecimal transferAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(StandingInstructionApiConstants.amountParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.amountParamName).value(transferAmount).positiveAmount();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.statusParamName, element)) {
|
||||
final Integer status = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.statusParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.statusParamName).value(status).notNull().inMinMaxRange(1,
|
||||
2);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.priorityParamName, element)) {
|
||||
final Integer priority = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.priorityParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.priorityParamName).value(priority).notNull()
|
||||
.inMinMaxRange(1, 4);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.instructionTypeParamName, element)) {
|
||||
final Integer standingInstructionType = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.instructionTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||
.notNull().inMinMaxRange(1, 2);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.recurrenceTypeParamName, element)) {
|
||||
final Integer recurrenceType = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||
.inMinMaxRange(1, 2);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.recurrenceFrequencyParamName, element)) {
|
||||
final Integer recurrenceFrequency = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceFrequencyParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceFrequencyParamName).value(recurrenceFrequency)
|
||||
.inMinMaxRange(0, 3);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.recurrenceIntervalParamName, element)) {
|
||||
final Integer recurrenceInterval = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceIntervalParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceIntervalParamName).value(recurrenceInterval)
|
||||
.integerGreaterThanZero();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.nameParamName, element)) {
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed(StandingInstructionApiConstants.nameParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.nameParamName).value(name).notNull();
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.portfolio.account.AccountDetailConstants;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class StandingInstructionDataValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final AccountTransfersDetailDataValidator accountTransfersDetailDataValidator;
|
||||
private static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(AccountDetailConstants.localeParamName,
|
||||
AccountDetailConstants.dateFormatParamName, AccountDetailConstants.fromOfficeIdParamName,
|
||||
AccountDetailConstants.fromClientIdParamName, AccountDetailConstants.fromAccountTypeParamName,
|
||||
AccountDetailConstants.fromAccountIdParamName, AccountDetailConstants.toOfficeIdParamName,
|
||||
AccountDetailConstants.toClientIdParamName, AccountDetailConstants.toAccountTypeParamName,
|
||||
AccountDetailConstants.toAccountIdParamName, AccountDetailConstants.transferTypeParamName,
|
||||
StandingInstructionApiConstants.priorityParamName, StandingInstructionApiConstants.instructionTypeParamName,
|
||||
StandingInstructionApiConstants.statusParamName, StandingInstructionApiConstants.amountParamName,
|
||||
StandingInstructionApiConstants.validFromParamName, StandingInstructionApiConstants.validTillParamName,
|
||||
StandingInstructionApiConstants.recurrenceTypeParamName, StandingInstructionApiConstants.recurrenceFrequencyParamName,
|
||||
StandingInstructionApiConstants.recurrenceIntervalParamName, StandingInstructionApiConstants.recurrenceOnMonthDayParamName,
|
||||
StandingInstructionApiConstants.nameParamName, StandingInstructionApiConstants.monthDayFormatParamName));
|
||||
|
||||
private static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(AccountDetailConstants.localeParamName,
|
||||
AccountDetailConstants.dateFormatParamName, StandingInstructionApiConstants.priorityParamName,
|
||||
StandingInstructionApiConstants.instructionTypeParamName, StandingInstructionApiConstants.statusParamName,
|
||||
StandingInstructionApiConstants.amountParamName, StandingInstructionApiConstants.validFromParamName,
|
||||
StandingInstructionApiConstants.validTillParamName, StandingInstructionApiConstants.recurrenceTypeParamName,
|
||||
StandingInstructionApiConstants.recurrenceFrequencyParamName, StandingInstructionApiConstants.recurrenceIntervalParamName,
|
||||
StandingInstructionApiConstants.recurrenceOnMonthDayParamName, StandingInstructionApiConstants.monthDayFormatParamName));
|
||||
|
||||
@Autowired
|
||||
public StandingInstructionDataValidator(final FromJsonHelper fromApiJsonHelper,
|
||||
final AccountTransfersDetailDataValidator accountTransfersDetailDataValidator) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.accountTransfersDetailDataValidator = accountTransfersDetailDataValidator;
|
||||
}
|
||||
|
||||
public void validateForCreate(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, CREATE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
this.accountTransfersDetailDataValidator.validate(command, baseDataValidator);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Integer status = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.statusParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.statusParamName).value(status).notNull().inMinMaxRange(1, 2);
|
||||
|
||||
final LocalDate validFrom = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validFromParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validFromParamName).value(validFrom).notNull();
|
||||
|
||||
final LocalDate validTill = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validTillParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validTillParamName).value(validTill)
|
||||
.validateDateAfter(validFrom);
|
||||
|
||||
final BigDecimal transferAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(StandingInstructionApiConstants.amountParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.amountParamName).value(transferAmount).positiveAmount();
|
||||
|
||||
final Integer transferType = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(transferTypeParamName).value(transferType).notNull().inMinMaxRange(1, 3);
|
||||
|
||||
final Integer priority = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.priorityParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.priorityParamName).value(priority).notNull().inMinMaxRange(1,
|
||||
4);
|
||||
|
||||
final Integer standingInstructionType = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.instructionTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||
.notNull().inMinMaxRange(1, 2);
|
||||
|
||||
final Integer recurrenceType = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.recurrenceTypeParamName,
|
||||
element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||
.inMinMaxRange(1, 2);
|
||||
boolean isPeriodic = false;
|
||||
if (recurrenceType != null) {
|
||||
isPeriodic = AccountTransferRecurrenceType.fromInt(recurrenceType).isPeriodicRecurrence();
|
||||
}
|
||||
|
||||
final Integer recurrenceFrequency = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceFrequencyParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceFrequencyParamName).value(recurrenceFrequency)
|
||||
.inMinMaxRange(0, 3);
|
||||
|
||||
if (recurrenceFrequency != null) {
|
||||
PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(recurrenceFrequency);
|
||||
if (frequencyType.isMonthly() || frequencyType.isYearly()) {
|
||||
final MonthDay monthDay = this.fromApiJsonHelper
|
||||
.extractMonthDayNamed(StandingInstructionApiConstants.recurrenceOnMonthDayParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceOnMonthDayParamName).value(monthDay)
|
||||
.notNull();
|
||||
}
|
||||
}
|
||||
|
||||
final Integer recurrenceInterval = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceIntervalParamName, element, Locale.getDefault());
|
||||
if (isPeriodic) {
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceIntervalParamName).value(recurrenceInterval)
|
||||
.notNull();
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceFrequencyParamName).value(recurrenceFrequency)
|
||||
.notNull();
|
||||
}
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceIntervalParamName).value(recurrenceInterval)
|
||||
.integerGreaterThanZero();
|
||||
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed(StandingInstructionApiConstants.nameParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.nameParamName).value(name).notNull();
|
||||
|
||||
final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
|
||||
if (toAccountType != null && (PortfolioAccountType.fromInt(toAccountType).isSavingsAccount()
|
||||
|| PortfolioAccountType.fromInt(toAccountType).isCurrentAccount())) {
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||
.notNull().inMinMaxRange(1, 1);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||
.inMinMaxRange(1, 1);
|
||||
|
||||
}
|
||||
if (standingInstructionType != null && StandingInstructionType.fromInt(standingInstructionType).isFixedAmoutTransfer()) {
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.amountParamName).value(transferAmount).notNull();
|
||||
}
|
||||
|
||||
String errorCode = null;
|
||||
AccountTransferType accountTransferType = AccountTransferType.fromInt(transferType);
|
||||
final Integer fromAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
|
||||
if (fromAccountType != null && toAccountType != null) {
|
||||
PortfolioAccountType fromPortfolioAccountType = PortfolioAccountType.fromInt(fromAccountType);
|
||||
PortfolioAccountType toPortfolioAccountType = PortfolioAccountType.fromInt(toAccountType);
|
||||
if (accountTransferType.isAccountTransfer()
|
||||
&& (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isLoanAccount())) {
|
||||
errorCode = "not.account.transfer";
|
||||
} else if (accountTransferType.isLoanRepayment()
|
||||
&& (fromPortfolioAccountType.isLoanAccount()
|
||||
|| (toPortfolioAccountType.isSavingsAccount() || toPortfolioAccountType.isCurrentAccount()))) {
|
||||
errorCode = "not.loan.repayment";
|
||||
}
|
||||
if (errorCode != null) {
|
||||
baseDataValidator.reset().parameter(transferTypeParamName).failWithCode(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateForUpdate(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, UPDATE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.validFromParamName, element)) {
|
||||
final LocalDate validFrom = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validFromParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validFromParamName).value(validFrom).notNull();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.validTillParamName, element)) {
|
||||
final LocalDate validTill = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validTillParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validTillParamName).value(validTill).notNull();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.amountParamName, element)) {
|
||||
final BigDecimal transferAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(StandingInstructionApiConstants.amountParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.amountParamName).value(transferAmount).positiveAmount();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.statusParamName, element)) {
|
||||
final Integer status = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.statusParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.statusParamName).value(status).notNull().inMinMaxRange(1,
|
||||
2);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.priorityParamName, element)) {
|
||||
final Integer priority = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.priorityParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.priorityParamName).value(priority).notNull()
|
||||
.inMinMaxRange(1, 4);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.instructionTypeParamName, element)) {
|
||||
final Integer standingInstructionType = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.instructionTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||
.notNull().inMinMaxRange(1, 2);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.recurrenceTypeParamName, element)) {
|
||||
final Integer recurrenceType = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||
.inMinMaxRange(1, 2);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.recurrenceFrequencyParamName, element)) {
|
||||
final Integer recurrenceFrequency = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceFrequencyParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceFrequencyParamName).value(recurrenceFrequency)
|
||||
.inMinMaxRange(0, 3);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.recurrenceIntervalParamName, element)) {
|
||||
final Integer recurrenceInterval = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceIntervalParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceIntervalParamName).value(recurrenceInterval)
|
||||
.integerGreaterThanZero();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.nameParamName, element)) {
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed(StandingInstructionApiConstants.nameParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.nameParamName).value(name).notNull();
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+314
-214
@@ -1,214 +1,314 @@
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromOfficeIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import java.util.Locale;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
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.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AccountTransferDetailAssembler {
|
||||
|
||||
private final ClientRepositoryWrapper clientRepository;
|
||||
private final OfficeRepositoryWrapper officeRepositoryWrapper;
|
||||
private final SavingsAccountAssembler savingsAccountAssembler;
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanAssembler loanAccountAssembler;
|
||||
|
||||
@Autowired
|
||||
public AccountTransferDetailAssembler(final ClientRepositoryWrapper clientRepository,
|
||||
final OfficeRepositoryWrapper officeRepositoryWrapper, final SavingsAccountAssembler savingsAccountAssembler,
|
||||
final FromJsonHelper fromApiJsonHelper, final LoanAssembler loanAccountAssembler) {
|
||||
this.clientRepository = clientRepository;
|
||||
this.officeRepositoryWrapper = officeRepositoryWrapper;
|
||||
this.savingsAccountAssembler = savingsAccountAssembler;
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.loanAccountAssembler = loanAccountAssembler;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
|
||||
|
||||
final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId, false);
|
||||
|
||||
final boolean backdatedTxnsAllowedTill = false;
|
||||
final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId, backdatedTxnsAllowedTill);
|
||||
|
||||
return assembleSavingsToSavingsTransfer(command, fromSavingsAccount, toSavingsAccount);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
|
||||
|
||||
final Long fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final boolean backdatedTxnsAllowedTill = false;
|
||||
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId, backdatedTxnsAllowedTill);
|
||||
|
||||
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
|
||||
|
||||
return assembleSavingsToLoanTransfer(command, fromSavingsAccount, toLoanAccount);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command) {
|
||||
|
||||
final Long fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
|
||||
final boolean backdatedTxnsAllowedTill = false;
|
||||
final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill);
|
||||
|
||||
return assembleLoanToSavingsTransfer(command, fromLoanAccount, toSavingsAccount);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
|
||||
final SavingsAccount toSavingsAccount) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient,
|
||||
toSavingsAccount, transfertype);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
|
||||
final Loan toLoanAccount) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount,
|
||||
transfertype);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command, final Loan fromLoanAccount,
|
||||
final SavingsAccount toSavingsAccount) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.loanTosavingsTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toSavingsAccount,
|
||||
transfertype);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final SavingsAccount fromSavingsAccount, final Loan toLoanAccount,
|
||||
Integer transferType) {
|
||||
final Office fromOffice = fromSavingsAccount.office();
|
||||
final Client fromClient = fromSavingsAccount.getClient();
|
||||
final Office toOffice = toLoanAccount.getOffice();
|
||||
final Client toClient = toLoanAccount.client();
|
||||
|
||||
return AccountTransferDetails.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount,
|
||||
transferType);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final SavingsAccount fromSavingsAccount,
|
||||
final SavingsAccount toSavingsAccount, Integer transferType) {
|
||||
final Office fromOffice = fromSavingsAccount.office();
|
||||
final Client fromClient = fromSavingsAccount.getClient();
|
||||
final Office toOffice = toSavingsAccount.office();
|
||||
final Client toClient = toSavingsAccount.getClient();
|
||||
|
||||
return AccountTransferDetails.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient,
|
||||
toSavingsAccount, transferType);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final Loan fromLoanAccount, final SavingsAccount toSavingsAccount,
|
||||
Integer transferType) {
|
||||
final Office fromOffice = fromLoanAccount.getOffice();
|
||||
final Client fromClient = fromLoanAccount.client();
|
||||
final Office toOffice = toSavingsAccount.office();
|
||||
final Client toClient = toSavingsAccount.getClient();
|
||||
|
||||
return AccountTransferDetails.loanTosavingsTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toSavingsAccount,
|
||||
transferType);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToLoanTransfer(Loan fromLoanAccount, Loan toLoanAccount, Integer transferType) {
|
||||
final Office fromOffice = fromLoanAccount.getOffice();
|
||||
final Client fromClient = fromLoanAccount.client();
|
||||
final Office toOffice = toLoanAccount.getOffice();
|
||||
final Client toClient = toLoanAccount.client();
|
||||
|
||||
return AccountTransferDetails.loanToLoanTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toLoanAccount,
|
||||
transferType);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromOfficeIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import java.util.Locale;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountRepository;
|
||||
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.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AccountTransferDetailAssembler {
|
||||
|
||||
private final ClientRepositoryWrapper clientRepository;
|
||||
private final OfficeRepositoryWrapper officeRepositoryWrapper;
|
||||
private final SavingsAccountAssembler savingsAccountAssembler;
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanAssembler loanAccountAssembler;
|
||||
private final CurrentAccountRepository currentAccountRepository;
|
||||
|
||||
@Autowired
|
||||
public AccountTransferDetailAssembler(final ClientRepositoryWrapper clientRepository,
|
||||
final OfficeRepositoryWrapper officeRepositoryWrapper, final SavingsAccountAssembler savingsAccountAssembler,
|
||||
final FromJsonHelper fromApiJsonHelper, final LoanAssembler loanAccountAssembler,
|
||||
final CurrentAccountRepository currentAccountRepository) {
|
||||
this.clientRepository = clientRepository;
|
||||
this.officeRepositoryWrapper = officeRepositoryWrapper;
|
||||
this.savingsAccountAssembler = savingsAccountAssembler;
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.loanAccountAssembler = loanAccountAssembler;
|
||||
this.currentAccountRepository = currentAccountRepository;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
|
||||
|
||||
final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId, false);
|
||||
|
||||
final boolean backdatedTxnsAllowedTill = false;
|
||||
final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId, backdatedTxnsAllowedTill);
|
||||
|
||||
return assembleSavingsToSavingsTransfer(command, fromSavingsAccount, toSavingsAccount);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
|
||||
|
||||
final Long fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final boolean backdatedTxnsAllowedTill = false;
|
||||
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId, backdatedTxnsAllowedTill);
|
||||
|
||||
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
|
||||
|
||||
return assembleSavingsToLoanTransfer(command, fromSavingsAccount, toLoanAccount);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command) {
|
||||
|
||||
final Long fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
|
||||
final boolean backdatedTxnsAllowedTill = false;
|
||||
final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill);
|
||||
|
||||
return assembleLoanToSavingsTransfer(command, fromLoanAccount, toSavingsAccount);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
|
||||
final SavingsAccount toSavingsAccount) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient,
|
||||
toSavingsAccount, transfertype);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
|
||||
final Loan toLoanAccount) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount,
|
||||
transfertype);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command, final Loan fromLoanAccount,
|
||||
final SavingsAccount toSavingsAccount) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.loanTosavingsTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toSavingsAccount,
|
||||
transfertype);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final SavingsAccount fromSavingsAccount, final Loan toLoanAccount,
|
||||
Integer transferType) {
|
||||
final Office fromOffice = fromSavingsAccount.office();
|
||||
final Client fromClient = fromSavingsAccount.getClient();
|
||||
final Office toOffice = toLoanAccount.getOffice();
|
||||
final Client toClient = toLoanAccount.client();
|
||||
|
||||
return AccountTransferDetails.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount,
|
||||
transferType);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final SavingsAccount fromSavingsAccount,
|
||||
final SavingsAccount toSavingsAccount, Integer transferType) {
|
||||
final Office fromOffice = fromSavingsAccount.office();
|
||||
final Client fromClient = fromSavingsAccount.getClient();
|
||||
final Office toOffice = toSavingsAccount.office();
|
||||
final Client toClient = toSavingsAccount.getClient();
|
||||
|
||||
return AccountTransferDetails.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient,
|
||||
toSavingsAccount, transferType);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final Loan fromLoanAccount, final SavingsAccount toSavingsAccount,
|
||||
Integer transferType) {
|
||||
final Office fromOffice = fromLoanAccount.getOffice();
|
||||
final Client fromClient = fromLoanAccount.client();
|
||||
final Office toOffice = toSavingsAccount.office();
|
||||
final Client toClient = toSavingsAccount.getClient();
|
||||
|
||||
return AccountTransferDetails.loanTosavingsTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toSavingsAccount,
|
||||
transferType);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToLoanTransfer(Loan fromLoanAccount, Loan toLoanAccount, Integer transferType) {
|
||||
final Office fromOffice = fromLoanAccount.getOffice();
|
||||
final Client fromClient = fromLoanAccount.client();
|
||||
final Office toOffice = toLoanAccount.getOffice();
|
||||
final Client toClient = toLoanAccount.client();
|
||||
|
||||
return AccountTransferDetails.loanToLoanTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toLoanAccount,
|
||||
transferType);
|
||||
}
|
||||
|
||||
// --- Current account assembly methods ---
|
||||
|
||||
public AccountTransferDetails assembleCurrentToCurrentTransfer(final JsonCommand command) {
|
||||
final Long fromCurrentAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final CurrentAccount fromCurrentAccount = currentAccountRepository.findById(fromCurrentAccountId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("From current account not found: " + fromCurrentAccountId));
|
||||
|
||||
final Long toCurrentAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final CurrentAccount toCurrentAccount = currentAccountRepository.findById(toCurrentAccountId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("To current account not found: " + toCurrentAccountId));
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.currentToCurrentTransfer(fromOffice, fromClient, fromCurrentAccount, toOffice, toClient,
|
||||
toCurrentAccount, transfertype);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleCurrentToSavingsTransfer(final JsonCommand command) {
|
||||
final Long fromCurrentAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final CurrentAccount fromCurrentAccount = currentAccountRepository.findById(fromCurrentAccountId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("From current account not found: " + fromCurrentAccountId));
|
||||
|
||||
final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId, false);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.currentToSavingsTransfer(fromOffice, fromClient, fromCurrentAccount, toOffice, toClient,
|
||||
toSavingsAccount, transfertype);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToCurrentTransfer(final JsonCommand command) {
|
||||
final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId, false);
|
||||
|
||||
final Long toCurrentAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final CurrentAccount toCurrentAccount = currentAccountRepository.findById(toCurrentAccountId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("To current account not found: " + toCurrentAccountId));
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.savingsToCurrentTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient,
|
||||
toCurrentAccount, transfertype);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleCurrentToLoanTransfer(final JsonCommand command) {
|
||||
final Long fromCurrentAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final CurrentAccount fromCurrentAccount = currentAccountRepository.findById(fromCurrentAccountId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("From current account not found: " + fromCurrentAccountId));
|
||||
|
||||
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.currentToLoanTransfer(fromOffice, fromClient, fromCurrentAccount, toOffice, toClient,
|
||||
toLoanAccount, transfertype);
|
||||
}
|
||||
}
|
||||
|
||||
+216
-165
@@ -1,165 +1,216 @@
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_account_transfer_details")
|
||||
public class AccountTransferDetails extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_office_id", nullable = false)
|
||||
private Office fromOffice;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_client_id", nullable = false)
|
||||
private Client fromClient;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_savings_account_id", nullable = true)
|
||||
private SavingsAccount fromSavingsAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_office_id", nullable = false)
|
||||
private Office toOffice;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_client_id", nullable = false)
|
||||
private Client toClient;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_savings_account_id", nullable = true)
|
||||
private SavingsAccount toSavingsAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_loan_account_id", nullable = true)
|
||||
private Loan toLoanAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_loan_account_id", nullable = true)
|
||||
private Loan fromLoanAccount;
|
||||
|
||||
@Column(name = "transfer_type")
|
||||
private Integer transferType;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "accountTransferDetails", orphanRemoval = true, fetch = FetchType.EAGER)
|
||||
private List<AccountTransferTransaction> accountTransferTransactions = new ArrayList<>();
|
||||
|
||||
@OneToOne(mappedBy = "accountTransferDetails", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.EAGER)
|
||||
private AccountTransferStandingInstruction accountTransferStandingInstruction;
|
||||
|
||||
public static AccountTransferDetails savingsToSavingsTransfer(final Office fromOffice, final Client fromClient,
|
||||
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
|
||||
Integer transferType) {
|
||||
|
||||
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, toOffice, toClient, toSavingsAccount, null,
|
||||
transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails savingsToLoanTransfer(final Office fromOffice, final Client fromClient,
|
||||
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final Loan toLoanAccount,
|
||||
Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, toOffice, toClient, null, toLoanAccount,
|
||||
transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails loanTosavingsTransfer(final Office fromOffice, final Client fromClient, final Loan fromLoanAccount,
|
||||
final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, toOffice, toClient, toSavingsAccount, null,
|
||||
transferType, null);
|
||||
}
|
||||
|
||||
protected AccountTransferDetails() {
|
||||
//
|
||||
}
|
||||
|
||||
private AccountTransferDetails(final Office fromOffice, final Client fromClient, final SavingsAccount fromSavingsAccount,
|
||||
final Loan fromLoanAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
|
||||
final Loan toLoanAccount, final Integer transferType,
|
||||
final AccountTransferStandingInstruction accountTransferStandingInstruction) {
|
||||
this.fromOffice = fromOffice;
|
||||
this.fromClient = fromClient;
|
||||
this.fromSavingsAccount = fromSavingsAccount;
|
||||
this.fromLoanAccount = fromLoanAccount;
|
||||
this.toOffice = toOffice;
|
||||
this.toClient = toClient;
|
||||
this.toSavingsAccount = toSavingsAccount;
|
||||
this.toLoanAccount = toLoanAccount;
|
||||
this.transferType = transferType;
|
||||
this.accountTransferStandingInstruction = accountTransferStandingInstruction;
|
||||
}
|
||||
|
||||
public SavingsAccount toSavingsAccount() {
|
||||
return this.toSavingsAccount;
|
||||
}
|
||||
|
||||
public SavingsAccount fromSavingsAccount() {
|
||||
return this.fromSavingsAccount;
|
||||
}
|
||||
|
||||
public void addAccountTransferTransaction(AccountTransferTransaction accountTransferTransaction) {
|
||||
this.accountTransferTransactions.add(accountTransferTransaction);
|
||||
}
|
||||
|
||||
public void updateAccountTransferStandingInstruction(final AccountTransferStandingInstruction accountTransferStandingInstruction) {
|
||||
this.accountTransferStandingInstruction = accountTransferStandingInstruction;
|
||||
}
|
||||
|
||||
public Loan toLoanAccount() {
|
||||
return this.toLoanAccount;
|
||||
}
|
||||
|
||||
public Loan fromLoanAccount() {
|
||||
return this.fromLoanAccount;
|
||||
}
|
||||
|
||||
public AccountTransferStandingInstruction accountTransferStandingInstruction() {
|
||||
return this.accountTransferStandingInstruction;
|
||||
}
|
||||
|
||||
public AccountTransferType transferType() {
|
||||
return AccountTransferType.fromInt(this.transferType);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails loanToLoanTransfer(Office fromOffice, Client fromClient, Loan fromLoanAccount, Office toOffice,
|
||||
Client toClient, Loan toLoanAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, toOffice, toClient, null, toLoanAccount,
|
||||
transferType, null);
|
||||
}
|
||||
|
||||
public List<AccountTransferTransaction> getAccountTransferTransactions() {
|
||||
return accountTransferTransactions;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_account_transfer_details")
|
||||
public class AccountTransferDetails extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_office_id", nullable = false)
|
||||
private Office fromOffice;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_client_id", nullable = false)
|
||||
private Client fromClient;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_savings_account_id", nullable = true)
|
||||
private SavingsAccount fromSavingsAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_office_id", nullable = false)
|
||||
private Office toOffice;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_client_id", nullable = false)
|
||||
private Client toClient;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_savings_account_id", nullable = true)
|
||||
private SavingsAccount toSavingsAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_loan_account_id", nullable = true)
|
||||
private Loan toLoanAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_loan_account_id", nullable = true)
|
||||
private Loan fromLoanAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_current_account_id", nullable = true)
|
||||
private CurrentAccount fromCurrentAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_current_account_id", nullable = true)
|
||||
private CurrentAccount toCurrentAccount;
|
||||
|
||||
@Column(name = "transfer_type")
|
||||
private Integer transferType;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "accountTransferDetails", orphanRemoval = true, fetch = FetchType.EAGER)
|
||||
private List<AccountTransferTransaction> accountTransferTransactions = new ArrayList<>();
|
||||
|
||||
@OneToOne(mappedBy = "accountTransferDetails", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.EAGER)
|
||||
private AccountTransferStandingInstruction accountTransferStandingInstruction;
|
||||
|
||||
public static AccountTransferDetails savingsToSavingsTransfer(final Office fromOffice, final Client fromClient,
|
||||
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
|
||||
Integer transferType) {
|
||||
|
||||
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, null, toOffice, toClient, toSavingsAccount,
|
||||
null, null, transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails savingsToLoanTransfer(final Office fromOffice, final Client fromClient,
|
||||
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final Loan toLoanAccount,
|
||||
Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, null, toOffice, toClient, null, toLoanAccount,
|
||||
null, transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails loanTosavingsTransfer(final Office fromOffice, final Client fromClient, final Loan fromLoanAccount,
|
||||
final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, null, toOffice, toClient, toSavingsAccount, null,
|
||||
null, transferType, null);
|
||||
}
|
||||
|
||||
protected AccountTransferDetails() {
|
||||
//
|
||||
}
|
||||
|
||||
private AccountTransferDetails(final Office fromOffice, final Client fromClient, final SavingsAccount fromSavingsAccount,
|
||||
final Loan fromLoanAccount, final CurrentAccount fromCurrentAccount,
|
||||
final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
|
||||
final Loan toLoanAccount, final CurrentAccount toCurrentAccount,
|
||||
final Integer transferType,
|
||||
final AccountTransferStandingInstruction accountTransferStandingInstruction) {
|
||||
this.fromOffice = fromOffice;
|
||||
this.fromClient = fromClient;
|
||||
this.fromSavingsAccount = fromSavingsAccount;
|
||||
this.fromLoanAccount = fromLoanAccount;
|
||||
this.fromCurrentAccount = fromCurrentAccount;
|
||||
this.toOffice = toOffice;
|
||||
this.toClient = toClient;
|
||||
this.toSavingsAccount = toSavingsAccount;
|
||||
this.toLoanAccount = toLoanAccount;
|
||||
this.toCurrentAccount = toCurrentAccount;
|
||||
this.transferType = transferType;
|
||||
this.accountTransferStandingInstruction = accountTransferStandingInstruction;
|
||||
}
|
||||
|
||||
public SavingsAccount toSavingsAccount() {
|
||||
return this.toSavingsAccount;
|
||||
}
|
||||
|
||||
public SavingsAccount fromSavingsAccount() {
|
||||
return this.fromSavingsAccount;
|
||||
}
|
||||
|
||||
public void addAccountTransferTransaction(AccountTransferTransaction accountTransferTransaction) {
|
||||
this.accountTransferTransactions.add(accountTransferTransaction);
|
||||
}
|
||||
|
||||
public void updateAccountTransferStandingInstruction(final AccountTransferStandingInstruction accountTransferStandingInstruction) {
|
||||
this.accountTransferStandingInstruction = accountTransferStandingInstruction;
|
||||
}
|
||||
|
||||
public Loan toLoanAccount() {
|
||||
return this.toLoanAccount;
|
||||
}
|
||||
|
||||
public Loan fromLoanAccount() {
|
||||
return this.fromLoanAccount;
|
||||
}
|
||||
|
||||
public CurrentAccount fromCurrentAccount() {
|
||||
return this.fromCurrentAccount;
|
||||
}
|
||||
|
||||
public CurrentAccount toCurrentAccount() {
|
||||
return this.toCurrentAccount;
|
||||
}
|
||||
|
||||
public AccountTransferStandingInstruction accountTransferStandingInstruction() {
|
||||
return this.accountTransferStandingInstruction;
|
||||
}
|
||||
|
||||
public AccountTransferType transferType() {
|
||||
return AccountTransferType.fromInt(this.transferType);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails loanToLoanTransfer(Office fromOffice, Client fromClient, Loan fromLoanAccount, Office toOffice,
|
||||
Client toClient, Loan toLoanAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, null, toOffice, toClient, null, toLoanAccount,
|
||||
null, transferType, null);
|
||||
}
|
||||
|
||||
// --- Current account factory methods ---
|
||||
|
||||
public static AccountTransferDetails currentToCurrentTransfer(final Office fromOffice, final Client fromClient,
|
||||
final CurrentAccount fromCurrentAccount, final Office toOffice, final Client toClient,
|
||||
final CurrentAccount toCurrentAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, null, fromCurrentAccount, toOffice, toClient, null, null,
|
||||
toCurrentAccount, transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails currentToSavingsTransfer(final Office fromOffice, final Client fromClient,
|
||||
final CurrentAccount fromCurrentAccount, final Office toOffice, final Client toClient,
|
||||
final SavingsAccount toSavingsAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, null, fromCurrentAccount, toOffice, toClient, toSavingsAccount,
|
||||
null, null, transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails savingsToCurrentTransfer(final Office fromOffice, final Client fromClient,
|
||||
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient,
|
||||
final CurrentAccount toCurrentAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, null, toOffice, toClient, null, null,
|
||||
toCurrentAccount, transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails currentToLoanTransfer(final Office fromOffice, final Client fromClient,
|
||||
final CurrentAccount fromCurrentAccount, final Office toOffice, final Client toClient,
|
||||
final Loan toLoanAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, null, fromCurrentAccount, toOffice, toClient, null, toLoanAccount,
|
||||
null, transferType, null);
|
||||
}
|
||||
|
||||
public List<AccountTransferTransaction> getAccountTransferTransactions() {
|
||||
return accountTransferTransactions;
|
||||
}
|
||||
}
|
||||
|
||||
+302
-290
@@ -1,290 +1,302 @@
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME;
|
||||
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.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 jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_account_transfer_standing_instructions", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = { "name" }, name = "name") })
|
||||
public class AccountTransferStandingInstruction extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "account_transfer_details_id", nullable = true)
|
||||
private AccountTransferDetails accountTransferDetails;
|
||||
|
||||
@Column(name = "name")
|
||||
private String name;
|
||||
|
||||
@Column(name = "priority")
|
||||
private Integer priority;
|
||||
|
||||
@Column(name = "instruction_type")
|
||||
private Integer instructionType;
|
||||
|
||||
@Column(name = "status")
|
||||
private Integer status;
|
||||
|
||||
@Column(name = "amount", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal amount;
|
||||
|
||||
@Column(name = "valid_from")
|
||||
private LocalDate validFrom;
|
||||
|
||||
@Column(name = "valid_till")
|
||||
private LocalDate validTill;
|
||||
|
||||
@Column(name = "recurrence_type")
|
||||
private Integer recurrenceType;
|
||||
|
||||
@Column(name = "recurrence_frequency")
|
||||
private Integer recurrenceFrequency;
|
||||
|
||||
@Column(name = "recurrence_interval")
|
||||
private Integer recurrenceInterval;
|
||||
|
||||
@Column(name = "recurrence_on_day")
|
||||
private Integer recurrenceOnDay;
|
||||
|
||||
@Column(name = "recurrence_on_month")
|
||||
private Integer recurrenceOnMonth;
|
||||
|
||||
@Column(name = "last_run_date")
|
||||
private LocalDate latsRunDate;
|
||||
|
||||
protected AccountTransferStandingInstruction() {
|
||||
|
||||
}
|
||||
|
||||
public static AccountTransferStandingInstruction create(final AccountTransferDetails accountTransferDetails, final String name,
|
||||
final Integer priority, final Integer instructionType, final Integer status, final BigDecimal amount, final LocalDate validFrom,
|
||||
final LocalDate validTill, final Integer recurrenceType, final Integer recurrenceFrequency, final Integer recurrenceInterval,
|
||||
final MonthDay recurrenceOnMonthDay) {
|
||||
Integer recurrenceOnDay = null;
|
||||
Integer recurrenceOnMonth = null;
|
||||
if (recurrenceOnMonthDay != null) {
|
||||
recurrenceOnDay = recurrenceOnMonthDay.getDayOfMonth();
|
||||
recurrenceOnMonth = recurrenceOnMonthDay.getMonthValue();
|
||||
}
|
||||
return new AccountTransferStandingInstruction(accountTransferDetails, name, priority, instructionType, status, amount, validFrom,
|
||||
validTill, recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnDay, recurrenceOnMonth);
|
||||
}
|
||||
|
||||
private AccountTransferStandingInstruction(final AccountTransferDetails accountTransferDetails, final String name,
|
||||
final Integer priority, final Integer instructionType, final Integer status, final BigDecimal amount, final LocalDate validFrom,
|
||||
final LocalDate validTill, final Integer recurrenceType, final Integer recurrenceFrequency, final Integer recurrenceInterval,
|
||||
final Integer recurrenceOnDay, final Integer recurrenceOnMonth) {
|
||||
this.accountTransferDetails = accountTransferDetails;
|
||||
this.name = name;
|
||||
this.priority = priority;
|
||||
this.instructionType = instructionType;
|
||||
this.status = status;
|
||||
this.amount = amount;
|
||||
this.validFrom = validFrom;
|
||||
this.validTill = validTill;
|
||||
this.recurrenceType = recurrenceType;
|
||||
this.recurrenceFrequency = recurrenceFrequency;
|
||||
this.recurrenceInterval = recurrenceInterval;
|
||||
this.recurrenceOnDay = recurrenceOnDay;
|
||||
this.recurrenceOnMonth = recurrenceOnMonth;
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
validateDependencies(baseDataValidator);
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> update(JsonCommand command) {
|
||||
final Map<String, Object> actualChanges = new HashMap<>();
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
if (StandingInstructionStatus.fromInt(this.status).isDeleted()) {
|
||||
baseDataValidator.reset().parameter(statusParamName).failWithCode("can.not.modify.once.deleted");
|
||||
}
|
||||
|
||||
if (command.isChangeInDateParameterNamed(validFromParamName, this.validFrom)) {
|
||||
this.validFrom = command.localDateValueOfParameterNamed(validFromParamName);
|
||||
actualChanges.put(validFromParamName, this.validFrom);
|
||||
}
|
||||
|
||||
if (command.isChangeInDateParameterNamed(validTillParamName, this.validTill)) {
|
||||
this.validTill = command.localDateValueOfParameterNamed(validTillParamName);
|
||||
actualChanges.put(validTillParamName, this.validTill);
|
||||
}
|
||||
|
||||
if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
|
||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
|
||||
actualChanges.put(amountParamName, newValue);
|
||||
this.amount = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(statusParamName, this.status)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(statusParamName);
|
||||
actualChanges.put(statusParamName, newValue);
|
||||
this.status = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(priorityParamName, this.priority)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(priorityParamName);
|
||||
actualChanges.put(priorityParamName, newValue);
|
||||
this.priority = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(instructionTypeParamName, this.instructionType)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(instructionTypeParamName);
|
||||
actualChanges.put(instructionTypeParamName, newValue);
|
||||
this.instructionType = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(recurrenceTypeParamName, this.recurrenceType)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(recurrenceTypeParamName);
|
||||
actualChanges.put(recurrenceTypeParamName, newValue);
|
||||
this.recurrenceType = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(recurrenceFrequencyParamName, this.recurrenceFrequency)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(recurrenceFrequencyParamName);
|
||||
actualChanges.put(recurrenceFrequencyParamName, newValue);
|
||||
this.recurrenceFrequency = newValue;
|
||||
}
|
||||
|
||||
if (command.hasParameter(recurrenceOnMonthDayParamName)) {
|
||||
final MonthDay monthDay = command.extractMonthDayNamed(recurrenceOnMonthDayParamName);
|
||||
final String actualValueEntered = command.stringValueOfParameterNamed(recurrenceOnMonthDayParamName);
|
||||
final Integer dayOfMonthValue = monthDay.getDayOfMonth();
|
||||
if (!this.recurrenceOnDay.equals(dayOfMonthValue)) {
|
||||
actualChanges.put(recurrenceOnMonthDayParamName, actualValueEntered);
|
||||
this.recurrenceOnDay = dayOfMonthValue;
|
||||
}
|
||||
|
||||
final Integer monthOfYear = monthDay.getMonthValue();
|
||||
if (!this.recurrenceOnMonth.equals(monthOfYear)) {
|
||||
actualChanges.put(recurrenceOnMonthDayParamName, actualValueEntered);
|
||||
this.recurrenceOnMonth = monthOfYear;
|
||||
}
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(recurrenceIntervalParamName, this.recurrenceInterval)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(recurrenceIntervalParamName);
|
||||
actualChanges.put(recurrenceIntervalParamName, newValue);
|
||||
this.recurrenceInterval = newValue;
|
||||
}
|
||||
validateDependencies(baseDataValidator);
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
private void validateDependencies(final DataValidatorBuilder baseDataValidator) {
|
||||
|
||||
if (this.validTill != null && this.validFrom != null) {
|
||||
baseDataValidator.reset().parameter(validTillParamName).value(this.validTill).validateDateAfter(this.validFrom);
|
||||
}
|
||||
|
||||
if (AccountTransferRecurrenceType.fromInt(recurrenceType).isPeriodicRecurrence()) {
|
||||
baseDataValidator.reset().parameter(recurrenceFrequencyParamName).value(this.recurrenceFrequency).notNull();
|
||||
baseDataValidator.reset().parameter(recurrenceIntervalParamName).value(this.recurrenceInterval).notNull();
|
||||
if (this.recurrenceFrequency != null) {
|
||||
PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(this.recurrenceFrequency);
|
||||
if (frequencyType.isMonthly()) {
|
||||
baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnDay).notNull();
|
||||
} else if (frequencyType.isYearly()) {
|
||||
baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnDay).notNull();
|
||||
baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnMonth).notNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.accountTransferDetails.toSavingsAccount() != null) {
|
||||
baseDataValidator.reset().parameter(instructionTypeParamName).value(this.instructionType).notNull().inMinMaxRange(1, 1);
|
||||
baseDataValidator.reset().parameter(recurrenceTypeParamName).value(this.recurrenceType).notNull().inMinMaxRange(1, 1);
|
||||
}
|
||||
|
||||
if (StandingInstructionType.fromInt(this.instructionType).isFixedAmoutTransfer()) {
|
||||
baseDataValidator.reset().parameter(amountParamName).value(this.amount).notNull();
|
||||
}
|
||||
|
||||
String errorCode = null;
|
||||
if (this.accountTransferDetails.transferType().isAccountTransfer()
|
||||
&& (this.accountTransferDetails.fromSavingsAccount() == null || this.accountTransferDetails.toSavingsAccount() == null)) {
|
||||
errorCode = "not.account.transfer";
|
||||
} else if (this.accountTransferDetails.transferType().isLoanRepayment()
|
||||
&& (this.accountTransferDetails.fromSavingsAccount() == null || this.accountTransferDetails.toLoanAccount() == null)) {
|
||||
errorCode = "not.loan.repayment";
|
||||
}
|
||||
if (errorCode != null) {
|
||||
baseDataValidator.reset().parameter(transferTypeParamName).failWithCode(errorCode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void updateLatsRunDate(LocalDate latsRunDate) {
|
||||
this.latsRunDate = latsRunDate;
|
||||
}
|
||||
|
||||
public void updateStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete the standing instruction by setting the status to 3 and appending "_deleted_" and the id to the name
|
||||
**/
|
||||
public void delete() {
|
||||
this.status = StandingInstructionStatus.DELETED.getValue();
|
||||
this.name = this.name + "_deleted_" + this.getId();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME;
|
||||
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.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 jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_account_transfer_standing_instructions", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = { "name" }, name = "name") })
|
||||
public class AccountTransferStandingInstruction extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "account_transfer_details_id", nullable = true)
|
||||
private AccountTransferDetails accountTransferDetails;
|
||||
|
||||
@Column(name = "name")
|
||||
private String name;
|
||||
|
||||
@Column(name = "priority")
|
||||
private Integer priority;
|
||||
|
||||
@Column(name = "instruction_type")
|
||||
private Integer instructionType;
|
||||
|
||||
@Column(name = "status")
|
||||
private Integer status;
|
||||
|
||||
@Column(name = "amount", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal amount;
|
||||
|
||||
@Column(name = "valid_from")
|
||||
private LocalDate validFrom;
|
||||
|
||||
@Column(name = "valid_till")
|
||||
private LocalDate validTill;
|
||||
|
||||
@Column(name = "recurrence_type")
|
||||
private Integer recurrenceType;
|
||||
|
||||
@Column(name = "recurrence_frequency")
|
||||
private Integer recurrenceFrequency;
|
||||
|
||||
@Column(name = "recurrence_interval")
|
||||
private Integer recurrenceInterval;
|
||||
|
||||
@Column(name = "recurrence_on_day")
|
||||
private Integer recurrenceOnDay;
|
||||
|
||||
@Column(name = "recurrence_on_month")
|
||||
private Integer recurrenceOnMonth;
|
||||
|
||||
@Column(name = "last_run_date")
|
||||
private LocalDate latsRunDate;
|
||||
|
||||
protected AccountTransferStandingInstruction() {
|
||||
|
||||
}
|
||||
|
||||
public static AccountTransferStandingInstruction create(final AccountTransferDetails accountTransferDetails, final String name,
|
||||
final Integer priority, final Integer instructionType, final Integer status, final BigDecimal amount, final LocalDate validFrom,
|
||||
final LocalDate validTill, final Integer recurrenceType, final Integer recurrenceFrequency, final Integer recurrenceInterval,
|
||||
final MonthDay recurrenceOnMonthDay) {
|
||||
Integer recurrenceOnDay = null;
|
||||
Integer recurrenceOnMonth = null;
|
||||
if (recurrenceOnMonthDay != null) {
|
||||
recurrenceOnDay = recurrenceOnMonthDay.getDayOfMonth();
|
||||
recurrenceOnMonth = recurrenceOnMonthDay.getMonthValue();
|
||||
}
|
||||
return new AccountTransferStandingInstruction(accountTransferDetails, name, priority, instructionType, status, amount, validFrom,
|
||||
validTill, recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnDay, recurrenceOnMonth);
|
||||
}
|
||||
|
||||
private AccountTransferStandingInstruction(final AccountTransferDetails accountTransferDetails, final String name,
|
||||
final Integer priority, final Integer instructionType, final Integer status, final BigDecimal amount, final LocalDate validFrom,
|
||||
final LocalDate validTill, final Integer recurrenceType, final Integer recurrenceFrequency, final Integer recurrenceInterval,
|
||||
final Integer recurrenceOnDay, final Integer recurrenceOnMonth) {
|
||||
this.accountTransferDetails = accountTransferDetails;
|
||||
this.name = name;
|
||||
this.priority = priority;
|
||||
this.instructionType = instructionType;
|
||||
this.status = status;
|
||||
this.amount = amount;
|
||||
this.validFrom = validFrom;
|
||||
this.validTill = validTill;
|
||||
this.recurrenceType = recurrenceType;
|
||||
this.recurrenceFrequency = recurrenceFrequency;
|
||||
this.recurrenceInterval = recurrenceInterval;
|
||||
this.recurrenceOnDay = recurrenceOnDay;
|
||||
this.recurrenceOnMonth = recurrenceOnMonth;
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
validateDependencies(baseDataValidator);
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> update(JsonCommand command) {
|
||||
final Map<String, Object> actualChanges = new HashMap<>();
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
if (StandingInstructionStatus.fromInt(this.status).isDeleted()) {
|
||||
baseDataValidator.reset().parameter(statusParamName).failWithCode("can.not.modify.once.deleted");
|
||||
}
|
||||
|
||||
if (command.isChangeInDateParameterNamed(validFromParamName, this.validFrom)) {
|
||||
this.validFrom = command.localDateValueOfParameterNamed(validFromParamName);
|
||||
actualChanges.put(validFromParamName, this.validFrom);
|
||||
}
|
||||
|
||||
if (command.isChangeInDateParameterNamed(validTillParamName, this.validTill)) {
|
||||
this.validTill = command.localDateValueOfParameterNamed(validTillParamName);
|
||||
actualChanges.put(validTillParamName, this.validTill);
|
||||
}
|
||||
|
||||
if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
|
||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
|
||||
actualChanges.put(amountParamName, newValue);
|
||||
this.amount = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(statusParamName, this.status)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(statusParamName);
|
||||
actualChanges.put(statusParamName, newValue);
|
||||
this.status = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(priorityParamName, this.priority)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(priorityParamName);
|
||||
actualChanges.put(priorityParamName, newValue);
|
||||
this.priority = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(instructionTypeParamName, this.instructionType)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(instructionTypeParamName);
|
||||
actualChanges.put(instructionTypeParamName, newValue);
|
||||
this.instructionType = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(recurrenceTypeParamName, this.recurrenceType)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(recurrenceTypeParamName);
|
||||
actualChanges.put(recurrenceTypeParamName, newValue);
|
||||
this.recurrenceType = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(recurrenceFrequencyParamName, this.recurrenceFrequency)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(recurrenceFrequencyParamName);
|
||||
actualChanges.put(recurrenceFrequencyParamName, newValue);
|
||||
this.recurrenceFrequency = newValue;
|
||||
}
|
||||
|
||||
if (command.hasParameter(recurrenceOnMonthDayParamName)) {
|
||||
final MonthDay monthDay = command.extractMonthDayNamed(recurrenceOnMonthDayParamName);
|
||||
final String actualValueEntered = command.stringValueOfParameterNamed(recurrenceOnMonthDayParamName);
|
||||
final Integer dayOfMonthValue = monthDay.getDayOfMonth();
|
||||
if (!this.recurrenceOnDay.equals(dayOfMonthValue)) {
|
||||
actualChanges.put(recurrenceOnMonthDayParamName, actualValueEntered);
|
||||
this.recurrenceOnDay = dayOfMonthValue;
|
||||
}
|
||||
|
||||
final Integer monthOfYear = monthDay.getMonthValue();
|
||||
if (!this.recurrenceOnMonth.equals(monthOfYear)) {
|
||||
actualChanges.put(recurrenceOnMonthDayParamName, actualValueEntered);
|
||||
this.recurrenceOnMonth = monthOfYear;
|
||||
}
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(recurrenceIntervalParamName, this.recurrenceInterval)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(recurrenceIntervalParamName);
|
||||
actualChanges.put(recurrenceIntervalParamName, newValue);
|
||||
this.recurrenceInterval = newValue;
|
||||
}
|
||||
validateDependencies(baseDataValidator);
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
private void validateDependencies(final DataValidatorBuilder baseDataValidator) {
|
||||
|
||||
if (this.validTill != null && this.validFrom != null) {
|
||||
baseDataValidator.reset().parameter(validTillParamName).value(this.validTill).validateDateAfter(this.validFrom);
|
||||
}
|
||||
|
||||
if (AccountTransferRecurrenceType.fromInt(recurrenceType).isPeriodicRecurrence()) {
|
||||
baseDataValidator.reset().parameter(recurrenceFrequencyParamName).value(this.recurrenceFrequency).notNull();
|
||||
baseDataValidator.reset().parameter(recurrenceIntervalParamName).value(this.recurrenceInterval).notNull();
|
||||
if (this.recurrenceFrequency != null) {
|
||||
PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(this.recurrenceFrequency);
|
||||
if (frequencyType.isMonthly()) {
|
||||
baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnDay).notNull();
|
||||
} else if (frequencyType.isYearly()) {
|
||||
baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnDay).notNull();
|
||||
baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnMonth).notNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For savings-to-savings or current-to-savings: enforce Fixed instruction + Periodic recurrence
|
||||
if (this.accountTransferDetails.toSavingsAccount() != null
|
||||
|| this.accountTransferDetails.toCurrentAccount() != null) {
|
||||
baseDataValidator.reset().parameter(instructionTypeParamName).value(this.instructionType).notNull().inMinMaxRange(1, 1);
|
||||
baseDataValidator.reset().parameter(recurrenceTypeParamName).value(this.recurrenceType).notNull().inMinMaxRange(1, 1);
|
||||
}
|
||||
|
||||
if (StandingInstructionType.fromInt(this.instructionType).isFixedAmoutTransfer()) {
|
||||
baseDataValidator.reset().parameter(amountParamName).value(this.amount).notNull();
|
||||
}
|
||||
|
||||
// Account Transfer: from must be savings OR current; to must be savings OR current
|
||||
boolean fromAccountValid = this.accountTransferDetails.fromSavingsAccount() != null
|
||||
|| this.accountTransferDetails.fromCurrentAccount() != null;
|
||||
boolean toAccountValid = this.accountTransferDetails.toSavingsAccount() != null
|
||||
|| this.accountTransferDetails.toCurrentAccount() != null;
|
||||
|
||||
// Loan Repayment: from must be savings OR current; to must be loan
|
||||
boolean fromAccountValidForLoan = this.accountTransferDetails.fromSavingsAccount() != null
|
||||
|| this.accountTransferDetails.fromCurrentAccount() != null;
|
||||
|
||||
String errorCode = null;
|
||||
if (this.accountTransferDetails.transferType().isAccountTransfer()
|
||||
&& !(fromAccountValid && toAccountValid)) {
|
||||
errorCode = "not.account.transfer";
|
||||
} else if (this.accountTransferDetails.transferType().isLoanRepayment()
|
||||
&& !(fromAccountValidForLoan && this.accountTransferDetails.toLoanAccount() != null)) {
|
||||
errorCode = "not.loan.repayment";
|
||||
}
|
||||
if (errorCode != null) {
|
||||
baseDataValidator.reset().parameter(transferTypeParamName).failWithCode(errorCode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void updateLatsRunDate(LocalDate latsRunDate) {
|
||||
this.latsRunDate = latsRunDate;
|
||||
}
|
||||
|
||||
public void updateStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete the standing instruction by setting the status to 3 and appending "_deleted_" and the id to the name
|
||||
**/
|
||||
public void delete() {
|
||||
this.status = StandingInstructionStatus.DELETED.getValue();
|
||||
this.name = this.name + "_deleted_" + this.getId();
|
||||
}
|
||||
}
|
||||
|
||||
+143
-93
@@ -1,93 +1,143 @@
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
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 org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class StandingInstructionAssembler {
|
||||
|
||||
private final AccountTransferDetailAssembler accountTransferDetailAssembler;
|
||||
|
||||
@Autowired
|
||||
public StandingInstructionAssembler(final AccountTransferDetailAssembler accountTransferDetailAssembler) {
|
||||
|
||||
this.accountTransferDetailAssembler = accountTransferDetailAssembler;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToSavingsTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public void assembleStandingInstruction(final JsonCommand command, final AccountTransferDetails accountTransferDetails) {
|
||||
final LocalDate validFrom = command.localDateValueOfParameterNamed(validFromParamName);
|
||||
final LocalDate validTill = command.localDateValueOfParameterNamed(validTillParamName);
|
||||
BigDecimal amount = null;
|
||||
final BigDecimal transferAmount = command.bigDecimalValueOfParameterNamed(amountParamName);
|
||||
if (transferAmount != null) {
|
||||
final Money monetaryAmount = Money.of(accountTransferDetails.fromSavingsAccount().getCurrency(), transferAmount);
|
||||
amount = monetaryAmount.getAmount();
|
||||
}
|
||||
final Integer status = command.integerValueOfParameterNamed(statusParamName);
|
||||
final Integer priority = command.integerValueOfParameterNamed(priorityParamName);
|
||||
final Integer standingInstructionType = command.integerValueOfParameterNamed(instructionTypeParamName);
|
||||
final Integer recurrenceType = command.integerValueOfParameterNamed(recurrenceTypeParamName);
|
||||
final Integer recurrenceFrequency = command.integerValueOfParameterNamed(recurrenceFrequencyParamName);
|
||||
final MonthDay recurrenceOnMonthDay = command.extractMonthDayNamed(recurrenceOnMonthDayParamName);
|
||||
final Integer recurrenceInterval = command.integerValueOfParameterNamed(recurrenceIntervalParamName);
|
||||
final String name = command.stringValueOfParameterNamed(nameParamName);
|
||||
AccountTransferStandingInstruction accountTransferStandingInstruction = AccountTransferStandingInstruction.create(
|
||||
accountTransferDetails, name, priority, standingInstructionType, status, amount, validFrom, validTill, recurrenceType,
|
||||
recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay);
|
||||
accountTransferDetails.updateAccountTransferStandingInstruction(accountTransferStandingInstruction);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToLoanTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleLoanToSavingsTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
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 org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class StandingInstructionAssembler {
|
||||
|
||||
private final AccountTransferDetailAssembler accountTransferDetailAssembler;
|
||||
|
||||
@Autowired
|
||||
public StandingInstructionAssembler(final AccountTransferDetailAssembler accountTransferDetailAssembler) {
|
||||
|
||||
this.accountTransferDetailAssembler = accountTransferDetailAssembler;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToSavingsTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public void assembleStandingInstruction(final JsonCommand command, final AccountTransferDetails accountTransferDetails) {
|
||||
final LocalDate validFrom = command.localDateValueOfParameterNamed(validFromParamName);
|
||||
final LocalDate validTill = command.localDateValueOfParameterNamed(validTillParamName);
|
||||
BigDecimal amount = null;
|
||||
final BigDecimal transferAmount = command.bigDecimalValueOfParameterNamed(amountParamName);
|
||||
if (transferAmount != null) {
|
||||
final MonetaryCurrency currency = resolveCurrency(accountTransferDetails);
|
||||
if (currency != null) {
|
||||
final Money monetaryAmount = Money.of(currency, transferAmount);
|
||||
amount = monetaryAmount.getAmount();
|
||||
} else {
|
||||
amount = transferAmount;
|
||||
}
|
||||
}
|
||||
final Integer status = command.integerValueOfParameterNamed(statusParamName);
|
||||
final Integer priority = command.integerValueOfParameterNamed(priorityParamName);
|
||||
final Integer standingInstructionType = command.integerValueOfParameterNamed(instructionTypeParamName);
|
||||
final Integer recurrenceType = command.integerValueOfParameterNamed(recurrenceTypeParamName);
|
||||
final Integer recurrenceFrequency = command.integerValueOfParameterNamed(recurrenceFrequencyParamName);
|
||||
final MonthDay recurrenceOnMonthDay = command.extractMonthDayNamed(recurrenceOnMonthDayParamName);
|
||||
final Integer recurrenceInterval = command.integerValueOfParameterNamed(recurrenceIntervalParamName);
|
||||
final String name = command.stringValueOfParameterNamed(nameParamName);
|
||||
AccountTransferStandingInstruction accountTransferStandingInstruction = AccountTransferStandingInstruction.create(
|
||||
accountTransferDetails, name, priority, standingInstructionType, status, amount, validFrom, validTill, recurrenceType,
|
||||
recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay);
|
||||
accountTransferDetails.updateAccountTransferStandingInstruction(accountTransferStandingInstruction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the currency from the "from" account — savings, loan (null), or current account.
|
||||
*/
|
||||
private MonetaryCurrency resolveCurrency(final AccountTransferDetails details) {
|
||||
final SavingsAccount fromSavings = details.fromSavingsAccount();
|
||||
if (fromSavings != null) {
|
||||
return fromSavings.getCurrency();
|
||||
}
|
||||
final CurrentAccount fromCurrent = details.fromCurrentAccount();
|
||||
if (fromCurrent != null) {
|
||||
return new MonetaryCurrency(fromCurrent.getCurrencyCode(), fromCurrent.getCurrencyDigits(),
|
||||
fromCurrent.getCurrencyMultiplesOf());
|
||||
}
|
||||
return null; // loan accounts don't have a direct currency on AccountTransferDetails
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToLoanTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleLoanToSavingsTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
// --- Current account methods ---
|
||||
|
||||
public AccountTransferDetails assembleCurrentToCurrentTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleCurrentToCurrentTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleCurrentToSavingsTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleCurrentToSavingsTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToCurrentTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToCurrentTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleCurrentToLoanTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleCurrentToLoanTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+73
-70
@@ -1,70 +1,73 @@
|
||||
/**
|
||||
* 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.jobs.executestandinginstructions;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
@Configuration
|
||||
public class ExecuteStandingInstructionsConfig {
|
||||
|
||||
@Autowired
|
||||
private JobRepository jobRepository;
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
@Autowired
|
||||
private StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
@Autowired
|
||||
private DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
@Autowired
|
||||
private AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||
|
||||
@Bean
|
||||
protected Step executeStandingInstructionsStep() {
|
||||
return new StepBuilder(JobName.EXECUTE_STANDING_INSTRUCTIONS.name(), jobRepository)
|
||||
.tasklet(executeStandingInstructionsTasklet(), transactionManager).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Job executeStandingInstructionsJob() {
|
||||
return new JobBuilder(JobName.EXECUTE_STANDING_INSTRUCTIONS.name(), jobRepository).start(executeStandingInstructionsStep())
|
||||
.incrementer(new RunIdIncrementer()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExecuteStandingInstructionsTasklet executeStandingInstructionsTasklet() {
|
||||
return new ExecuteStandingInstructionsTasklet(standingInstructionReadPlatformService, jdbcTemplate, sqlGenerator,
|
||||
accountTransfersWritePlatformService);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.jobs.executestandinginstructions;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
@Configuration
|
||||
public class ExecuteStandingInstructionsConfig {
|
||||
|
||||
@Autowired
|
||||
private JobRepository jobRepository;
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
@Autowired
|
||||
private StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
@Autowired
|
||||
private DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
@Autowired
|
||||
private AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||
@Autowired
|
||||
private AccountTransferDetailRepository accountTransferDetailRepository;
|
||||
|
||||
@Bean
|
||||
protected Step executeStandingInstructionsStep() {
|
||||
return new StepBuilder(JobName.EXECUTE_STANDING_INSTRUCTIONS.name(), jobRepository)
|
||||
.tasklet(executeStandingInstructionsTasklet(), transactionManager).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Job executeStandingInstructionsJob() {
|
||||
return new JobBuilder(JobName.EXECUTE_STANDING_INSTRUCTIONS.name(), jobRepository).start(executeStandingInstructionsStep())
|
||||
.incrementer(new RunIdIncrementer()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExecuteStandingInstructionsTasklet executeStandingInstructionsTasklet() {
|
||||
return new ExecuteStandingInstructionsTasklet(standingInstructionReadPlatformService, jdbcTemplate, sqlGenerator,
|
||||
accountTransfersWritePlatformService, accountTransferDetailRepository);
|
||||
}
|
||||
}
|
||||
|
||||
+180
-172
@@ -1,172 +1,180 @@
|
||||
/**
|
||||
* 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.jobs.executestandinginstructions;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionData;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||
import org.springframework.batch.repeat.RepeatStatus;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ExecuteStandingInstructionsTasklet implements Tasklet {
|
||||
|
||||
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||
Collection<StandingInstructionData> instructionData = standingInstructionReadPlatformService
|
||||
.retrieveAll(StandingInstructionStatus.ACTIVE.getValue());
|
||||
List<Throwable> errors = new ArrayList<>();
|
||||
for (StandingInstructionData data : instructionData) {
|
||||
boolean isDueForTransfer = false;
|
||||
AccountTransferRecurrenceType recurrenceType = data.getRecurrenceType();
|
||||
StandingInstructionType instructionType = data.getInstructionType();
|
||||
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
||||
if (recurrenceType.isPeriodicRecurrence()) {
|
||||
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
||||
PeriodFrequencyType frequencyType = data.getRecurrenceFrequency();
|
||||
LocalDate startDate = data.getValidFrom();
|
||||
if (frequencyType.isMonthly()) {
|
||||
startDate = startDate.withDayOfMonth(data.getRecurrenceOnDay());
|
||||
if (DateUtils.isBefore(startDate, data.getValidFrom())) {
|
||||
startDate = startDate.plusMonths(1);
|
||||
}
|
||||
} else if (frequencyType.isYearly()) {
|
||||
startDate = startDate.withDayOfMonth(data.getRecurrenceOnDay()).withMonth(data.getRecurrenceOnMonth());
|
||||
if (DateUtils.isBefore(startDate, data.getValidFrom())) {
|
||||
startDate = startDate.plusYears(1);
|
||||
}
|
||||
}
|
||||
isDueForTransfer = scheduledDateGenerator.isDateFallsInSchedule(frequencyType, data.getRecurrenceInterval(), startDate,
|
||||
transactionDate);
|
||||
|
||||
}
|
||||
BigDecimal transactionAmount = data.getAmount();
|
||||
if (data.getToAccountType().isLoanAccount()
|
||||
&& (recurrenceType.isDuesRecurrence() || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) {
|
||||
StandingInstructionDuesData standingInstructionDuesData = standingInstructionReadPlatformService
|
||||
.retriveLoanDuesData(data.getToAccount().getId());
|
||||
if (data.getInstructionType().isDuesAmoutTransfer()) {
|
||||
transactionAmount = standingInstructionDuesData.totalDueAmount();
|
||||
}
|
||||
if (recurrenceType.isDuesRecurrence()) {
|
||||
isDueForTransfer = isDueForTransfer(standingInstructionDuesData);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDueForTransfer && transactionAmount != null && transactionAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
final SavingsAccount fromSavingsAccount = null;
|
||||
final boolean isRegularTransaction = true;
|
||||
final boolean isExceptionForBalanceCheck = false;
|
||||
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount,
|
||||
data.getFromAccountType(), data.getToAccountType(), data.getFromAccount().getId(), data.getToAccount().getId(),
|
||||
data.getName() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null,
|
||||
data.getTransferType().getValue(), null, null, ExternalId.empty(), null, null, fromSavingsAccount,
|
||||
isRegularTransaction, isExceptionForBalanceCheck);
|
||||
final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId());
|
||||
|
||||
if (transferCompleted) {
|
||||
final String updateQuery = "UPDATE m_account_transfer_standing_instructions SET last_run_date = ? where id = ?";
|
||||
jdbcTemplate.update(updateQuery, transactionDate, data.getId());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
throw new JobExecutionException(errors);
|
||||
}
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
|
||||
private boolean transferAmount(final List<Throwable> errors, final AccountTransferDTO accountTransferDTO, final Long instructionId) {
|
||||
boolean transferCompleted = true;
|
||||
StringBuilder errorLog = new StringBuilder();
|
||||
StringBuilder updateQuery = new StringBuilder(
|
||||
"INSERT INTO m_account_transfer_standing_instructions_history (standing_instruction_id, " + sqlGenerator.escape("status")
|
||||
+ ", amount,execution_time, error_log) VALUES (");
|
||||
try {
|
||||
accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
} catch (final PlatformApiDataValidationException e) {
|
||||
errors.add(new Exception("Validation exception while transfering funds for standing Instruction id" + instructionId + " from "
|
||||
+ accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("Validation exception while trasfering funds ").append(e.getDefaultUserMessage());
|
||||
} catch (final InsufficientAccountBalanceException e) {
|
||||
errors.add(new Exception("InsufficientAccountBalance Exception while trasfering funds for standing Instruction id"
|
||||
+ instructionId + " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("InsufficientAccountBalance Exception ");
|
||||
} catch (final AbstractPlatformServiceUnavailableException e) {
|
||||
errors.add(new Exception("Platform exception while trasfering funds for standing Instruction id" + instructionId + " from "
|
||||
+ accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("Platform exception while trasfering funds ").append(e.getDefaultUserMessage());
|
||||
} catch (Exception e) {
|
||||
errors.add(new Exception("Unhandled System Exception while trasfering funds for standing Instruction id" + instructionId
|
||||
+ " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("Exception while trasfering funds ").append(e.getMessage());
|
||||
|
||||
}
|
||||
updateQuery.append(instructionId).append(",");
|
||||
if (errorLog.length() > 0) {
|
||||
transferCompleted = false;
|
||||
updateQuery.append("'failed'").append(",");
|
||||
} else {
|
||||
updateQuery.append("'success'").append(",");
|
||||
}
|
||||
updateQuery.append(accountTransferDTO.getTransactionAmount().doubleValue());
|
||||
updateQuery.append(", now(),");
|
||||
updateQuery.append("'").append(errorLog).append("')");
|
||||
jdbcTemplate.update(updateQuery.toString());
|
||||
return transferCompleted;
|
||||
}
|
||||
|
||||
public boolean isDueForTransfer(StandingInstructionDuesData standingInstructionDuesData) {
|
||||
return standingInstructionDuesData.dueDate() != null
|
||||
&& !standingInstructionDuesData.dueDate().isAfter(LocalDate.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.jobs.executestandinginstructions;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionData;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData;
|
||||
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.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||
import org.springframework.batch.repeat.RepeatStatus;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ExecuteStandingInstructionsTasklet implements Tasklet {
|
||||
|
||||
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||
Collection<StandingInstructionData> instructionData = standingInstructionReadPlatformService
|
||||
.retrieveAll(StandingInstructionStatus.ACTIVE.getValue());
|
||||
List<Throwable> errors = new ArrayList<>();
|
||||
for (StandingInstructionData data : instructionData) {
|
||||
boolean isDueForTransfer = false;
|
||||
AccountTransferRecurrenceType recurrenceType = data.recurrenceTypeEnum();
|
||||
StandingInstructionType instructionType = data.instructionTypeEnum();
|
||||
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
||||
if (recurrenceType.isPeriodicRecurrence()) {
|
||||
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
||||
PeriodFrequencyType frequencyType = data.recurrenceFrequencyEnum();
|
||||
LocalDate startDate = data.getValidFrom();
|
||||
if (frequencyType.isMonthly()) {
|
||||
startDate = startDate.withDayOfMonth(data.getRecurrenceOnDay());
|
||||
if (DateUtils.isBefore(startDate, data.getValidFrom())) {
|
||||
startDate = startDate.plusMonths(1);
|
||||
}
|
||||
} else if (frequencyType.isYearly()) {
|
||||
startDate = startDate.withDayOfMonth(data.getRecurrenceOnDay()).withMonth(data.getRecurrenceOnMonth());
|
||||
if (DateUtils.isBefore(startDate, data.getValidFrom())) {
|
||||
startDate = startDate.plusYears(1);
|
||||
}
|
||||
}
|
||||
isDueForTransfer = scheduledDateGenerator.isDateFallsInSchedule(frequencyType, data.getRecurrenceInterval(), startDate,
|
||||
transactionDate);
|
||||
|
||||
}
|
||||
BigDecimal transactionAmount = data.getAmount();
|
||||
if (data.toAccountTypeEnum() != null && data.toAccountTypeEnum().isLoanAccount()
|
||||
&& (recurrenceType.isDuesRecurrence() || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) {
|
||||
StandingInstructionDuesData standingInstructionDuesData = standingInstructionReadPlatformService
|
||||
.retriveLoanDuesData(data.getToAccount().getId());
|
||||
if (data.instructionTypeEnum().isDuesAmoutTransfer()) {
|
||||
transactionAmount = standingInstructionDuesData.totalDueAmount();
|
||||
}
|
||||
if (recurrenceType.isDuesRecurrence()) {
|
||||
isDueForTransfer = isDueForTransfer(standingInstructionDuesData);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDueForTransfer && transactionAmount != null && transactionAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
final SavingsAccount fromSavingsAccount = null;
|
||||
final boolean isRegularTransaction = true;
|
||||
final boolean isExceptionForBalanceCheck = false;
|
||||
// Load existing AccountTransferDetails so transactions are linked to
|
||||
// this standing instruction and appear in its transaction history
|
||||
final AccountTransferDetails existingDetails = data.getAccountDetailId() != null
|
||||
? accountTransferDetailRepository.findById(data.getAccountDetailId()).orElse(null)
|
||||
: null;
|
||||
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount,
|
||||
data.fromAccountTypeEnum(), data.toAccountTypeEnum(), data.getFromAccount().getId(), data.getToAccount().getId(),
|
||||
data.getName() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null,
|
||||
data.transferTypeEnum().getValue(), existingDetails, null, ExternalId.empty(), null, null, fromSavingsAccount,
|
||||
isRegularTransaction, isExceptionForBalanceCheck);
|
||||
final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId());
|
||||
|
||||
if (transferCompleted) {
|
||||
final String updateQuery = "UPDATE m_account_transfer_standing_instructions SET last_run_date = ? where id = ?";
|
||||
jdbcTemplate.update(updateQuery, transactionDate, data.getId());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
throw new JobExecutionException(errors);
|
||||
}
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
|
||||
private boolean transferAmount(final List<Throwable> errors, final AccountTransferDTO accountTransferDTO, final Long instructionId) {
|
||||
boolean transferCompleted = true;
|
||||
StringBuilder errorLog = new StringBuilder();
|
||||
StringBuilder updateQuery = new StringBuilder(
|
||||
"INSERT INTO m_account_transfer_standing_instructions_history (standing_instruction_id, " + sqlGenerator.escape("status")
|
||||
+ ", amount,execution_time, error_log) VALUES (");
|
||||
try {
|
||||
accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
} catch (final PlatformApiDataValidationException e) {
|
||||
errors.add(new Exception("Validation exception while transfering funds for standing Instruction id" + instructionId + " from "
|
||||
+ accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("Validation exception while trasfering funds ").append(e.getDefaultUserMessage());
|
||||
} catch (final InsufficientAccountBalanceException e) {
|
||||
errors.add(new Exception("InsufficientAccountBalance Exception while trasfering funds for standing Instruction id"
|
||||
+ instructionId + " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("InsufficientAccountBalance Exception ");
|
||||
} catch (final AbstractPlatformServiceUnavailableException e) {
|
||||
errors.add(new Exception("Platform exception while trasfering funds for standing Instruction id" + instructionId + " from "
|
||||
+ accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("Platform exception while trasfering funds ").append(e.getDefaultUserMessage());
|
||||
} catch (Exception e) {
|
||||
errors.add(new Exception("Unhandled System Exception while trasfering funds for standing Instruction id" + instructionId
|
||||
+ " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("Exception while trasfering funds ").append(e.getMessage());
|
||||
|
||||
}
|
||||
updateQuery.append(instructionId).append(",");
|
||||
if (errorLog.length() > 0) {
|
||||
transferCompleted = false;
|
||||
updateQuery.append("'failed'").append(",");
|
||||
} else {
|
||||
updateQuery.append("'success'").append(",");
|
||||
}
|
||||
updateQuery.append(accountTransferDTO.getTransactionAmount().doubleValue());
|
||||
updateQuery.append(", now(),");
|
||||
updateQuery.append("'").append(errorLog).append("')");
|
||||
jdbcTemplate.update(updateQuery.toString());
|
||||
return transferCompleted;
|
||||
}
|
||||
|
||||
public boolean isDueForTransfer(StandingInstructionDuesData standingInstructionDuesData) {
|
||||
return standingInstructionDuesData.dueDate() != null
|
||||
&& !standingInstructionDuesData.dueDate().isAfter(LocalDate.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||
}
|
||||
}
|
||||
|
||||
+206
-202
@@ -1,202 +1,206 @@
|
||||
/**
|
||||
* 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 org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
|
||||
public final class AccountTransferEnumerations {
|
||||
|
||||
private AccountTransferEnumerations() {
|
||||
|
||||
}
|
||||
|
||||
public static EnumOptionData accountType(final Integer type) {
|
||||
return accountType(PortfolioAccountType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData accountType(final PortfolioAccountType type) {
|
||||
|
||||
EnumOptionData optionData = null;
|
||||
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case LOAN:
|
||||
optionData = new EnumOptionData(PortfolioAccountType.LOAN.getValue().longValue(), PortfolioAccountType.LOAN.getCode(),
|
||||
"Loan Account");
|
||||
break;
|
||||
case SAVINGS:
|
||||
optionData = new EnumOptionData(PortfolioAccountType.SAVINGS.getValue().longValue(),
|
||||
PortfolioAccountType.SAVINGS.getCode(), "Savings Account");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData recurrenceType(final Integer type) {
|
||||
return recurrenceType(AccountTransferRecurrenceType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData recurrenceType(final AccountTransferRecurrenceType type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case PERIODIC:
|
||||
optionData = new EnumOptionData(AccountTransferRecurrenceType.PERIODIC.getValue().longValue(),
|
||||
AccountTransferRecurrenceType.PERIODIC.getCode(), "Periodic Recurrence");
|
||||
break;
|
||||
case AS_PER_DUES:
|
||||
optionData = new EnumOptionData(AccountTransferRecurrenceType.AS_PER_DUES.getValue().longValue(),
|
||||
AccountTransferRecurrenceType.AS_PER_DUES.getCode(), "As Per Dues Recurrence");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData transferType(final Integer type) {
|
||||
return transferType(AccountTransferType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData transferType(final AccountTransferType type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case ACCOUNT_TRANSFER:
|
||||
optionData = new EnumOptionData(AccountTransferType.ACCOUNT_TRANSFER.getValue().longValue(),
|
||||
AccountTransferType.ACCOUNT_TRANSFER.getCode(), "Account Transfer");
|
||||
break;
|
||||
case LOAN_REPAYMENT:
|
||||
optionData = new EnumOptionData(AccountTransferType.LOAN_REPAYMENT.getValue().longValue(),
|
||||
AccountTransferType.LOAN_REPAYMENT.getCode(), "Loan Repayment");
|
||||
break;
|
||||
case CHARGE_PAYMENT:
|
||||
optionData = new EnumOptionData(AccountTransferType.CHARGE_PAYMENT.getValue().longValue(),
|
||||
AccountTransferType.CHARGE_PAYMENT.getCode(), "Charge Payment");
|
||||
break;
|
||||
case INTEREST_TRANSFER:
|
||||
optionData = new EnumOptionData(AccountTransferType.INTEREST_TRANSFER.getValue().longValue(),
|
||||
AccountTransferType.INTEREST_TRANSFER.getCode(), "Interest Transfer");
|
||||
break;
|
||||
case LOAN_DOWN_PAYMENT:
|
||||
optionData = new EnumOptionData(AccountTransferType.LOAN_DOWN_PAYMENT.getValue().longValue(),
|
||||
AccountTransferType.LOAN_DOWN_PAYMENT.getCode(), "Loan Down Payment");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionPriority(final Integer type) {
|
||||
return standingInstructionPriority(StandingInstructionPriority.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionPriority(final StandingInstructionPriority type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case HIGH:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.HIGH.getValue().longValue(),
|
||||
StandingInstructionPriority.HIGH.getCode(), "High Priority");
|
||||
break;
|
||||
case LOW:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.LOW.getValue().longValue(),
|
||||
StandingInstructionPriority.LOW.getCode(), "Low Priority");
|
||||
break;
|
||||
case MEDIUM:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.MEDIUM.getValue().longValue(),
|
||||
StandingInstructionPriority.MEDIUM.getCode(), "Medium Priority");
|
||||
break;
|
||||
case URGENT:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.URGENT.getValue().longValue(),
|
||||
StandingInstructionPriority.URGENT.getCode(), "Urgent Priority");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionStatus(final Integer type) {
|
||||
return standingInstructionStatus(StandingInstructionStatus.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionStatus(final StandingInstructionStatus type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case ACTIVE:
|
||||
optionData = new EnumOptionData(StandingInstructionStatus.ACTIVE.getValue().longValue(),
|
||||
StandingInstructionStatus.ACTIVE.getCode(), "Active");
|
||||
break;
|
||||
case DELETED:
|
||||
optionData = new EnumOptionData(StandingInstructionStatus.DELETED.getValue().longValue(),
|
||||
StandingInstructionStatus.DELETED.getCode(), "Deleted");
|
||||
break;
|
||||
case DISABLED:
|
||||
optionData = new EnumOptionData(StandingInstructionStatus.DISABLED.getValue().longValue(),
|
||||
StandingInstructionStatus.DISABLED.getCode(), "Disabled");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionType(final Integer type) {
|
||||
return standingInstructionType(StandingInstructionType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionType(final StandingInstructionType type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case DUES:
|
||||
optionData = new EnumOptionData(StandingInstructionType.DUES.getValue().longValue(),
|
||||
StandingInstructionType.DUES.getCode(), "Dues");
|
||||
break;
|
||||
case FIXED:
|
||||
optionData = new EnumOptionData(StandingInstructionType.FIXED.getValue().longValue(),
|
||||
StandingInstructionType.FIXED.getCode(), "Fixed");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 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 org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
|
||||
public final class AccountTransferEnumerations {
|
||||
|
||||
private AccountTransferEnumerations() {
|
||||
|
||||
}
|
||||
|
||||
public static EnumOptionData accountType(final Integer type) {
|
||||
return accountType(PortfolioAccountType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData accountType(final PortfolioAccountType type) {
|
||||
|
||||
EnumOptionData optionData = null;
|
||||
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case LOAN:
|
||||
optionData = new EnumOptionData(PortfolioAccountType.LOAN.getValue().longValue(), PortfolioAccountType.LOAN.getCode(),
|
||||
"Loan Account");
|
||||
break;
|
||||
case SAVINGS:
|
||||
optionData = new EnumOptionData(PortfolioAccountType.SAVINGS.getValue().longValue(),
|
||||
PortfolioAccountType.SAVINGS.getCode(), "Savings Account");
|
||||
break;
|
||||
case CURRENT:
|
||||
optionData = new EnumOptionData(PortfolioAccountType.CURRENT.getValue().longValue(),
|
||||
PortfolioAccountType.CURRENT.getCode(), "Current Account");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData recurrenceType(final Integer type) {
|
||||
return recurrenceType(AccountTransferRecurrenceType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData recurrenceType(final AccountTransferRecurrenceType type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case PERIODIC:
|
||||
optionData = new EnumOptionData(AccountTransferRecurrenceType.PERIODIC.getValue().longValue(),
|
||||
AccountTransferRecurrenceType.PERIODIC.getCode(), "Periodic Recurrence");
|
||||
break;
|
||||
case AS_PER_DUES:
|
||||
optionData = new EnumOptionData(AccountTransferRecurrenceType.AS_PER_DUES.getValue().longValue(),
|
||||
AccountTransferRecurrenceType.AS_PER_DUES.getCode(), "As Per Dues Recurrence");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData transferType(final Integer type) {
|
||||
return transferType(AccountTransferType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData transferType(final AccountTransferType type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case ACCOUNT_TRANSFER:
|
||||
optionData = new EnumOptionData(AccountTransferType.ACCOUNT_TRANSFER.getValue().longValue(),
|
||||
AccountTransferType.ACCOUNT_TRANSFER.getCode(), "Account Transfer");
|
||||
break;
|
||||
case LOAN_REPAYMENT:
|
||||
optionData = new EnumOptionData(AccountTransferType.LOAN_REPAYMENT.getValue().longValue(),
|
||||
AccountTransferType.LOAN_REPAYMENT.getCode(), "Loan Repayment");
|
||||
break;
|
||||
case CHARGE_PAYMENT:
|
||||
optionData = new EnumOptionData(AccountTransferType.CHARGE_PAYMENT.getValue().longValue(),
|
||||
AccountTransferType.CHARGE_PAYMENT.getCode(), "Charge Payment");
|
||||
break;
|
||||
case INTEREST_TRANSFER:
|
||||
optionData = new EnumOptionData(AccountTransferType.INTEREST_TRANSFER.getValue().longValue(),
|
||||
AccountTransferType.INTEREST_TRANSFER.getCode(), "Interest Transfer");
|
||||
break;
|
||||
case LOAN_DOWN_PAYMENT:
|
||||
optionData = new EnumOptionData(AccountTransferType.LOAN_DOWN_PAYMENT.getValue().longValue(),
|
||||
AccountTransferType.LOAN_DOWN_PAYMENT.getCode(), "Loan Down Payment");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionPriority(final Integer type) {
|
||||
return standingInstructionPriority(StandingInstructionPriority.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionPriority(final StandingInstructionPriority type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case HIGH:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.HIGH.getValue().longValue(),
|
||||
StandingInstructionPriority.HIGH.getCode(), "High Priority");
|
||||
break;
|
||||
case LOW:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.LOW.getValue().longValue(),
|
||||
StandingInstructionPriority.LOW.getCode(), "Low Priority");
|
||||
break;
|
||||
case MEDIUM:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.MEDIUM.getValue().longValue(),
|
||||
StandingInstructionPriority.MEDIUM.getCode(), "Medium Priority");
|
||||
break;
|
||||
case URGENT:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.URGENT.getValue().longValue(),
|
||||
StandingInstructionPriority.URGENT.getCode(), "Urgent Priority");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionStatus(final Integer type) {
|
||||
return standingInstructionStatus(StandingInstructionStatus.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionStatus(final StandingInstructionStatus type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case ACTIVE:
|
||||
optionData = new EnumOptionData(StandingInstructionStatus.ACTIVE.getValue().longValue(),
|
||||
StandingInstructionStatus.ACTIVE.getCode(), "Active");
|
||||
break;
|
||||
case DELETED:
|
||||
optionData = new EnumOptionData(StandingInstructionStatus.DELETED.getValue().longValue(),
|
||||
StandingInstructionStatus.DELETED.getCode(), "Deleted");
|
||||
break;
|
||||
case DISABLED:
|
||||
optionData = new EnumOptionData(StandingInstructionStatus.DISABLED.getValue().longValue(),
|
||||
StandingInstructionStatus.DISABLED.getCode(), "Disabled");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionType(final Integer type) {
|
||||
return standingInstructionType(StandingInstructionType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionType(final StandingInstructionType type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case DUES:
|
||||
optionData = new EnumOptionData(StandingInstructionType.DUES.getValue().longValue(),
|
||||
StandingInstructionType.DUES.getCode(), "Dues");
|
||||
break;
|
||||
case FIXED:
|
||||
optionData = new EnumOptionData(StandingInstructionType.FIXED.getValue().longValue(),
|
||||
StandingInstructionType.FIXED.getCode(), "Fixed");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+865
-561
File diff suppressed because it is too large
Load Diff
+482
-396
@@ -1,396 +1,482 @@
|
||||
/**
|
||||
* 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 java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO;
|
||||
import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
|
||||
import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccountReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
// mapper
|
||||
private final PortfolioSavingsAccountMapper savingsAccountMapper;
|
||||
private final PortfolioLoanAccountMapper loanAccountMapper;
|
||||
private final PortfolioLoanAccountRefundByTransferMapper accountRefundByTransferMapper;
|
||||
|
||||
public PortfolioAccountReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.savingsAccountMapper = new PortfolioSavingsAccountMapper();
|
||||
this.loanAccountMapper = new PortfolioLoanAccountMapper();
|
||||
this.accountRefundByTransferMapper = new PortfolioLoanAccountRefundByTransferMapper(sqlGenerator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId) {
|
||||
return retrieveOne(accountId, accountTypeId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId, final String currencyCode) {
|
||||
|
||||
Object[] sqlParams = new Object[] { accountId };
|
||||
PortfolioAccountData accountData = null;
|
||||
try {
|
||||
String sql = null;
|
||||
final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId);
|
||||
switch (accountType) {
|
||||
case INVALID:
|
||||
break;
|
||||
case LOAN:
|
||||
|
||||
sql = "select " + this.loanAccountMapper.schema() + " where la.id = ?";
|
||||
if (currencyCode != null) {
|
||||
sql += " and la.currency_code = ?";
|
||||
sqlParams = new Object[] { accountId, currencyCode };
|
||||
}
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.loanAccountMapper, sqlParams);
|
||||
break;
|
||||
case SAVINGS:
|
||||
sql = "select " + this.savingsAccountMapper.schema() + " where sa.id = ?";
|
||||
if (currencyCode != null) {
|
||||
sql += " and sa.currency_code = ?";
|
||||
sqlParams = new Object[] { accountId, currencyCode };
|
||||
}
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.savingsAccountMapper, sqlParams);
|
||||
break;
|
||||
}
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AccountTransferNotFoundException(accountId, e);
|
||||
}
|
||||
|
||||
return accountData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PortfolioAccountData> retrieveAllForLookup(final PortfolioAccountDTO portfolioAccountDTO) {
|
||||
|
||||
final List<Object> sqlParams = new ArrayList<>();
|
||||
// sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
Collection<PortfolioAccountData> accounts = null;
|
||||
String sql = null;
|
||||
long defaultAccountStatus = 300; // Active Status
|
||||
if (portfolioAccountDTO.getAccountStatus() != null) {
|
||||
defaultAccountStatus = portfolioAccountDTO.getFirstAccountStatus();
|
||||
}
|
||||
final PortfolioAccountType accountType = PortfolioAccountType.fromInt(portfolioAccountDTO.getAccountTypeId());
|
||||
switch (accountType) {
|
||||
case INVALID:
|
||||
break;
|
||||
case LOAN:
|
||||
sql = "select " + this.loanAccountMapper.schema() + " where ";
|
||||
if (portfolioAccountDTO.getClientId() != null) {
|
||||
sql += " la.client_id = ? and la.loan_status_id in (?) ";
|
||||
sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
} else {
|
||||
sql += " la.loan_status_id in (?) ";
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
}
|
||||
if (portfolioAccountDTO.getCurrencyCode() != null) {
|
||||
sql += " and la.currency_code = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getCurrencyCode());
|
||||
}
|
||||
|
||||
accounts = this.jdbcTemplate.query(sql, this.loanAccountMapper, sqlParams.toArray()); // NOSONAR
|
||||
break;
|
||||
case SAVINGS:
|
||||
sql = "select " + this.savingsAccountMapper.schema() + " where ";
|
||||
if (portfolioAccountDTO.getClientId() != null) {
|
||||
sql += " sa.client_id = ? and sa.status_enum in (?) ";
|
||||
sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
} else {
|
||||
sql += " sa.status_enum in (?) ";
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
}
|
||||
if (portfolioAccountDTO.getCurrencyCode() != null) {
|
||||
sql += " and sa.currency_code = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getCurrencyCode());
|
||||
}
|
||||
|
||||
if (portfolioAccountDTO.getDepositType() != null) {
|
||||
sql += " and sa.deposit_type_enum = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getDepositType().shortValue());
|
||||
}
|
||||
|
||||
if (portfolioAccountDTO.isExcludeOverDraftAccounts()) {
|
||||
sql += " and sa.allow_overdraft = false";
|
||||
}
|
||||
|
||||
if (portfolioAccountDTO.getClientId() == null && portfolioAccountDTO.getGroupId() != null) {
|
||||
sql += " and sa.group_id = ? ";
|
||||
sqlParams.add(portfolioAccountDTO.getGroupId());
|
||||
}
|
||||
|
||||
accounts = this.jdbcTemplate.query(sql, this.savingsAccountMapper, sqlParams.toArray()); // NOSONAR
|
||||
break;
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
private static final class PortfolioSavingsAccountMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
PortfolioSavingsAccountMapper() {
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("sp.id as productId, sp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("sa.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_savings_account sa ");
|
||||
sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = sa.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = sa.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol,
|
||||
currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PortfolioLoanAccountMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
PortfolioLoanAccountMapper() {
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("lp.id as productId, lp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append("la.total_overpaid_derived as totalOverpaid, ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_loan la ");
|
||||
sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol,
|
||||
currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PortfolioLoanAccountRefundByTransferMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private String schemaSql;
|
||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
|
||||
PortfolioLoanAccountRefundByTransferMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
this.sqlGenerator = sqlGenerator;
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
final StringBuilder amountQueryString = new StringBuilder(400);
|
||||
amountQueryString.append("(select (SUM(COALESCE(mr.principal_completed_derived, 0)) +");
|
||||
amountQueryString.append("SUM(COALESCE(mr.interest_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(COALESCE(mr.fee_charges_completed_derived, 0)) + ");
|
||||
amountQueryString.append(" SUM(COALESCE(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived");
|
||||
amountQueryString.append(" from m_loan ml INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id");
|
||||
amountQueryString.append(" where ml.id=? and ml.loan_status_id = 300");
|
||||
amountQueryString.append(" and mr.duedate >= " + sqlGenerator.currentBusinessDate() + " group by ml.id having");
|
||||
amountQueryString.append(" (SUM(COALESCE(mr.principal_completed_derived, 0)) + ");
|
||||
amountQueryString.append(" SUM(COALESCE(mr.interest_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(COALESCE(mr.fee_charges_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(COALESCE(mr.penalty_charges_completed_derived, 0))) > 0) as totalOverpaid ");
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("lp.id as productId, lp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append(amountQueryString.toString());
|
||||
sqlBuilder.append(", ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_loan la ");
|
||||
sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol,
|
||||
currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOneByPaidInAdvance(Long accountId, Integer accountTypeId) {
|
||||
// TODO Auto-generated method stub
|
||||
Object[] sqlParams = new Object[] { accountId, accountId };
|
||||
PortfolioAccountData accountData = null;
|
||||
// String currencyCode = null;
|
||||
try {
|
||||
String sql = null;
|
||||
// final PortfolioAccountType accountType =
|
||||
// PortfolioAccountType.fromInt(accountTypeId);
|
||||
|
||||
sql = "select " + this.accountRefundByTransferMapper.schema() + " where la.id = ?";
|
||||
/*
|
||||
* if (currencyCode != null) { sql += " and la.currency_code = ?"; sqlParams = new Object[] {accountId ,
|
||||
* accountId,currencyCode }; }
|
||||
*/
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.accountRefundByTransferMapper, sqlParams);
|
||||
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AccountTransferNotFoundException(accountId, e);
|
||||
}
|
||||
|
||||
return accountData;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO;
|
||||
import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
|
||||
import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccountReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
// mapper
|
||||
private final PortfolioSavingsAccountMapper savingsAccountMapper;
|
||||
private final PortfolioLoanAccountMapper loanAccountMapper;
|
||||
private final PortfolioLoanAccountRefundByTransferMapper accountRefundByTransferMapper;
|
||||
private final PortfolioCurrentAccountMapper currentAccountMapper;
|
||||
|
||||
public PortfolioAccountReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.savingsAccountMapper = new PortfolioSavingsAccountMapper();
|
||||
this.loanAccountMapper = new PortfolioLoanAccountMapper();
|
||||
this.accountRefundByTransferMapper = new PortfolioLoanAccountRefundByTransferMapper(sqlGenerator);
|
||||
this.currentAccountMapper = new PortfolioCurrentAccountMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId) {
|
||||
return retrieveOne(accountId, accountTypeId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId, final String currencyCode) {
|
||||
|
||||
Object[] sqlParams = new Object[] { accountId };
|
||||
PortfolioAccountData accountData = null;
|
||||
try {
|
||||
String sql = null;
|
||||
final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId);
|
||||
switch (accountType) {
|
||||
case INVALID:
|
||||
break;
|
||||
case LOAN:
|
||||
|
||||
sql = "select " + this.loanAccountMapper.schema() + " where la.id = ?";
|
||||
if (currencyCode != null) {
|
||||
sql += " and la.currency_code = ?";
|
||||
sqlParams = new Object[] { accountId, currencyCode };
|
||||
}
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.loanAccountMapper, sqlParams);
|
||||
break;
|
||||
case SAVINGS:
|
||||
sql = "select " + this.savingsAccountMapper.schema() + " where sa.id = ?";
|
||||
if (currencyCode != null) {
|
||||
sql += " and sa.currency_code = ?";
|
||||
sqlParams = new Object[] { accountId, currencyCode };
|
||||
}
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.savingsAccountMapper, sqlParams);
|
||||
break;
|
||||
case CURRENT:
|
||||
sql = "select " + this.currentAccountMapper.schema() + " where ca.id = ?";
|
||||
if (currencyCode != null) {
|
||||
sql += " and ca.currency_code = ?";
|
||||
sqlParams = new Object[] { accountId, currencyCode };
|
||||
}
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.currentAccountMapper, sqlParams);
|
||||
break;
|
||||
}
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AccountTransferNotFoundException(accountId, e);
|
||||
}
|
||||
|
||||
return accountData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PortfolioAccountData> retrieveAllForLookup(final PortfolioAccountDTO portfolioAccountDTO) {
|
||||
|
||||
final List<Object> sqlParams = new ArrayList<>();
|
||||
// sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
Collection<PortfolioAccountData> accounts = null;
|
||||
String sql = null;
|
||||
long defaultAccountStatus = 300; // Active Status
|
||||
if (portfolioAccountDTO.getAccountStatus() != null) {
|
||||
defaultAccountStatus = portfolioAccountDTO.getFirstAccountStatus();
|
||||
}
|
||||
final PortfolioAccountType accountType = PortfolioAccountType.fromInt(portfolioAccountDTO.getAccountTypeId());
|
||||
switch (accountType) {
|
||||
case INVALID:
|
||||
break;
|
||||
case LOAN:
|
||||
sql = "select " + this.loanAccountMapper.schema() + " where ";
|
||||
if (portfolioAccountDTO.getClientId() != null) {
|
||||
sql += " la.client_id = ? and la.loan_status_id in (?) ";
|
||||
sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
} else {
|
||||
sql += " la.loan_status_id in (?) ";
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
}
|
||||
if (portfolioAccountDTO.getCurrencyCode() != null) {
|
||||
sql += " and la.currency_code = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getCurrencyCode());
|
||||
}
|
||||
|
||||
accounts = this.jdbcTemplate.query(sql, this.loanAccountMapper, sqlParams.toArray()); // NOSONAR
|
||||
break;
|
||||
case SAVINGS:
|
||||
sql = "select " + this.savingsAccountMapper.schema() + " where ";
|
||||
if (portfolioAccountDTO.getClientId() != null) {
|
||||
sql += " sa.client_id = ? and sa.status_enum in (?) ";
|
||||
sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
} else {
|
||||
sql += " sa.status_enum in (?) ";
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
}
|
||||
if (portfolioAccountDTO.getCurrencyCode() != null) {
|
||||
sql += " and sa.currency_code = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getCurrencyCode());
|
||||
}
|
||||
|
||||
if (portfolioAccountDTO.getDepositType() != null) {
|
||||
sql += " and sa.deposit_type_enum = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getDepositType().shortValue());
|
||||
}
|
||||
|
||||
if (portfolioAccountDTO.isExcludeOverDraftAccounts()) {
|
||||
sql += " and sa.allow_overdraft = false";
|
||||
}
|
||||
|
||||
if (portfolioAccountDTO.getClientId() == null && portfolioAccountDTO.getGroupId() != null) {
|
||||
sql += " and sa.group_id = ? ";
|
||||
sqlParams.add(portfolioAccountDTO.getGroupId());
|
||||
}
|
||||
|
||||
accounts = this.jdbcTemplate.query(sql, this.savingsAccountMapper, sqlParams.toArray()); // NOSONAR
|
||||
break;
|
||||
case CURRENT:
|
||||
sql = "select " + this.currentAccountMapper.schema() + " where ";
|
||||
if (portfolioAccountDTO.getClientId() != null) {
|
||||
sql += " ca.client_id = ? and ca.status_enum in (?) ";
|
||||
sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
} else {
|
||||
sql += " ca.status_enum in (?) ";
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
}
|
||||
if (portfolioAccountDTO.getCurrencyCode() != null) {
|
||||
sql += " and ca.currency_code = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getCurrencyCode());
|
||||
}
|
||||
|
||||
accounts = this.jdbcTemplate.query(sql, this.currentAccountMapper, sqlParams.toArray()); // NOSONAR
|
||||
break;
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
private static final class PortfolioSavingsAccountMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
PortfolioSavingsAccountMapper() {
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("sp.id as productId, sp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("sa.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_savings_account sa ");
|
||||
sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = sa.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = sa.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol,
|
||||
currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PortfolioLoanAccountMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
PortfolioLoanAccountMapper() {
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("lp.id as productId, lp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append("la.total_overpaid_derived as totalOverpaid, ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_loan la ");
|
||||
sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol,
|
||||
currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PortfolioLoanAccountRefundByTransferMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private String schemaSql;
|
||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
|
||||
PortfolioLoanAccountRefundByTransferMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
this.sqlGenerator = sqlGenerator;
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
final StringBuilder amountQueryString = new StringBuilder(400);
|
||||
amountQueryString.append("(select (SUM(COALESCE(mr.principal_completed_derived, 0)) +");
|
||||
amountQueryString.append("SUM(COALESCE(mr.interest_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(COALESCE(mr.fee_charges_completed_derived, 0)) + ");
|
||||
amountQueryString.append(" SUM(COALESCE(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived");
|
||||
amountQueryString.append(" from m_loan ml INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id");
|
||||
amountQueryString.append(" where ml.id=? and ml.loan_status_id = 300");
|
||||
amountQueryString.append(" and mr.duedate >= " + sqlGenerator.currentBusinessDate() + " group by ml.id having");
|
||||
amountQueryString.append(" (SUM(COALESCE(mr.principal_completed_derived, 0)) + ");
|
||||
amountQueryString.append(" SUM(COALESCE(mr.interest_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(COALESCE(mr.fee_charges_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(COALESCE(mr.penalty_charges_completed_derived, 0))) > 0) as totalOverpaid ");
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("lp.id as productId, lp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append(amountQueryString.toString());
|
||||
sqlBuilder.append(", ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_loan la ");
|
||||
sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol,
|
||||
currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOneByPaidInAdvance(Long accountId, Integer accountTypeId) {
|
||||
// TODO Auto-generated method stub
|
||||
Object[] sqlParams = new Object[] { accountId, accountId };
|
||||
PortfolioAccountData accountData = null;
|
||||
// String currencyCode = null;
|
||||
try {
|
||||
String sql = null;
|
||||
// final PortfolioAccountType accountType =
|
||||
// PortfolioAccountType.fromInt(accountTypeId);
|
||||
|
||||
sql = "select " + this.accountRefundByTransferMapper.schema() + " where la.id = ?";
|
||||
/*
|
||||
* if (currencyCode != null) { sql += " and la.currency_code = ?"; sqlParams = new Object[] {accountId ,
|
||||
* accountId,currencyCode }; }
|
||||
*/
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.accountRefundByTransferMapper, sqlParams);
|
||||
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AccountTransferNotFoundException(accountId, e);
|
||||
}
|
||||
|
||||
return accountData;
|
||||
}
|
||||
|
||||
private static final class PortfolioCurrentAccountMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
PortfolioCurrentAccountMapper() {
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("ca.id as id, ca.account_no as accountNo, ca.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("null as groupId, null as groupName, ");
|
||||
sqlBuilder.append("cp.id as productId, cp.name as productName, ");
|
||||
sqlBuilder.append("null as fieldOfficerId, null as fieldOfficerName, ");
|
||||
sqlBuilder.append("ca.currency_code as currencyCode, ca.currency_digits as currencyDigits, ");
|
||||
sqlBuilder.append("ca.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_current_account ca ");
|
||||
sqlBuilder.append("join m_current_product cp ON ca.product_id = cp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = ca.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = ca.client_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = JdbcSupport.getLong(rs, "fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
|
||||
currencyDisplaySymbol, currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+625
-591
File diff suppressed because it is too large
Load Diff
+179
-145
@@ -1,145 +1,179 @@
|
||||
/**
|
||||
* 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.fromAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.account.exception.StandingInstructionNotFoundException;
|
||||
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 StandingInstructionWritePlatformServiceImpl implements StandingInstructionWritePlatformService {
|
||||
|
||||
private final StandingInstructionDataValidator standingInstructionDataValidator;
|
||||
private final StandingInstructionAssembler standingInstructionAssembler;
|
||||
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
||||
private final StandingInstructionRepository standingInstructionRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult create(final JsonCommand command) {
|
||||
|
||||
this.standingInstructionDataValidator.validateForCreate(command);
|
||||
|
||||
final Integer fromAccountTypeId = command.integerValueSansLocaleOfParameterNamed(fromAccountTypeParamName);
|
||||
final PortfolioAccountType fromAccountType = PortfolioAccountType.fromInt(fromAccountTypeId);
|
||||
|
||||
final Integer toAccountTypeId = command.integerValueSansLocaleOfParameterNamed(toAccountTypeParamName);
|
||||
final PortfolioAccountType toAccountType = PortfolioAccountType.fromInt(toAccountTypeId);
|
||||
|
||||
final Long fromClientId = command.longValueOfParameterNamed(fromClientIdParamName);
|
||||
|
||||
Long standingInstructionId = null;
|
||||
try {
|
||||
if (isSavingsToSavingsAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler
|
||||
.assembleSavingsToSavingsTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isSavingsToLoanAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleSavingsToLoanTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isLoanToSavingsAccountTransfer(fromAccountType, toAccountType)) {
|
||||
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleLoanToSavingsTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
|
||||
}
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
final Throwable throwable = dve.getMostSpecificCause();
|
||||
handleDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(standingInstructionId)
|
||||
.withClientId(fromClientId);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void handleDataIntegrityIssues(final JsonCommand command, Throwable realCause, final NonTransientDataAccessException dve) {
|
||||
if (realCause.getMessage().contains("name")) {
|
||||
final String name = command.stringValueOfParameterNamed(StandingInstructionApiConstants.nameParamName);
|
||||
throw new PlatformDataIntegrityException("error.msg.standinginstruction.duplicate.name",
|
||||
"Standinginstruction with name `" + name + "` already exists", "name", name);
|
||||
}
|
||||
log.error("Error occured.", dve);
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.client.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
||||
}
|
||||
|
||||
private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount();
|
||||
}
|
||||
|
||||
private boolean isSavingsToLoanAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isSavingsAccount() && toAccountType.isLoanAccount();
|
||||
}
|
||||
|
||||
private boolean isSavingsToSavingsAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||
final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult update(final Long id, final JsonCommand command) {
|
||||
this.standingInstructionDataValidator.validateForUpdate(command);
|
||||
AccountTransferStandingInstruction standingInstructionsForUpdate = this.standingInstructionRepository.findById(id)
|
||||
.orElseThrow(() -> new StandingInstructionNotFoundException(id));
|
||||
final Map<String, Object> actualChanges = standingInstructionsForUpdate.update(command);
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(id).with(actualChanges).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult delete(final Long id) {
|
||||
AccountTransferStandingInstruction standingInstructionsForUpdate = this.standingInstructionRepository.findById(id).orElseThrow();
|
||||
// update the "deleted" and "name" properties of the standing
|
||||
// instruction
|
||||
standingInstructionsForUpdate.delete();
|
||||
|
||||
final Map<String, Object> actualChanges = new HashMap<>();
|
||||
actualChanges.put(statusParamName, StandingInstructionStatus.DELETED.getValue());
|
||||
return new CommandProcessingResultBuilder().withEntityId(id).with(actualChanges).build();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.fromAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.account.exception.StandingInstructionNotFoundException;
|
||||
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 StandingInstructionWritePlatformServiceImpl implements StandingInstructionWritePlatformService {
|
||||
|
||||
private final StandingInstructionDataValidator standingInstructionDataValidator;
|
||||
private final StandingInstructionAssembler standingInstructionAssembler;
|
||||
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
||||
private final StandingInstructionRepository standingInstructionRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult create(final JsonCommand command) {
|
||||
|
||||
this.standingInstructionDataValidator.validateForCreate(command);
|
||||
|
||||
final Integer fromAccountTypeId = command.integerValueSansLocaleOfParameterNamed(fromAccountTypeParamName);
|
||||
final PortfolioAccountType fromAccountType = PortfolioAccountType.fromInt(fromAccountTypeId);
|
||||
|
||||
final Integer toAccountTypeId = command.integerValueSansLocaleOfParameterNamed(toAccountTypeParamName);
|
||||
final PortfolioAccountType toAccountType = PortfolioAccountType.fromInt(toAccountTypeId);
|
||||
|
||||
final Long fromClientId = command.longValueOfParameterNamed(fromClientIdParamName);
|
||||
|
||||
Long standingInstructionId = null;
|
||||
try {
|
||||
if (isSavingsToSavingsAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler
|
||||
.assembleSavingsToSavingsTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isSavingsToLoanAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleSavingsToLoanTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isLoanToSavingsAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleLoanToSavingsTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isCurrentToCurrentAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleCurrentToCurrentTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isCurrentToSavingsAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleCurrentToSavingsTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isSavingsToCurrentAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleSavingsToCurrentTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isCurrentToLoanAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleCurrentToLoanTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
}
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
final Throwable throwable = dve.getMostSpecificCause();
|
||||
handleDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(standingInstructionId)
|
||||
.withClientId(fromClientId);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void handleDataIntegrityIssues(final JsonCommand command, Throwable realCause, final NonTransientDataAccessException dve) {
|
||||
if (realCause.getMessage().contains("name")) {
|
||||
final String name = command.stringValueOfParameterNamed(StandingInstructionApiConstants.nameParamName);
|
||||
throw new PlatformDataIntegrityException("error.msg.standinginstruction.duplicate.name",
|
||||
"Standinginstruction with name `" + name + "` already exists", "name", name);
|
||||
}
|
||||
log.error("Error occured.", dve);
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.client.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
||||
}
|
||||
|
||||
private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount();
|
||||
}
|
||||
|
||||
private boolean isSavingsToLoanAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isSavingsAccount() && toAccountType.isLoanAccount();
|
||||
}
|
||||
|
||||
private boolean isSavingsToSavingsAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||
final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount();
|
||||
}
|
||||
|
||||
private boolean isCurrentToCurrentAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||
final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isCurrentAccount() && toAccountType.isCurrentAccount();
|
||||
}
|
||||
|
||||
private boolean isCurrentToSavingsAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||
final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isCurrentAccount() && toAccountType.isSavingsAccount();
|
||||
}
|
||||
|
||||
private boolean isSavingsToCurrentAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||
final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isSavingsAccount() && toAccountType.isCurrentAccount();
|
||||
}
|
||||
|
||||
private boolean isCurrentToLoanAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||
final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isCurrentAccount() && toAccountType.isLoanAccount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult update(final Long id, final JsonCommand command) {
|
||||
this.standingInstructionDataValidator.validateForUpdate(command);
|
||||
AccountTransferStandingInstruction standingInstructionsForUpdate = this.standingInstructionRepository.findById(id)
|
||||
.orElseThrow(() -> new StandingInstructionNotFoundException(id));
|
||||
final Map<String, Object> actualChanges = standingInstructionsForUpdate.update(command);
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(id).with(actualChanges).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult delete(final Long id) {
|
||||
AccountTransferStandingInstruction standingInstructionsForUpdate = this.standingInstructionRepository.findById(id).orElseThrow();
|
||||
// update the "deleted" and "name" properties of the standing
|
||||
// instruction
|
||||
standingInstructionsForUpdate.delete();
|
||||
|
||||
final Map<String, Object> actualChanges = new HashMap<>();
|
||||
actualChanges.put(statusParamName, StandingInstructionStatus.DELETED.getValue());
|
||||
return new CommandProcessingResultBuilder().withEntityId(id).with(actualChanges).build();
|
||||
}
|
||||
}
|
||||
|
||||
+140
-134
@@ -1,134 +1,140 @@
|
||||
/**
|
||||
* 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.starter;
|
||||
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.infrastructure.core.service.PaginationHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
||||
import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
|
||||
import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.data.AccountTransfersDataValidator;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
|
||||
import org.apache.fineract.portfolio.account.mapper.AccountTransfersMapper;
|
||||
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.PortfolioAccountReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.PortfolioAccountReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionHistoryReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionHistoryReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
|
||||
import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.apache.fineract.portfolio.savings.domain.GSIMRepositoy;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class AccountConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccountAssociationsReadPlatformService.class)
|
||||
public AccountAssociationsReadPlatformService accountAssociationsReadPlatformService(JdbcTemplate jdbcTemplate) {
|
||||
return new AccountAssociationsReadPlatformServiceImpl(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccountTransfersReadPlatformService.class)
|
||||
public AccountTransfersReadPlatformService accountTransfersReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
ClientReadPlatformService clientReadPlatformService, OfficeReadPlatformService officeReadPlatformService,
|
||||
PortfolioAccountReadPlatformService portfolioAccountReadPlatformService, ColumnValidator columnValidator,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator, AccountTransfersMapper accountTransfersMapper, PaginationHelper paginationHelper,
|
||||
SqlValidator sqlValidator) {
|
||||
return new AccountTransfersReadPlatformServiceImpl(jdbcTemplate, clientReadPlatformService, officeReadPlatformService,
|
||||
portfolioAccountReadPlatformService, columnValidator, sqlGenerator, accountTransfersMapper, paginationHelper, sqlValidator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccountTransfersWritePlatformService.class)
|
||||
public AccountTransfersWritePlatformService accountTransfersWritePlatformService(
|
||||
AccountTransfersDataValidator accountTransfersDataValidator, AccountTransferAssembler accountTransferAssembler,
|
||||
AccountTransferRepository accountTransferRepository, SavingsAccountAssembler savingsAccountAssembler,
|
||||
SavingsAccountDomainService savingsAccountDomainService, LoanAssembler loanAccountAssembler,
|
||||
LoanAccountDomainService loanAccountDomainService, SavingsAccountWritePlatformService savingsAccountWritePlatformService,
|
||||
AccountTransferDetailRepository accountTransferDetailRepository, LoanReadPlatformService loanReadPlatformService,
|
||||
GSIMRepositoy gsimRepository, ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory,
|
||||
FineractProperties fineractProperties) {
|
||||
return new AccountTransfersWritePlatformServiceImpl(accountTransfersDataValidator, accountTransferAssembler,
|
||||
accountTransferRepository, savingsAccountAssembler, savingsAccountDomainService, loanAccountAssembler,
|
||||
loanAccountDomainService, savingsAccountWritePlatformService, accountTransferDetailRepository, loanReadPlatformService,
|
||||
gsimRepository, configurationDomainService, externalIdFactory, fineractProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(PortfolioAccountReadPlatformService.class)
|
||||
public PortfolioAccountReadPlatformService portfolioAccountReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
return new PortfolioAccountReadPlatformServiceImpl(jdbcTemplate, sqlGenerator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StandingInstructionHistoryReadPlatformService.class)
|
||||
public StandingInstructionHistoryReadPlatformService standingInstructionHistoryReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
|
||||
return new StandingInstructionHistoryReadPlatformServiceImpl(jdbcTemplate, columnValidator, sqlGenerator, paginationHelper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StandingInstructionReadPlatformService.class)
|
||||
public StandingInstructionReadPlatformService standingInstructionReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
ClientReadPlatformService clientReadPlatformService, OfficeReadPlatformService officeReadPlatformService,
|
||||
PortfolioAccountReadPlatformService portfolioAccountReadPlatformService,
|
||||
DropdownReadPlatformService dropdownReadPlatformService, ColumnValidator columnValidator,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
|
||||
return new StandingInstructionReadPlatformServiceImpl(jdbcTemplate, clientReadPlatformService, officeReadPlatformService,
|
||||
portfolioAccountReadPlatformService, dropdownReadPlatformService, columnValidator, sqlGenerator, paginationHelper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StandingInstructionWritePlatformService.class)
|
||||
public StandingInstructionWritePlatformService standingInstructionWritePlatformService(
|
||||
StandingInstructionDataValidator standingInstructionDataValidator, StandingInstructionAssembler standingInstructionAssembler,
|
||||
AccountTransferDetailRepository accountTransferDetailRepository, StandingInstructionRepository standingInstructionRepository) {
|
||||
return new StandingInstructionWritePlatformServiceImpl(standingInstructionDataValidator, standingInstructionAssembler,
|
||||
accountTransferDetailRepository, standingInstructionRepository);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.starter;
|
||||
|
||||
import org.apache.fineract.accounting.journalentry.service.AccountingProcessorHelper;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.infrastructure.core.service.PaginationHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
||||
import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
|
||||
import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountRepository;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransactionRepository;
|
||||
import org.apache.fineract.portfolio.account.data.AccountTransfersDataValidator;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
|
||||
import org.apache.fineract.portfolio.account.mapper.AccountTransfersMapper;
|
||||
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.PortfolioAccountReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.PortfolioAccountReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionHistoryReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionHistoryReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
|
||||
import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.apache.fineract.portfolio.savings.domain.GSIMRepositoy;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class AccountConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccountAssociationsReadPlatformService.class)
|
||||
public AccountAssociationsReadPlatformService accountAssociationsReadPlatformService(JdbcTemplate jdbcTemplate) {
|
||||
return new AccountAssociationsReadPlatformServiceImpl(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccountTransfersReadPlatformService.class)
|
||||
public AccountTransfersReadPlatformService accountTransfersReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
ClientReadPlatformService clientReadPlatformService, OfficeReadPlatformService officeReadPlatformService,
|
||||
PortfolioAccountReadPlatformService portfolioAccountReadPlatformService, ColumnValidator columnValidator,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator, AccountTransfersMapper accountTransfersMapper, PaginationHelper paginationHelper,
|
||||
SqlValidator sqlValidator) {
|
||||
return new AccountTransfersReadPlatformServiceImpl(jdbcTemplate, clientReadPlatformService, officeReadPlatformService,
|
||||
portfolioAccountReadPlatformService, columnValidator, sqlGenerator, accountTransfersMapper, paginationHelper, sqlValidator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccountTransfersWritePlatformService.class)
|
||||
public AccountTransfersWritePlatformService accountTransfersWritePlatformService(
|
||||
AccountTransfersDataValidator accountTransfersDataValidator, AccountTransferAssembler accountTransferAssembler,
|
||||
AccountTransferRepository accountTransferRepository, SavingsAccountAssembler savingsAccountAssembler,
|
||||
SavingsAccountDomainService savingsAccountDomainService, LoanAssembler loanAccountAssembler,
|
||||
LoanAccountDomainService loanAccountDomainService, SavingsAccountWritePlatformService savingsAccountWritePlatformService,
|
||||
AccountTransferDetailRepository accountTransferDetailRepository, LoanReadPlatformService loanReadPlatformService,
|
||||
GSIMRepositoy gsimRepository, ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory,
|
||||
FineractProperties fineractProperties, CurrentAccountRepository currentAccountRepository,
|
||||
CurrentAccountTransactionRepository currentAccountTransactionRepository,
|
||||
AccountingProcessorHelper accountingProcessorHelper) {
|
||||
return new AccountTransfersWritePlatformServiceImpl(accountTransfersDataValidator, accountTransferAssembler,
|
||||
accountTransferRepository, savingsAccountAssembler, savingsAccountDomainService, loanAccountAssembler,
|
||||
loanAccountDomainService, savingsAccountWritePlatformService, accountTransferDetailRepository, loanReadPlatformService,
|
||||
gsimRepository, configurationDomainService, externalIdFactory, fineractProperties,
|
||||
currentAccountRepository, currentAccountTransactionRepository, accountingProcessorHelper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(PortfolioAccountReadPlatformService.class)
|
||||
public PortfolioAccountReadPlatformService portfolioAccountReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
return new PortfolioAccountReadPlatformServiceImpl(jdbcTemplate, sqlGenerator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StandingInstructionHistoryReadPlatformService.class)
|
||||
public StandingInstructionHistoryReadPlatformService standingInstructionHistoryReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
|
||||
return new StandingInstructionHistoryReadPlatformServiceImpl(jdbcTemplate, columnValidator, sqlGenerator, paginationHelper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StandingInstructionReadPlatformService.class)
|
||||
public StandingInstructionReadPlatformService standingInstructionReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
ClientReadPlatformService clientReadPlatformService, OfficeReadPlatformService officeReadPlatformService,
|
||||
PortfolioAccountReadPlatformService portfolioAccountReadPlatformService,
|
||||
DropdownReadPlatformService dropdownReadPlatformService, ColumnValidator columnValidator,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
|
||||
return new StandingInstructionReadPlatformServiceImpl(jdbcTemplate, clientReadPlatformService, officeReadPlatformService,
|
||||
portfolioAccountReadPlatformService, dropdownReadPlatformService, columnValidator, sqlGenerator, paginationHelper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StandingInstructionWritePlatformService.class)
|
||||
public StandingInstructionWritePlatformService standingInstructionWritePlatformService(
|
||||
StandingInstructionDataValidator standingInstructionDataValidator, StandingInstructionAssembler standingInstructionAssembler,
|
||||
AccountTransferDetailRepository accountTransferDetailRepository, StandingInstructionRepository standingInstructionRepository) {
|
||||
return new StandingInstructionWritePlatformServiceImpl(standingInstructionDataValidator, standingInstructionAssembler,
|
||||
accountTransferDetailRepository, standingInstructionRepository);
|
||||
}
|
||||
}
|
||||
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
package org.apache.fineract.portfolio.cashexchange.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchange;
|
||||
import org.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchangeDetail;
|
||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchangeDetailRepository;
|
||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchangeRepository;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Path("/v1/cashexchange")
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public class CashExchangeApiResource {
|
||||
|
||||
private final CashExchangeRepository repository;
|
||||
private final CashExchangeDetailRepository detailRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final DenominationBreakdownWriter denominationBreakdownWriter;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@POST
|
||||
@Transactional
|
||||
public String create(String body) {
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
|
||||
final Long accountId = json.has("accountId") && !json.get("accountId").isJsonNull() ? json.get("accountId").getAsLong() : null;
|
||||
final String accountNo = json.has("accountNo") && !json.get("accountNo").isJsonNull() ? json.get("accountNo").getAsString()
|
||||
: null;
|
||||
final Long clientId = json.has("clientId") && !json.get("clientId").isJsonNull() ? json.get("clientId").getAsLong() : null;
|
||||
final LocalDate exchangeDate = json.has("exchangeDate") && !json.get("exchangeDate").isJsonNull()
|
||||
? LocalDate.parse(json.get("exchangeDate").getAsString())
|
||||
: null;
|
||||
final String note = json.has("note") && !json.get("note").isJsonNull() ? json.get("note").getAsString() : null;
|
||||
final String customerIdProof = json.has("customerIdProof") && !json.get("customerIdProof").isJsonNull()
|
||||
? json.get("customerIdProof").getAsString() : null;
|
||||
|
||||
final JsonArray received = json.has("received") && json.get("received").isJsonArray() ? json.getAsJsonArray("received")
|
||||
: new JsonArray();
|
||||
final JsonArray given = json.has("given") && json.get("given").isJsonArray() ? json.getAsJsonArray("given") : new JsonArray();
|
||||
|
||||
final BigDecimal receivedTotal = sumTotal(received);
|
||||
final BigDecimal givenTotal = sumTotal(given);
|
||||
|
||||
if (!hasPositiveCount(received) || !hasPositiveCount(given)) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashexchange.empty",
|
||||
"Both received and given denominations are required.");
|
||||
}
|
||||
if (receivedTotal.compareTo(givenTotal) != 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashexchange.mismatch",
|
||||
"Received total (" + receivedTotal + ") must equal given total (" + givenTotal + ").");
|
||||
}
|
||||
if (receivedTotal.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashexchange.empty",
|
||||
"Both received and given denominations are required.");
|
||||
}
|
||||
|
||||
CashExchange header = new CashExchange();
|
||||
header.setAccountId(accountId);
|
||||
header.setAccountNo(accountNo);
|
||||
header.setClientId(clientId);
|
||||
header.setExchangeAmount(receivedTotal);
|
||||
header.setExchangeDate(exchangeDate);
|
||||
header.setNote(note);
|
||||
header.setCustomerIdProof(customerIdProof);
|
||||
header.setIsReversed(false);
|
||||
header.setCreatedAt(LocalDateTime.now());
|
||||
try {
|
||||
AppUser user = this.context.authenticatedUser();
|
||||
if (user != null) {
|
||||
header.setPerformedBy(user.getId());
|
||||
header.setPerformedByUsername(user.getUsername());
|
||||
// Auto-detect cashier linked to this user
|
||||
try {
|
||||
Long cashierId = jdbcTemplate.queryForObject(
|
||||
"SELECT mc.id FROM m_cashiers mc JOIN m_staff ms ON ms.id = mc.staff_id "
|
||||
+ "JOIN m_appuser au ON au.staff_id = ms.id WHERE au.id = ?",
|
||||
Long.class, user.getId());
|
||||
header.setCashierId(cashierId);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// resolving the current user must never block recording the exchange
|
||||
}
|
||||
|
||||
// Validate that cashier has enough of each denomination being GIVEN to the customer
|
||||
if (header.getCashierId() != null) {
|
||||
final java.util.Map<Long, Long> stock = denominationBreakdownWriter.getCashierNetPosition(header.getCashierId());
|
||||
final java.util.List<String> shortages = new java.util.ArrayList<>();
|
||||
for (JsonElement el : given) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count <= 0) continue;
|
||||
if (!item.has("denominationId") || item.get("denominationId").isJsonNull()) continue;
|
||||
long denomId = item.get("denominationId").getAsLong();
|
||||
long available = stock.getOrDefault(denomId, 0L);
|
||||
if (count > available) {
|
||||
int denomValue = item.has("denominationValue") && !item.get("denominationValue").isJsonNull()
|
||||
? item.get("denominationValue").getAsInt() : 0;
|
||||
shortages.add("₹" + denomValue + " (need " + count + ", available " + available + ")");
|
||||
}
|
||||
}
|
||||
if (!shortages.isEmpty()) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.cashier.denomination.insufficient",
|
||||
"Insufficient denomination stock for exchange — cannot give: " + String.join("; ", shortages));
|
||||
}
|
||||
}
|
||||
|
||||
CashExchange saved = repository.saveAndFlush(header);
|
||||
|
||||
saveDetails(saved.getId(), "RECEIVED", received);
|
||||
saveDetails(saved.getId(), "GIVEN", given);
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", saved.getId());
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{id}/reverse")
|
||||
public String reverse(@PathParam("id") Long id, String body) {
|
||||
CashExchange exchange = repository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Cash exchange not found: " + id));
|
||||
if (Boolean.TRUE.equals(exchange.getIsReversed())) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashexchange.already.reversed",
|
||||
"Cash exchange " + id + " is already reversed.");
|
||||
}
|
||||
String reason = null;
|
||||
try {
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
reason = json.has("reason") && !json.get("reason").isJsonNull() ? json.get("reason").getAsString() : null;
|
||||
} catch (Exception ignored) {}
|
||||
exchange.setIsReversed(true);
|
||||
exchange.setReversedAt(LocalDateTime.now());
|
||||
exchange.setReverseReason(reason);
|
||||
try {
|
||||
AppUser user = this.context.authenticatedUser();
|
||||
if (user != null) exchange.setReversedBy(user.getId());
|
||||
} catch (Exception ignored) {}
|
||||
repository.save(exchange);
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", id);
|
||||
result.addProperty("reversed", true);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
public String getAll(@QueryParam("accountId") Long accountId) {
|
||||
List<CashExchange> list = accountId != null ? repository.findByAccountIdOrderByCreatedAtDesc(accountId)
|
||||
: repository.findAllByOrderByCreatedAtDesc();
|
||||
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
public String getOne(@PathParam("id") Long id) {
|
||||
CashExchange header = repository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Cash exchange not found: " + id));
|
||||
Map<String, Object> map = toMap(header);
|
||||
List<Map<String, Object>> details = detailRepository.findByExchangeId(id).stream().map(this::detailToMap)
|
||||
.collect(Collectors.toList());
|
||||
map.put("details", details);
|
||||
return gson.toJson(map);
|
||||
}
|
||||
|
||||
private BigDecimal sumTotal(JsonArray items) {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int value = item.has("denominationValue") && !item.get("denominationValue").isJsonNull()
|
||||
? item.get("denominationValue").getAsInt()
|
||||
: 0;
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count > 0) {
|
||||
total = total.add(BigDecimal.valueOf((long) value * count));
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private boolean hasPositiveCount(JsonArray items) {
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void saveDetails(Long exchangeId, String direction, JsonArray items) {
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count <= 0) {
|
||||
continue;
|
||||
}
|
||||
int value = item.has("denominationValue") && !item.get("denominationValue").isJsonNull()
|
||||
? item.get("denominationValue").getAsInt()
|
||||
: 0;
|
||||
Long denominationId = item.has("denominationId") && !item.get("denominationId").isJsonNull()
|
||||
? item.get("denominationId").getAsLong()
|
||||
: null;
|
||||
CashExchangeDetail detail = new CashExchangeDetail();
|
||||
detail.setExchangeId(exchangeId);
|
||||
detail.setDirection(direction);
|
||||
detail.setDenominationId(denominationId);
|
||||
detail.setDenominationValue(value);
|
||||
detail.setCount(count);
|
||||
detail.setTotalAmount(BigDecimal.valueOf((long) value * count));
|
||||
detailRepository.save(detail);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap(CashExchange e) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("id", e.getId());
|
||||
map.put("cashierId", e.getCashierId());
|
||||
map.put("accountId", e.getAccountId());
|
||||
map.put("accountNo", e.getAccountNo());
|
||||
map.put("clientId", e.getClientId());
|
||||
map.put("customerIdProof", e.getCustomerIdProof());
|
||||
map.put("exchangeAmount", e.getExchangeAmount());
|
||||
map.put("exchangeDate", e.getExchangeDate() != null ? e.getExchangeDate().toString() : null);
|
||||
map.put("note", e.getNote());
|
||||
map.put("performedBy", e.getPerformedBy());
|
||||
map.put("performedByUsername", e.getPerformedByUsername());
|
||||
map.put("createdAt", e.getCreatedAt() != null ? e.getCreatedAt().toString() : null);
|
||||
map.put("isReversed", Boolean.TRUE.equals(e.getIsReversed()));
|
||||
map.put("reversedBy", e.getReversedBy());
|
||||
map.put("reversedAt", e.getReversedAt() != null ? e.getReversedAt().toString() : null);
|
||||
map.put("reverseReason", e.getReverseReason());
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, Object> detailToMap(CashExchangeDetail d) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("id", d.getId());
|
||||
map.put("direction", d.getDirection());
|
||||
map.put("denominationId", d.getDenominationId());
|
||||
map.put("denominationValue", d.getDenominationValue());
|
||||
map.put("count", d.getCount());
|
||||
map.put("totalAmount", d.getTotalAmount());
|
||||
return map;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.cashexchange.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_exchange")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashExchange {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "account_id")
|
||||
private Long accountId;
|
||||
|
||||
@Column(name = "account_no", length = 50)
|
||||
private String accountNo;
|
||||
|
||||
@Column(name = "client_id")
|
||||
private Long clientId;
|
||||
|
||||
@Column(name = "exchange_amount", nullable = false)
|
||||
private BigDecimal exchangeAmount;
|
||||
|
||||
@Column(name = "exchange_date")
|
||||
private LocalDate exchangeDate;
|
||||
|
||||
@Column(name = "note", length = 500)
|
||||
private String note;
|
||||
|
||||
@Column(name = "cashier_id")
|
||||
private Long cashierId;
|
||||
|
||||
@Column(name = "customer_id_proof", length = 50)
|
||||
private String customerIdProof;
|
||||
|
||||
@Column(name = "performed_by")
|
||||
private Long performedBy;
|
||||
|
||||
@Column(name = "performed_by_username", length = 100)
|
||||
private String performedByUsername;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "is_reversed")
|
||||
private Boolean isReversed;
|
||||
|
||||
@Column(name = "reversed_by")
|
||||
private Long reversedBy;
|
||||
|
||||
@Column(name = "reversed_at")
|
||||
private LocalDateTime reversedAt;
|
||||
|
||||
@Column(name = "reverse_reason", length = 500)
|
||||
private String reverseReason;
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.cashexchange.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_exchange_detail")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashExchangeDetail {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "exchange_id", nullable = false)
|
||||
private Long exchangeId;
|
||||
|
||||
@Column(name = "direction", nullable = false, length = 10)
|
||||
private String direction;
|
||||
|
||||
@Column(name = "denomination_id")
|
||||
private Long denominationId;
|
||||
|
||||
@Column(name = "denomination_value", nullable = false)
|
||||
private Integer denominationValue;
|
||||
|
||||
@Column(name = "count", nullable = false)
|
||||
private Integer count;
|
||||
|
||||
@Column(name = "total_amount", nullable = false)
|
||||
private BigDecimal totalAmount;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package org.apache.fineract.portfolio.cashexchange.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CashExchangeDetailRepository extends JpaRepository<CashExchangeDetail, Long> {
|
||||
List<CashExchangeDetail> findByExchangeId(Long exchangeId);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.apache.fineract.portfolio.cashexchange.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CashExchangeRepository extends JpaRepository<CashExchange, Long> {
|
||||
List<CashExchange> findAllByOrderByCreatedAtDesc();
|
||||
List<CashExchange> findByAccountIdOrderByCreatedAtDesc(Long accountId);
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
package org.apache.fineract.portfolio.cashintercashier.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.cashintercashier.domain.CashInterCashier;
|
||||
import org.apache.fineract.portfolio.cashintercashier.domain.CashInterCashierDetail;
|
||||
import org.apache.fineract.portfolio.cashintercashier.domain.CashInterCashierDetailRepository;
|
||||
import org.apache.fineract.portfolio.cashintercashier.domain.CashInterCashierRepository;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Path("/v1/cashintercashier")
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public class CashInterCashierApiResource {
|
||||
|
||||
private final CashInterCashierRepository repository;
|
||||
private final CashInterCashierDetailRepository detailRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@POST
|
||||
@Transactional
|
||||
public String create(String body) {
|
||||
context.authenticatedUser().validateHasPermissionTo("CREATE_CASHINTERCASHIER");
|
||||
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
|
||||
final Long tellerId = json.has("tellerId") && !json.get("tellerId").isJsonNull()
|
||||
? json.get("tellerId").getAsLong() : null;
|
||||
final Long fromCashierId = json.has("fromCashierId") && !json.get("fromCashierId").isJsonNull()
|
||||
? json.get("fromCashierId").getAsLong() : null;
|
||||
final Long toCashierId = json.has("toCashierId") && !json.get("toCashierId").isJsonNull()
|
||||
? json.get("toCashierId").getAsLong() : null;
|
||||
|
||||
if (tellerId == null || fromCashierId == null || toCashierId == null) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashintercashier.parties.required",
|
||||
"tellerId, fromCashierId and toCashierId are required.");
|
||||
}
|
||||
if (fromCashierId.equals(toCashierId)) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashintercashier.same.cashier",
|
||||
"From-cashier and To-cashier cannot be the same.");
|
||||
}
|
||||
|
||||
final LocalDate transferDate = json.has("transferDate") && !json.get("transferDate").isJsonNull()
|
||||
? LocalDate.parse(json.get("transferDate").getAsString()) : LocalDate.now();
|
||||
final String fromParty = json.has("fromParty") && !json.get("fromParty").isJsonNull()
|
||||
? json.get("fromParty").getAsString() : null;
|
||||
final String toParty = json.has("toParty") && !json.get("toParty").isJsonNull()
|
||||
? json.get("toParty").getAsString() : null;
|
||||
final String note = json.has("note") && !json.get("note").isJsonNull()
|
||||
? json.get("note").getAsString() : null;
|
||||
|
||||
final JsonArray denominations = json.has("denominations") && json.get("denominations").isJsonArray()
|
||||
? json.getAsJsonArray("denominations") : new JsonArray();
|
||||
|
||||
if (!hasPositiveCount(denominations)) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashintercashier.empty",
|
||||
"At least one denomination with a positive count is required.");
|
||||
}
|
||||
final BigDecimal total = sumTotal(denominations);
|
||||
|
||||
CashInterCashier header = new CashInterCashier();
|
||||
header.setTransferDate(transferDate);
|
||||
header.setTellerId(tellerId);
|
||||
header.setFromCashierId(fromCashierId);
|
||||
header.setToCashierId(toCashierId);
|
||||
header.setFromParty(fromParty);
|
||||
header.setToParty(toParty);
|
||||
header.setTransferAmount(total);
|
||||
header.setNote(note);
|
||||
header.setCreatedAt(LocalDateTime.now());
|
||||
header.setIsReversed(false);
|
||||
try {
|
||||
AppUser user = this.context.authenticatedUser();
|
||||
header.setPerformedBy(user.getId());
|
||||
header.setPerformedByUsername(user.getUsername());
|
||||
} catch (Exception ignored) {}
|
||||
CashInterCashier saved = repository.saveAndFlush(header);
|
||||
saveDetails(saved.getId(), denominations);
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", saved.getId());
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{id}/reverse")
|
||||
@Transactional
|
||||
public String reverse(@PathParam("id") Long id, String body) {
|
||||
context.authenticatedUser().validateHasPermissionTo("REVERSE_CASHINTERCASHIER");
|
||||
|
||||
CashInterCashier record = repository.findById(id)
|
||||
.orElseThrow(() -> new PlatformDataIntegrityException("error.msg.cashintercashier.not.found",
|
||||
"Inter-cashier transfer not found: " + id));
|
||||
if (Boolean.TRUE.equals(record.getIsReversed())) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashintercashier.already.reversed",
|
||||
"Inter-cashier transfer " + id + " is already reversed.");
|
||||
}
|
||||
|
||||
String reason = null;
|
||||
try {
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
reason = json.has("reason") ? json.get("reason").getAsString() : null;
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
AppUser user = null;
|
||||
try { user = this.context.authenticatedUser(); } catch (Exception ignored) {}
|
||||
|
||||
record.setIsReversed(true);
|
||||
record.setReversedAt(LocalDateTime.now());
|
||||
record.setReverseReason(reason);
|
||||
if (user != null) record.setReversedBy(user.getId());
|
||||
repository.save(record);
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", id);
|
||||
result.addProperty("reversed", true);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
public String getAll() {
|
||||
return gson.toJson(repository.findAllByOrderByCreatedAtDesc().stream()
|
||||
.map(this::toMap).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
public String getOne(@PathParam("id") Long id) {
|
||||
CashInterCashier header = repository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Inter-cashier transfer not found: " + id));
|
||||
Map<String, Object> map = toMap(header);
|
||||
map.put("details", detailRepository.findByInterCashierId(id).stream()
|
||||
.map(this::detailToMap).collect(Collectors.toList()));
|
||||
return gson.toJson(map);
|
||||
}
|
||||
|
||||
private BigDecimal sumTotal(JsonArray items) {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int value = item.has("denominationValue") ? item.get("denominationValue").getAsInt() : 0;
|
||||
int count = item.has("count") ? item.get("count").getAsInt() : 0;
|
||||
if (count > 0) total = total.add(BigDecimal.valueOf((long) value * count));
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private boolean hasPositiveCount(JsonArray items) {
|
||||
for (JsonElement el : items) {
|
||||
if (el.getAsJsonObject().get("count").getAsInt() > 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void saveDetails(Long interCashierId, JsonArray items) {
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int count = item.has("count") ? item.get("count").getAsInt() : 0;
|
||||
if (count <= 0) continue;
|
||||
int value = item.has("denominationValue") ? item.get("denominationValue").getAsInt() : 0;
|
||||
Long denomId = item.has("denominationId") && !item.get("denominationId").isJsonNull()
|
||||
? item.get("denominationId").getAsLong() : null;
|
||||
CashInterCashierDetail d = new CashInterCashierDetail();
|
||||
d.setInterCashierId(interCashierId);
|
||||
d.setDenominationId(denomId);
|
||||
d.setDenominationValue(value);
|
||||
d.setCount(count);
|
||||
d.setTotalAmount(BigDecimal.valueOf((long) value * count));
|
||||
detailRepository.save(d);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap(CashInterCashier e) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("id", e.getId());
|
||||
m.put("transferDate", e.getTransferDate() != null ? e.getTransferDate().toString() : null);
|
||||
m.put("tellerId", e.getTellerId());
|
||||
m.put("fromCashierId", e.getFromCashierId());
|
||||
m.put("toCashierId", e.getToCashierId());
|
||||
m.put("fromParty", e.getFromParty());
|
||||
m.put("toParty", e.getToParty());
|
||||
m.put("transferAmount", e.getTransferAmount());
|
||||
m.put("note", e.getNote());
|
||||
m.put("performedByUsername", e.getPerformedByUsername());
|
||||
m.put("createdAt", e.getCreatedAt() != null ? e.getCreatedAt().toString() : null);
|
||||
m.put("isReversed", e.getIsReversed());
|
||||
return m;
|
||||
}
|
||||
|
||||
private Map<String, Object> detailToMap(CashInterCashierDetail d) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("id", d.getId());
|
||||
m.put("denominationId", d.getDenominationId());
|
||||
m.put("denominationValue", d.getDenominationValue());
|
||||
m.put("count", d.getCount());
|
||||
m.put("totalAmount", d.getTotalAmount());
|
||||
return m;
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package org.apache.fineract.portfolio.cashintercashier.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_inter_cashier")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashInterCashier {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "transfer_date")
|
||||
private LocalDate transferDate;
|
||||
|
||||
@Column(name = "teller_id")
|
||||
private Long tellerId;
|
||||
|
||||
@Column(name = "from_cashier_id")
|
||||
private Long fromCashierId;
|
||||
|
||||
@Column(name = "to_cashier_id")
|
||||
private Long toCashierId;
|
||||
|
||||
@Column(name = "from_party", length = 100)
|
||||
private String fromParty;
|
||||
|
||||
@Column(name = "to_party", length = 100)
|
||||
private String toParty;
|
||||
|
||||
@Column(name = "transfer_amount", nullable = false)
|
||||
private BigDecimal transferAmount;
|
||||
|
||||
@Column(name = "note", length = 500)
|
||||
private String note;
|
||||
|
||||
@Column(name = "performed_by")
|
||||
private Long performedBy;
|
||||
|
||||
@Column(name = "performed_by_username", length = 100)
|
||||
private String performedByUsername;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "is_reversed")
|
||||
private Boolean isReversed = false;
|
||||
|
||||
@Column(name = "reversed_by")
|
||||
private Long reversedBy;
|
||||
|
||||
@Column(name = "reversed_at")
|
||||
private LocalDateTime reversedAt;
|
||||
|
||||
@Column(name = "reverse_reason", length = 500)
|
||||
private String reverseReason;
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package org.apache.fineract.portfolio.cashintercashier.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_inter_cashier_detail")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashInterCashierDetail {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "inter_cashier_id", nullable = false)
|
||||
private Long interCashierId;
|
||||
|
||||
@Column(name = "denomination_id")
|
||||
private Long denominationId;
|
||||
|
||||
@Column(name = "denomination_value", nullable = false)
|
||||
private Integer denominationValue;
|
||||
|
||||
@Column(name = "count", nullable = false)
|
||||
private Integer count;
|
||||
|
||||
@Column(name = "total_amount", nullable = false)
|
||||
private BigDecimal totalAmount;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package org.apache.fineract.portfolio.cashintercashier.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CashInterCashierDetailRepository extends JpaRepository<CashInterCashierDetail, Long> {
|
||||
List<CashInterCashierDetail> findByInterCashierId(Long interCashierId);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.apache.fineract.portfolio.cashintercashier.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CashInterCashierRepository extends JpaRepository<CashInterCashier, Long> {
|
||||
List<CashInterCashier> findAllByOrderByCreatedAtDesc();
|
||||
List<CashInterCashier> findByTellerIdOrderByCreatedAtDesc(Long tellerId);
|
||||
}
|
||||
+387
@@ -0,0 +1,387 @@
|
||||
package org.apache.fineract.portfolio.cashreconciliation.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.cashreconciliation.domain.CashReconciliation;
|
||||
import org.apache.fineract.portfolio.cashreconciliation.domain.CashReconciliationDetail;
|
||||
import org.apache.fineract.portfolio.cashreconciliation.domain.CashReconciliationDetailRepository;
|
||||
import org.apache.fineract.portfolio.cashreconciliation.domain.CashReconciliationRepository;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Path("/v1/cashreconciliation")
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public class CashReconciliationApiResource {
|
||||
|
||||
private final CashReconciliationRepository repository;
|
||||
private final CashReconciliationDetailRepository detailRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* GET /cashreconciliation/expected?cashierId=X&asOfDate=yyyy-MM-dd
|
||||
* Returns denomination-wise expected balance for a cashier.
|
||||
* Computation: allocations (m_cash_transfer_detail where to_cashier_id=X)
|
||||
* + inter-cashier-in (m_cash_inter_cashier_detail where to_cashier_id=X)
|
||||
* - settlements (m_cash_settlement_detail where from_cashier_id=X)
|
||||
* - inter-cashier-out (m_cash_inter_cashier_detail where from_cashier_id=X)
|
||||
*/
|
||||
@GET
|
||||
@Path("expected")
|
||||
public String getExpected(@QueryParam("cashierId") Long cashierId, @QueryParam("asOfDate") String asOfDate) {
|
||||
context.authenticatedUser().validateHasPermissionTo("READ_CASHRECONCILIATION");
|
||||
if (cashierId == null) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashreconciliation.cashier.required",
|
||||
"cashierId query param is required.");
|
||||
}
|
||||
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now().toString();
|
||||
|
||||
// In: vault → cashier allocations — GROUP BY denomination_id+kind to keep coin and note separate
|
||||
final String sqlIn1 = "SELECT ctd.denomination_id, ctd.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(ctd.count) AS total_count "
|
||||
+ "FROM m_cash_transfer_detail ctd "
|
||||
+ "JOIN m_cash_transfer ct ON ct.id = ctd.transfer_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = ctd.denomination_id "
|
||||
+ "WHERE ct.to_cashier_id = ? AND (ct.is_reversed IS NULL OR ct.is_reversed = false) "
|
||||
+ "AND ct.transfer_date <= CAST(? AS DATE) "
|
||||
+ "GROUP BY ctd.denomination_id, ctd.denomination_value, dm.kind";
|
||||
|
||||
// In: inter-cashier in
|
||||
final String sqlIn2 = "SELECT icd.denomination_id, icd.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(icd.count) AS total_count "
|
||||
+ "FROM m_cash_inter_cashier_detail icd "
|
||||
+ "JOIN m_cash_inter_cashier ic ON ic.id = icd.inter_cashier_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = icd.denomination_id "
|
||||
+ "WHERE ic.to_cashier_id = ? AND (ic.is_reversed IS NULL OR ic.is_reversed = false) "
|
||||
+ "AND ic.transfer_date <= CAST(? AS DATE) "
|
||||
+ "GROUP BY icd.denomination_id, icd.denomination_value, dm.kind";
|
||||
|
||||
// In: customer deposits received by this cashier
|
||||
final String sqlIn3 = "SELECT std.denomination_id, std.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(std.count) AS total_count "
|
||||
+ "FROM m_savings_txn_denomination std "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = std.denomination_id "
|
||||
+ "WHERE std.txn_type = 'DEPOSIT' "
|
||||
+ "AND std.txn_date <= CAST(? AS DATE) "
|
||||
+ "AND std.officer_id IN ("
|
||||
+ " SELECT au.id FROM m_appuser au "
|
||||
+ " JOIN m_cashiers mc ON mc.staff_id = au.staff_id WHERE mc.id = ?) "
|
||||
+ "GROUP BY std.denomination_id, std.denomination_value, dm.kind";
|
||||
|
||||
// Out: settlements to vault
|
||||
final String sqlOut1 = "SELECT csd.denomination_id, csd.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(csd.count) AS total_count "
|
||||
+ "FROM m_cash_settlement_detail csd "
|
||||
+ "JOIN m_cash_settlement cs ON cs.id = csd.settlement_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = csd.denomination_id "
|
||||
+ "WHERE cs.from_cashier_id = ? AND (cs.is_reversed IS NULL OR cs.is_reversed = false) "
|
||||
+ "AND cs.settlement_date <= CAST(? AS DATE) "
|
||||
+ "GROUP BY csd.denomination_id, csd.denomination_value, dm.kind";
|
||||
|
||||
// Out: inter-cashier out
|
||||
final String sqlOut2 = "SELECT icd.denomination_id, icd.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(icd.count) AS total_count "
|
||||
+ "FROM m_cash_inter_cashier_detail icd "
|
||||
+ "JOIN m_cash_inter_cashier ic ON ic.id = icd.inter_cashier_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = icd.denomination_id "
|
||||
+ "WHERE ic.from_cashier_id = ? AND (ic.is_reversed IS NULL OR ic.is_reversed = false) "
|
||||
+ "AND ic.transfer_date <= CAST(? AS DATE) "
|
||||
+ "GROUP BY icd.denomination_id, icd.denomination_value, dm.kind";
|
||||
|
||||
// Out: customer withdrawals paid by this cashier
|
||||
final String sqlOut3 = "SELECT std.denomination_id, std.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(std.count) AS total_count "
|
||||
+ "FROM m_savings_txn_denomination std "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = std.denomination_id "
|
||||
+ "WHERE std.txn_type = 'WITHDRAWAL' "
|
||||
+ "AND std.txn_date <= CAST(? AS DATE) "
|
||||
+ "AND std.officer_id IN ("
|
||||
+ " SELECT au.id FROM m_appuser au "
|
||||
+ " JOIN m_cashiers mc ON mc.staff_id = au.staff_id WHERE mc.id = ?) "
|
||||
+ "GROUP BY std.denomination_id, std.denomination_value, dm.kind";
|
||||
|
||||
// In: cash exchange — customer gave denomination TO cashier (RECEIVED direction)
|
||||
final String sqlIn4 = "SELECT ced.denomination_id, ced.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(ced.count) AS total_count "
|
||||
+ "FROM m_cash_exchange_detail ced "
|
||||
+ "JOIN m_cash_exchange ce ON ce.id = ced.exchange_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = ced.denomination_id "
|
||||
+ "WHERE ce.cashier_id = ? AND ced.direction = 'RECEIVED' "
|
||||
+ "AND (ce.is_reversed IS NULL OR ce.is_reversed = false) "
|
||||
+ "AND ce.exchange_date <= CAST(? AS DATE) "
|
||||
+ "GROUP BY ced.denomination_id, ced.denomination_value, dm.kind";
|
||||
|
||||
// Out: cash exchange — cashier gave denomination TO customer (GIVEN direction)
|
||||
final String sqlOut4 = "SELECT ced.denomination_id, ced.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(ced.count) AS total_count "
|
||||
+ "FROM m_cash_exchange_detail ced "
|
||||
+ "JOIN m_cash_exchange ce ON ce.id = ced.exchange_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = ced.denomination_id "
|
||||
+ "WHERE ce.cashier_id = ? AND ced.direction = 'GIVEN' "
|
||||
+ "AND (ce.is_reversed IS NULL OR ce.is_reversed = false) "
|
||||
+ "AND ce.exchange_date <= CAST(? AS DATE) "
|
||||
+ "GROUP BY ced.denomination_id, ced.denomination_value, dm.kind";
|
||||
|
||||
// Balance map: denomination_id → long[3] {denomValue, kindFlag (0=note/1=coin), netCount}
|
||||
Map<Long, long[]> balance = new LinkedHashMap<>();
|
||||
|
||||
addToBalance(balance, jdbcTemplate.query(sqlIn1, DENOM_ROW, cashierId, dateFilter), 1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlIn2, DENOM_ROW, cashierId, dateFilter), 1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlIn3, DENOM_ROW, dateFilter, cashierId), 1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlIn4, DENOM_ROW, cashierId, dateFilter), 1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlOut1, DENOM_ROW, cashierId, dateFilter), -1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlOut2, DENOM_ROW, cashierId, dateFilter), -1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlOut3, DENOM_ROW, dateFilter, cashierId), -1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlOut4, DENOM_ROW, cashierId, dateFilter), -1);
|
||||
|
||||
List<Map<String, Object>> rows = new ArrayList<>();
|
||||
balance.entrySet().stream()
|
||||
.sorted((a, b) -> Long.compare(b.getValue()[0], a.getValue()[0]))
|
||||
.forEach(e -> {
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("denominationId", e.getKey());
|
||||
row.put("denominationValue", (int) e.getValue()[0]);
|
||||
row.put("kind", e.getValue()[1] == 1L ? "coin" : "note");
|
||||
row.put("expectedCount", e.getValue()[2]);
|
||||
row.put("expectedAmount", BigDecimal.valueOf(e.getValue()[0] * e.getValue()[2]));
|
||||
rows.add(row);
|
||||
});
|
||||
|
||||
BigDecimal grandTotal = rows.stream()
|
||||
.map(r -> (BigDecimal) r.get("expectedAmount"))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("cashierId", cashierId);
|
||||
result.addProperty("asOfDate", dateFilter);
|
||||
result.addProperty("expectedTotal", grandTotal);
|
||||
result.add("denominations", gson.toJsonTree(rows));
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /cashreconciliation/expected/me
|
||||
* Same as /expected but auto-detects cashierId from the authenticated user.
|
||||
*/
|
||||
@GET
|
||||
@Path("expected/me")
|
||||
public String getExpectedForMe(@QueryParam("asOfDate") String asOfDate) {
|
||||
context.authenticatedUser().validateHasPermissionTo("READ_CASHRECONCILIATION");
|
||||
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now().toString();
|
||||
Long appUserId;
|
||||
try {
|
||||
appUserId = context.authenticatedUser().getId();
|
||||
} catch (Exception e) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashreconciliation.not.authenticated", "Not authenticated.");
|
||||
}
|
||||
Long cashierId;
|
||||
try {
|
||||
cashierId = jdbcTemplate.queryForObject(
|
||||
"SELECT mc.id FROM m_cashiers mc JOIN m_staff ms ON ms.id = mc.staff_id JOIN m_appuser au ON au.staff_id = ms.id WHERE au.id = ?",
|
||||
Long.class, appUserId);
|
||||
} catch (Exception e) {
|
||||
Map<String, Object> empty = new LinkedHashMap<>();
|
||||
empty.put("cashierId", null);
|
||||
empty.put("asOfDate", dateFilter);
|
||||
empty.put("expectedTotal", java.math.BigDecimal.ZERO);
|
||||
empty.put("denominations", new ArrayList<>());
|
||||
return gson.toJson(empty);
|
||||
}
|
||||
// Delegate to the existing /expected logic
|
||||
return getExpected(cashierId, dateFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /cashreconciliation/check?cashierId=X&date=yyyy-MM-dd
|
||||
* Returns whether reconciliation has been done today for a cashier.
|
||||
*/
|
||||
@GET
|
||||
@Path("check")
|
||||
public String check(@QueryParam("cashierId") Long cashierId, @QueryParam("date") String date) {
|
||||
context.authenticatedUser().validateHasPermissionTo("READ_CASHRECONCILIATION");
|
||||
if (cashierId == null) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashreconciliation.cashier.required",
|
||||
"cashierId query param is required.");
|
||||
}
|
||||
final String reconDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
|
||||
Integer count = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_cash_reconciliation WHERE cashier_id = ? AND recon_date = CAST(? AS DATE)",
|
||||
Integer.class, cashierId, reconDate);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("cashierId", cashierId);
|
||||
result.put("date", reconDate);
|
||||
result.put("reconciled", count != null && count > 0);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Transactional
|
||||
public String create(String body) {
|
||||
context.authenticatedUser().validateHasPermissionTo("CREATE_CASHRECONCILIATION");
|
||||
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
|
||||
final Long cashierId = json.has("cashierId") ? json.get("cashierId").getAsLong() : null;
|
||||
if (cashierId == null) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashreconciliation.cashier.required",
|
||||
"cashierId is required.");
|
||||
}
|
||||
final LocalDate reconDate = json.has("reconDate") && !json.get("reconDate").isJsonNull()
|
||||
? LocalDate.parse(json.get("reconDate").getAsString()) : LocalDate.now();
|
||||
final String cashierName = json.has("cashierName") && !json.get("cashierName").isJsonNull()
|
||||
? json.get("cashierName").getAsString() : null;
|
||||
final Long tellerId = json.has("tellerId") && !json.get("tellerId").isJsonNull()
|
||||
? json.get("tellerId").getAsLong() : null;
|
||||
final String note = json.has("note") && !json.get("note").isJsonNull()
|
||||
? json.get("note").getAsString() : null;
|
||||
|
||||
final JsonArray denominations = json.has("denominations") && json.get("denominations").isJsonArray()
|
||||
? json.getAsJsonArray("denominations") : new JsonArray();
|
||||
|
||||
BigDecimal expectedTotal = BigDecimal.ZERO;
|
||||
BigDecimal physicalTotal = BigDecimal.ZERO;
|
||||
List<CashReconciliationDetail> details = new ArrayList<>();
|
||||
|
||||
for (JsonElement el : denominations) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int denomValue = item.has("denominationValue") ? item.get("denominationValue").getAsInt() : 0;
|
||||
int expectedCount = item.has("expectedCount") ? item.get("expectedCount").getAsInt() : 0;
|
||||
int physicalCount = item.has("physicalCount") ? item.get("physicalCount").getAsInt() : 0;
|
||||
int varianceCount = physicalCount - expectedCount;
|
||||
BigDecimal varianceAmount = BigDecimal.valueOf((long) denomValue * varianceCount);
|
||||
expectedTotal = expectedTotal.add(BigDecimal.valueOf((long) denomValue * expectedCount));
|
||||
physicalTotal = physicalTotal.add(BigDecimal.valueOf((long) denomValue * physicalCount));
|
||||
|
||||
Long denomId = item.has("denominationId") && !item.get("denominationId").isJsonNull()
|
||||
? item.get("denominationId").getAsLong() : null;
|
||||
|
||||
CashReconciliationDetail d = new CashReconciliationDetail();
|
||||
d.setDenominationId(denomId);
|
||||
d.setDenominationValue(denomValue);
|
||||
d.setExpectedCount(expectedCount);
|
||||
d.setPhysicalCount(physicalCount);
|
||||
d.setVarianceCount(varianceCount);
|
||||
d.setVarianceAmount(varianceAmount);
|
||||
details.add(d);
|
||||
}
|
||||
|
||||
BigDecimal variance = physicalTotal.subtract(expectedTotal);
|
||||
String status = variance.compareTo(BigDecimal.ZERO) == 0 ? "OK"
|
||||
: variance.compareTo(BigDecimal.ZERO) > 0 ? "EXCESS" : "SHORT";
|
||||
|
||||
CashReconciliation header = new CashReconciliation();
|
||||
header.setReconDate(reconDate);
|
||||
header.setCashierId(cashierId);
|
||||
header.setCashierName(cashierName);
|
||||
header.setTellerId(tellerId);
|
||||
header.setExpectedAmount(expectedTotal);
|
||||
header.setPhysicalAmount(physicalTotal);
|
||||
header.setVariance(variance);
|
||||
header.setStatus(status);
|
||||
header.setNote(note);
|
||||
header.setCreatedAt(LocalDateTime.now());
|
||||
try {
|
||||
AppUser user = this.context.authenticatedUser();
|
||||
header.setPerformedBy(user.getId());
|
||||
header.setPerformedByUsername(user.getUsername());
|
||||
} catch (Exception ignored) {}
|
||||
CashReconciliation saved = repository.saveAndFlush(header);
|
||||
|
||||
for (CashReconciliationDetail d : details) {
|
||||
d.setReconciliationId(saved.getId());
|
||||
detailRepository.save(d);
|
||||
}
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", saved.getId());
|
||||
result.addProperty("status", status);
|
||||
result.addProperty("variance", variance);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
public String getAll() {
|
||||
return gson.toJson(repository.findAllByOrderByCreatedAtDesc().stream()
|
||||
.map(this::toMap).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
public String getOne(@PathParam("id") Long id) {
|
||||
CashReconciliation header = repository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Cash reconciliation not found: " + id));
|
||||
Map<String, Object> map = toMap(header);
|
||||
map.put("details", detailRepository.findByReconciliationId(id).stream()
|
||||
.map(this::detailToMap).collect(Collectors.toList()));
|
||||
return gson.toJson(map);
|
||||
}
|
||||
|
||||
// Row mapper: Object[]{Long denomId, Integer denomValue, String kind, Long count}
|
||||
private static final org.springframework.jdbc.core.RowMapper<Object[]> DENOM_ROW =
|
||||
(rs, r) -> new Object[] {
|
||||
rs.getLong("denomination_id"),
|
||||
rs.getInt("denomination_value"),
|
||||
rs.getString("kind"),
|
||||
rs.getLong("total_count")
|
||||
};
|
||||
|
||||
private void addToBalance(Map<Long, long[]> balance, List<Object[]> rows, int sign) {
|
||||
for (Object[] row : rows) {
|
||||
Long denomId = (Long) row[0];
|
||||
int denomValue = (Integer) row[1];
|
||||
long kindFlag = "coin".equalsIgnoreCase((String) row[2]) ? 1L : 0L;
|
||||
long count = (Long) row[3];
|
||||
long[] entry = balance.computeIfAbsent(denomId, k -> new long[] { denomValue, kindFlag, 0L });
|
||||
entry[2] += count * sign;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap(CashReconciliation e) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("id", e.getId());
|
||||
m.put("reconDate", e.getReconDate() != null ? e.getReconDate().toString() : null);
|
||||
m.put("cashierId", e.getCashierId());
|
||||
m.put("cashierName", e.getCashierName());
|
||||
m.put("tellerId", e.getTellerId());
|
||||
m.put("expectedAmount", e.getExpectedAmount());
|
||||
m.put("physicalAmount", e.getPhysicalAmount());
|
||||
m.put("variance", e.getVariance());
|
||||
m.put("status", e.getStatus());
|
||||
m.put("note", e.getNote());
|
||||
m.put("performedByUsername", e.getPerformedByUsername());
|
||||
m.put("createdAt", e.getCreatedAt() != null ? e.getCreatedAt().toString() : null);
|
||||
return m;
|
||||
}
|
||||
|
||||
private Map<String, Object> detailToMap(CashReconciliationDetail d) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("id", d.getId());
|
||||
m.put("denominationId", d.getDenominationId());
|
||||
m.put("denominationValue", d.getDenominationValue());
|
||||
m.put("expectedCount", d.getExpectedCount());
|
||||
m.put("physicalCount", d.getPhysicalCount());
|
||||
m.put("varianceCount", d.getVarianceCount());
|
||||
m.put("varianceAmount", d.getVarianceAmount());
|
||||
return m;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package org.apache.fineract.portfolio.cashreconciliation.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_reconciliation")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashReconciliation {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "recon_date", nullable = false)
|
||||
private LocalDate reconDate;
|
||||
|
||||
@Column(name = "cashier_id", nullable = false)
|
||||
private Long cashierId;
|
||||
|
||||
@Column(name = "cashier_name", length = 150)
|
||||
private String cashierName;
|
||||
|
||||
@Column(name = "teller_id")
|
||||
private Long tellerId;
|
||||
|
||||
@Column(name = "expected_amount")
|
||||
private BigDecimal expectedAmount = BigDecimal.ZERO;
|
||||
|
||||
@Column(name = "physical_amount")
|
||||
private BigDecimal physicalAmount = BigDecimal.ZERO;
|
||||
|
||||
@Column(name = "variance")
|
||||
private BigDecimal variance = BigDecimal.ZERO;
|
||||
|
||||
@Column(name = "status", length = 10)
|
||||
private String status = "OK";
|
||||
|
||||
@Column(name = "note", length = 500)
|
||||
private String note;
|
||||
|
||||
@Column(name = "performed_by")
|
||||
private Long performedBy;
|
||||
|
||||
@Column(name = "performed_by_username", length = 100)
|
||||
private String performedByUsername;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package org.apache.fineract.portfolio.cashreconciliation.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_reconciliation_detail")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashReconciliationDetail {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "reconciliation_id", nullable = false)
|
||||
private Long reconciliationId;
|
||||
|
||||
@Column(name = "denomination_id")
|
||||
private Long denominationId;
|
||||
|
||||
@Column(name = "denomination_value", nullable = false)
|
||||
private Integer denominationValue;
|
||||
|
||||
@Column(name = "expected_count")
|
||||
private Integer expectedCount = 0;
|
||||
|
||||
@Column(name = "physical_count")
|
||||
private Integer physicalCount = 0;
|
||||
|
||||
@Column(name = "variance_count")
|
||||
private Integer varianceCount = 0;
|
||||
|
||||
@Column(name = "variance_amount")
|
||||
private BigDecimal varianceAmount = BigDecimal.ZERO;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package org.apache.fineract.portfolio.cashreconciliation.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CashReconciliationDetailRepository extends JpaRepository<CashReconciliationDetail, Long> {
|
||||
List<CashReconciliationDetail> findByReconciliationId(Long reconciliationId);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.apache.fineract.portfolio.cashreconciliation.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CashReconciliationRepository extends JpaRepository<CashReconciliation, Long> {
|
||||
List<CashReconciliation> findAllByOrderByCreatedAtDesc();
|
||||
List<CashReconciliation> findByCashierIdOrderByReconDateDesc(Long cashierId);
|
||||
}
|
||||
+437
@@ -0,0 +1,437 @@
|
||||
package org.apache.fineract.portfolio.cashreport.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/cashreports")
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public class CashReportApiResource {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final PlatformSecurityContext context;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@GET
|
||||
@Path("closing")
|
||||
public String closing(@QueryParam("date") String date) {
|
||||
final String txnDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
|
||||
|
||||
// Single query — conditional aggregation for deposit, withdrawal, net per denomination
|
||||
final String sql = "SELECT std.denomination_id, std.denomination_value, COALESCE(dm.kind, 'note') AS kind, "
|
||||
+ "SUM(CASE WHEN std.txn_type = 'DEPOSIT' THEN std.count ELSE 0 END) AS deposit_count, "
|
||||
+ "SUM(CASE WHEN std.txn_type = 'DEPOSIT' THEN std.total_amount ELSE 0 END) AS deposit_amount, "
|
||||
+ "SUM(CASE WHEN std.txn_type = 'WITHDRAWAL' THEN std.count ELSE 0 END) AS withdrawal_count, "
|
||||
+ "SUM(CASE WHEN std.txn_type = 'WITHDRAWAL' THEN std.total_amount ELSE 0 END) AS withdrawal_amount "
|
||||
+ "FROM m_savings_txn_denomination std "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = std.denomination_id "
|
||||
+ "WHERE std.txn_date = CAST(? AS DATE) "
|
||||
+ "GROUP BY std.denomination_id, std.denomination_value, dm.kind "
|
||||
+ "ORDER BY std.denomination_value DESC, dm.kind";
|
||||
|
||||
final List<Map<String, Object>> rows = jdbcTemplate.query(sql, (rs, rowNum) -> {
|
||||
long depositCount = rs.getLong("deposit_count");
|
||||
long withdrawalCount = rs.getLong("withdrawal_count");
|
||||
BigDecimal depositAmount = rs.getBigDecimal("deposit_amount");
|
||||
BigDecimal withdrawalAmount = rs.getBigDecimal("withdrawal_amount");
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("denominationId", rs.getLong("denomination_id"));
|
||||
row.put("denominationValue", rs.getInt("denomination_value"));
|
||||
row.put("kind", rs.getString("kind"));
|
||||
row.put("depositCount", depositCount);
|
||||
row.put("depositAmount", depositAmount);
|
||||
row.put("withdrawalCount", withdrawalCount);
|
||||
row.put("withdrawalAmount", withdrawalAmount);
|
||||
row.put("netCount", depositCount - withdrawalCount);
|
||||
row.put("netAmount", depositAmount.subtract(withdrawalAmount));
|
||||
return row;
|
||||
}, txnDate);
|
||||
|
||||
BigDecimal depositTotal = BigDecimal.ZERO;
|
||||
BigDecimal withdrawalTotal = BigDecimal.ZERO;
|
||||
for (Map<String, Object> row : rows) {
|
||||
depositTotal = depositTotal.add((BigDecimal) row.get("depositAmount"));
|
||||
withdrawalTotal = withdrawalTotal.add((BigDecimal) row.get("withdrawalAmount"));
|
||||
}
|
||||
|
||||
// Cash exchange rows — separate section (denomination swap, net monetary = 0)
|
||||
final String exchangeSql = "SELECT ced.denomination_id, ced.denomination_value, COALESCE(dm.kind, 'note') AS kind, "
|
||||
+ "SUM(CASE WHEN ced.direction = 'RECEIVED' THEN ced.count ELSE 0 END) AS received_count, "
|
||||
+ "SUM(CASE WHEN ced.direction = 'RECEIVED' THEN ced.total_amount ELSE 0 END) AS received_amount, "
|
||||
+ "SUM(CASE WHEN ced.direction = 'GIVEN' THEN ced.count ELSE 0 END) AS given_count, "
|
||||
+ "SUM(CASE WHEN ced.direction = 'GIVEN' THEN ced.total_amount ELSE 0 END) AS given_amount "
|
||||
+ "FROM m_cash_exchange_detail ced "
|
||||
+ "JOIN m_cash_exchange ce ON ce.id = ced.exchange_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = ced.denomination_id "
|
||||
+ "WHERE ce.exchange_date = CAST(? AS DATE) "
|
||||
+ "AND (ce.is_reversed IS NULL OR ce.is_reversed = false) "
|
||||
+ "GROUP BY ced.denomination_id, ced.denomination_value, dm.kind "
|
||||
+ "ORDER BY ced.denomination_value DESC, dm.kind";
|
||||
|
||||
final List<Map<String, Object>> exchangeRows = jdbcTemplate.query(exchangeSql, (rs, rowNum) -> {
|
||||
long receivedCount = rs.getLong("received_count");
|
||||
long givenCount = rs.getLong("given_count");
|
||||
BigDecimal receivedAmount = rs.getBigDecimal("received_amount");
|
||||
BigDecimal givenAmount = rs.getBigDecimal("given_amount");
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("denominationId", rs.getLong("denomination_id"));
|
||||
row.put("denominationValue", rs.getInt("denomination_value"));
|
||||
row.put("kind", rs.getString("kind"));
|
||||
row.put("receivedCount", receivedCount);
|
||||
row.put("receivedAmount", receivedAmount);
|
||||
row.put("givenCount", givenCount);
|
||||
row.put("givenAmount", givenAmount);
|
||||
row.put("netCount", receivedCount - givenCount);
|
||||
row.put("netAmount", receivedAmount.subtract(givenAmount));
|
||||
return row;
|
||||
}, txnDate);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("date", txnDate);
|
||||
result.put("rows", rows);
|
||||
result.put("depositTotal", depositTotal);
|
||||
result.put("withdrawalTotal", withdrawalTotal);
|
||||
result.put("netTotal", depositTotal.subtract(withdrawalTotal));
|
||||
result.put("exchangeRows", exchangeRows);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("excess")
|
||||
public String excess(@QueryParam("threshold") BigDecimal threshold, @QueryParam("fromDate") String fromDate,
|
||||
@QueryParam("toDate") String toDate) {
|
||||
final BigDecimal effectiveThreshold = threshold != null ? threshold : BigDecimal.valueOf(50000);
|
||||
final boolean hasDateRange = fromDate != null && !fromDate.isBlank() && toDate != null && !toDate.isBlank();
|
||||
|
||||
// Savings / Current / FD / RD transactions
|
||||
final StringBuilder sql = new StringBuilder();
|
||||
sql.append("SELECT txn_id, MAX(account_no) account_no, MAX(client_id) client_id, MAX(cif_no) cif_no, ");
|
||||
sql.append("MAX(txn_date) txn_date, SUM(total_amount) total_amount FROM m_savings_txn_denomination ");
|
||||
final List<Object> args = new ArrayList<>();
|
||||
if (hasDateRange) {
|
||||
sql.append("WHERE txn_date BETWEEN CAST(? AS DATE) AND CAST(? AS DATE) ");
|
||||
args.add(fromDate);
|
||||
args.add(toDate);
|
||||
}
|
||||
sql.append("GROUP BY txn_id HAVING SUM(total_amount) >= ? ORDER BY total_amount DESC");
|
||||
args.add(effectiveThreshold);
|
||||
|
||||
final List<Map<String, Object>> savingsRows = jdbcTemplate.query(sql.toString(), (rs, rowNum) -> {
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("type", "SAVINGS_TXN");
|
||||
row.put("txnId", rs.getLong("txn_id"));
|
||||
row.put("accountNo", rs.getString("account_no"));
|
||||
row.put("clientId", rs.getObject("client_id"));
|
||||
row.put("cifNo", rs.getString("cif_no"));
|
||||
final java.sql.Date d = rs.getDate("txn_date");
|
||||
row.put("txnDate", d != null ? d.toString() : null);
|
||||
row.put("totalAmount", rs.getBigDecimal("total_amount"));
|
||||
row.put("idProof", null);
|
||||
return row;
|
||||
}, args.toArray());
|
||||
|
||||
// Cash exchange transactions
|
||||
final StringBuilder exSql = new StringBuilder();
|
||||
exSql.append("SELECT id, account_no, client_id, exchange_date, exchange_amount, customer_id_proof, performed_by_username ");
|
||||
exSql.append("FROM m_cash_exchange ");
|
||||
exSql.append("WHERE (is_reversed IS NULL OR is_reversed = false) AND exchange_amount >= ? ");
|
||||
final List<Object> exArgs = new ArrayList<>();
|
||||
exArgs.add(effectiveThreshold);
|
||||
if (hasDateRange) {
|
||||
exSql.append("AND exchange_date BETWEEN CAST(? AS DATE) AND CAST(? AS DATE) ");
|
||||
exArgs.add(fromDate);
|
||||
exArgs.add(toDate);
|
||||
}
|
||||
exSql.append("ORDER BY exchange_amount DESC");
|
||||
|
||||
final List<Map<String, Object>> exchangeRows = jdbcTemplate.query(exSql.toString(), (rs, rowNum) -> {
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("type", "EXCHANGE");
|
||||
row.put("txnId", rs.getLong("id"));
|
||||
row.put("accountNo", rs.getString("account_no"));
|
||||
row.put("clientId", rs.getObject("client_id"));
|
||||
row.put("cifNo", null);
|
||||
final java.sql.Date d = rs.getDate("exchange_date");
|
||||
row.put("txnDate", d != null ? d.toString() : null);
|
||||
row.put("totalAmount", rs.getBigDecimal("exchange_amount"));
|
||||
row.put("idProof", rs.getString("customer_id_proof"));
|
||||
return row;
|
||||
}, exArgs.toArray());
|
||||
|
||||
// Merge and sort by amount DESC
|
||||
final List<Map<String, Object>> combined = new ArrayList<>();
|
||||
combined.addAll(savingsRows);
|
||||
combined.addAll(exchangeRows);
|
||||
combined.sort((a, b) -> ((BigDecimal) b.get("totalAmount")).compareTo((BigDecimal) a.get("totalAmount")));
|
||||
|
||||
return gson.toJson(combined);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /cashreports/cashier-position?cashierId=X&asOfDate=yyyy-MM-dd
|
||||
* Returns denomination-wise net position per cashier grouped by denomination_id+kind.
|
||||
*/
|
||||
@GET
|
||||
@Path("cashier-position")
|
||||
public String cashierPosition(@QueryParam("cashierId") Long cashierId,
|
||||
@QueryParam("asOfDate") String asOfDate) {
|
||||
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now().toString();
|
||||
|
||||
if (cashierId != null) {
|
||||
return gson.toJson(buildPositionForCashier(cashierId, dateFilter));
|
||||
}
|
||||
|
||||
final String idSql = "SELECT DISTINCT cashier_id FROM ("
|
||||
+ " SELECT to_cashier_id AS cashier_id FROM m_cash_transfer WHERE to_cashier_id IS NOT NULL "
|
||||
+ " UNION SELECT from_cashier_id FROM m_cash_settlement WHERE from_cashier_id IS NOT NULL "
|
||||
+ " UNION SELECT from_cashier_id FROM m_cash_inter_cashier WHERE from_cashier_id IS NOT NULL "
|
||||
+ " UNION SELECT to_cashier_id FROM m_cash_inter_cashier WHERE to_cashier_id IS NOT NULL "
|
||||
+ " UNION SELECT cashier_id FROM m_cash_exchange WHERE cashier_id IS NOT NULL "
|
||||
+ ") ids";
|
||||
|
||||
List<Long> cashierIds = jdbcTemplate.queryForList(idSql, Long.class);
|
||||
List<Map<String, Object>> allPositions = new ArrayList<>();
|
||||
for (Long cid : cashierIds) {
|
||||
allPositions.add(buildPositionForCashier(cid, dateFilter));
|
||||
}
|
||||
return gson.toJson(allPositions);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("cashier-position/me")
|
||||
public String cashierPositionMe(@QueryParam("asOfDate") String asOfDate) {
|
||||
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now().toString();
|
||||
Long appUserId;
|
||||
try {
|
||||
appUserId = context.authenticatedUser().getId();
|
||||
} catch (Exception e) {
|
||||
Map<String, Object> err = new LinkedHashMap<>();
|
||||
err.put("error", "Not authenticated");
|
||||
return gson.toJson(err);
|
||||
}
|
||||
|
||||
Long cashierId;
|
||||
try {
|
||||
cashierId = jdbcTemplate.queryForObject(
|
||||
"SELECT mc.id FROM m_cashiers mc JOIN m_staff ms ON ms.id = mc.staff_id JOIN m_appuser au ON au.staff_id = ms.id WHERE au.id = ?",
|
||||
Long.class, appUserId);
|
||||
} catch (Exception e) {
|
||||
Map<String, Object> empty = new LinkedHashMap<>();
|
||||
empty.put("cashierId", null);
|
||||
empty.put("asOfDate", dateFilter);
|
||||
empty.put("totalAmount", BigDecimal.ZERO);
|
||||
empty.put("denominations", new ArrayList<>());
|
||||
return gson.toJson(empty);
|
||||
}
|
||||
|
||||
return gson.toJson(buildPositionForCashier(cashierId, dateFilter));
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /cashreports/cashier-morning?cashierId=X&date=YYYY-MM-DD
|
||||
* Returns denomination-wise cash received by the cashier from vault on the given date
|
||||
* (today's morning opening allocation from m_cash_transfer only, non-reversed).
|
||||
*/
|
||||
@GET
|
||||
@Path("cashier-morning")
|
||||
public String cashierMorning(@QueryParam("cashierId") Long cashierId,
|
||||
@QueryParam("date") String date) {
|
||||
final String txnDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
|
||||
if (cashierId == null) {
|
||||
Map<String, Object> empty = new LinkedHashMap<>();
|
||||
empty.put("cashierId", null);
|
||||
empty.put("date", txnDate);
|
||||
empty.put("totalMorning", BigDecimal.ZERO);
|
||||
empty.put("denominations", new ArrayList<>());
|
||||
return gson.toJson(empty);
|
||||
}
|
||||
|
||||
final String sql = "SELECT ctd.denomination_id, ctd.denomination_value, "
|
||||
+ "COALESCE(dm.kind, 'note') AS kind, "
|
||||
+ "SUM(ctd.count) AS total_count, "
|
||||
+ "SUM(ctd.total_amount) AS total_amount "
|
||||
+ "FROM m_cash_transfer_detail ctd "
|
||||
+ "JOIN m_cash_transfer ct ON ct.id = ctd.transfer_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = ctd.denomination_id "
|
||||
+ "WHERE ct.to_cashier_id = ? "
|
||||
+ "AND ct.transfer_date = CAST(? AS DATE) "
|
||||
+ "AND (ct.is_reversed IS NULL OR ct.is_reversed = false) "
|
||||
+ "GROUP BY ctd.denomination_id, ctd.denomination_value, dm.kind "
|
||||
+ "ORDER BY ctd.denomination_value DESC";
|
||||
|
||||
List<Map<String, Object>> rows = jdbcTemplate.query(sql, (rs, rowNum) -> {
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("denominationId", rs.getLong("denomination_id"));
|
||||
row.put("denominationValue", rs.getInt("denomination_value"));
|
||||
row.put("kind", rs.getString("kind"));
|
||||
row.put("morningCount", rs.getLong("total_count"));
|
||||
row.put("morningAmount", rs.getBigDecimal("total_amount"));
|
||||
return row;
|
||||
}, cashierId, txnDate);
|
||||
|
||||
BigDecimal total = rows.stream()
|
||||
.map(r -> (BigDecimal) r.get("morningAmount"))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("cashierId", cashierId);
|
||||
result.put("date", txnDate);
|
||||
result.put("totalMorning", total);
|
||||
result.put("denominations", rows);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildPositionForCashier(Long cashierId, String dateFilter) {
|
||||
// Vault → Cashier allocations — GROUP BY denomination_id+kind to keep coin and note separate
|
||||
final String sqlIn1 = "SELECT ctd.denomination_id, ctd.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(ctd.count) AS total_count "
|
||||
+ "FROM m_cash_transfer_detail ctd JOIN m_cash_transfer ct ON ct.id = ctd.transfer_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = ctd.denomination_id "
|
||||
+ "WHERE ct.to_cashier_id = ? AND (ct.is_reversed IS NULL OR ct.is_reversed = false) "
|
||||
+ "AND ct.transfer_date <= CAST(? AS DATE) "
|
||||
+ "GROUP BY ctd.denomination_id, ctd.denomination_value, dm.kind";
|
||||
|
||||
// Inter-cashier IN
|
||||
final String sqlIn2 = "SELECT icd.denomination_id, icd.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(icd.count) AS total_count "
|
||||
+ "FROM m_cash_inter_cashier_detail icd JOIN m_cash_inter_cashier ic ON ic.id = icd.inter_cashier_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = icd.denomination_id "
|
||||
+ "WHERE ic.to_cashier_id = ? AND (ic.is_reversed IS NULL OR ic.is_reversed = false) "
|
||||
+ "AND ic.transfer_date <= CAST(? AS DATE) "
|
||||
+ "GROUP BY icd.denomination_id, icd.denomination_value, dm.kind";
|
||||
|
||||
// Customer deposits
|
||||
final String sqlIn3 = "SELECT std.denomination_id, std.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(std.count) AS total_count "
|
||||
+ "FROM m_savings_txn_denomination std "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = std.denomination_id "
|
||||
+ "WHERE std.txn_type = 'DEPOSIT' AND std.txn_date <= CAST(? AS DATE) "
|
||||
+ "AND std.officer_id IN ("
|
||||
+ " SELECT au.id FROM m_appuser au "
|
||||
+ " JOIN m_cashiers mc ON mc.staff_id = au.staff_id WHERE mc.id = ?) "
|
||||
+ "GROUP BY std.denomination_id, std.denomination_value, dm.kind";
|
||||
|
||||
// Settlements back to vault
|
||||
final String sqlOut1 = "SELECT csd.denomination_id, csd.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(csd.count) AS total_count "
|
||||
+ "FROM m_cash_settlement_detail csd JOIN m_cash_settlement cs ON cs.id = csd.settlement_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = csd.denomination_id "
|
||||
+ "WHERE cs.from_cashier_id = ? AND (cs.is_reversed IS NULL OR cs.is_reversed = false) "
|
||||
+ "AND cs.settlement_date <= CAST(? AS DATE) "
|
||||
+ "GROUP BY csd.denomination_id, csd.denomination_value, dm.kind";
|
||||
|
||||
// Inter-cashier OUT
|
||||
final String sqlOut2 = "SELECT icd.denomination_id, icd.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(icd.count) AS total_count "
|
||||
+ "FROM m_cash_inter_cashier_detail icd JOIN m_cash_inter_cashier ic ON ic.id = icd.inter_cashier_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = icd.denomination_id "
|
||||
+ "WHERE ic.from_cashier_id = ? AND (ic.is_reversed IS NULL OR ic.is_reversed = false) "
|
||||
+ "AND ic.transfer_date <= CAST(? AS DATE) "
|
||||
+ "GROUP BY icd.denomination_id, icd.denomination_value, dm.kind";
|
||||
|
||||
// Customer withdrawals
|
||||
final String sqlOut3 = "SELECT std.denomination_id, std.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(std.count) AS total_count "
|
||||
+ "FROM m_savings_txn_denomination std "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = std.denomination_id "
|
||||
+ "WHERE std.txn_type = 'WITHDRAWAL' AND std.txn_date <= CAST(? AS DATE) "
|
||||
+ "AND std.officer_id IN ("
|
||||
+ " SELECT au.id FROM m_appuser au "
|
||||
+ " JOIN m_cashiers mc ON mc.staff_id = au.staff_id WHERE mc.id = ?) "
|
||||
+ "GROUP BY std.denomination_id, std.denomination_value, dm.kind";
|
||||
|
||||
// Exchange IN: customer gave denomination to cashier (RECEIVED direction)
|
||||
final String sqlIn4 = "SELECT ced.denomination_id, ced.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(ced.count) AS total_count "
|
||||
+ "FROM m_cash_exchange_detail ced "
|
||||
+ "JOIN m_cash_exchange ce ON ce.id = ced.exchange_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = ced.denomination_id "
|
||||
+ "WHERE ce.cashier_id = ? AND ced.direction = 'RECEIVED' "
|
||||
+ "AND (ce.is_reversed IS NULL OR ce.is_reversed = false) "
|
||||
+ "AND ce.exchange_date <= CAST(? AS DATE) "
|
||||
+ "GROUP BY ced.denomination_id, ced.denomination_value, dm.kind";
|
||||
|
||||
// Exchange OUT: cashier gave denomination to customer (GIVEN direction)
|
||||
final String sqlOut4 = "SELECT ced.denomination_id, ced.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(ced.count) AS total_count "
|
||||
+ "FROM m_cash_exchange_detail ced "
|
||||
+ "JOIN m_cash_exchange ce ON ce.id = ced.exchange_id "
|
||||
+ "LEFT JOIN m_denomination_master dm ON dm.id = ced.denomination_id "
|
||||
+ "WHERE ce.cashier_id = ? AND ced.direction = 'GIVEN' "
|
||||
+ "AND (ce.is_reversed IS NULL OR ce.is_reversed = false) "
|
||||
+ "AND ce.exchange_date <= CAST(? AS DATE) "
|
||||
+ "GROUP BY ced.denomination_id, ced.denomination_value, dm.kind";
|
||||
|
||||
// Balance map: denomination_id → long[3] {denomValue, kindFlag (0=note/1=coin), netCount}
|
||||
Map<Long, long[]> balance = new LinkedHashMap<>();
|
||||
|
||||
addToBalance(balance, jdbcTemplate.query(sqlIn1, DENOM_ROW, cashierId, dateFilter), 1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlIn2, DENOM_ROW, cashierId, dateFilter), 1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlIn3, DENOM_ROW, dateFilter, cashierId), 1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlIn4, DENOM_ROW, cashierId, dateFilter), 1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlOut1, DENOM_ROW, cashierId, dateFilter), -1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlOut2, DENOM_ROW, cashierId, dateFilter), -1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlOut3, DENOM_ROW, dateFilter, cashierId), -1);
|
||||
addToBalance(balance, jdbcTemplate.query(sqlOut4, DENOM_ROW, cashierId, dateFilter), -1);
|
||||
|
||||
List<Map<String, Object>> rows = new ArrayList<>();
|
||||
balance.entrySet().stream()
|
||||
.filter(e -> e.getValue()[2] != 0)
|
||||
.sorted((a, b) -> Long.compare(b.getValue()[0], a.getValue()[0]))
|
||||
.forEach(e -> {
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("denominationId", e.getKey());
|
||||
row.put("denominationValue", (int) e.getValue()[0]);
|
||||
row.put("kind", e.getValue()[1] == 1L ? "coin" : "note");
|
||||
row.put("count", e.getValue()[2]);
|
||||
row.put("amount", BigDecimal.valueOf(e.getValue()[0] * e.getValue()[2]));
|
||||
rows.add(row);
|
||||
});
|
||||
|
||||
BigDecimal totalAmount = rows.stream()
|
||||
.map(r -> (BigDecimal) r.get("amount"))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
String cashierName = null;
|
||||
try {
|
||||
cashierName = jdbcTemplate.queryForObject(
|
||||
"SELECT ms.display_name FROM m_cashiers mc JOIN m_staff ms ON ms.id = mc.staff_id WHERE mc.id = ?",
|
||||
String.class, cashierId);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("cashierId", cashierId);
|
||||
result.put("cashierName", cashierName);
|
||||
result.put("asOfDate", dateFilter);
|
||||
result.put("totalAmount", totalAmount);
|
||||
result.put("denominations", rows);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Row mapper: returns Object[]{Long denomId, Integer denomValue, String kind, Long count}
|
||||
private static final org.springframework.jdbc.core.RowMapper<Object[]> DENOM_ROW =
|
||||
(rs, r) -> new Object[] {
|
||||
rs.getLong("denomination_id"),
|
||||
rs.getInt("denomination_value"),
|
||||
rs.getString("kind"),
|
||||
rs.getLong("total_count")
|
||||
};
|
||||
|
||||
private void addToBalance(Map<Long, long[]> balance, List<Object[]> rows, int sign) {
|
||||
for (Object[] row : rows) {
|
||||
Long denomId = (Long) row[0];
|
||||
int denomValue = (Integer) row[1];
|
||||
long kindFlag = "coin".equalsIgnoreCase((String) row[2]) ? 1L : 0L;
|
||||
long count = (Long) row[3];
|
||||
long[] entry = balance.computeIfAbsent(denomId, k -> new long[] { denomValue, kindFlag, 0L });
|
||||
entry[2] += count * sign;
|
||||
}
|
||||
}
|
||||
}
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
package org.apache.fineract.portfolio.cashsettlement.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlement;
|
||||
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlementDetail;
|
||||
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlementDetailRepository;
|
||||
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlementRepository;
|
||||
import org.apache.fineract.portfolio.teller.service.GlDispatchService;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
@Path("/v1/cashsettlement")
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public class CashSettlementApiResource {
|
||||
|
||||
private final CashSettlementRepository repository;
|
||||
private final CashSettlementDetailRepository detailRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final GlDispatchService glDispatchService;
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@POST
|
||||
public String create(String body) {
|
||||
context.authenticatedUser().validateHasPermissionTo("CREATE_CASHSETTLEMENT");
|
||||
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
|
||||
final Long fromCashierId = json.has("fromCashierId") && !json.get("fromCashierId").isJsonNull()
|
||||
? json.get("fromCashierId").getAsLong() : null;
|
||||
final Long toTellerId = json.has("toTellerId") && !json.get("toTellerId").isJsonNull()
|
||||
? json.get("toTellerId").getAsLong() : null;
|
||||
|
||||
if (fromCashierId == null || toTellerId == null) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashsettlement.parties.required",
|
||||
"fromCashierId and toTellerId are required.");
|
||||
}
|
||||
|
||||
final LocalDate settlementDate = json.has("settlementDate") && !json.get("settlementDate").isJsonNull()
|
||||
? LocalDate.parse(json.get("settlementDate").getAsString()) : LocalDate.now();
|
||||
final String fromParty = json.has("fromParty") && !json.get("fromParty").isJsonNull()
|
||||
? json.get("fromParty").getAsString() : null;
|
||||
final String toParty = json.has("toParty") && !json.get("toParty").isJsonNull()
|
||||
? json.get("toParty").getAsString() : null;
|
||||
final String note = json.has("note") && !json.get("note").isJsonNull()
|
||||
? json.get("note").getAsString() : null;
|
||||
final String currencyCode = json.has("currencyCode") && !json.get("currencyCode").isJsonNull()
|
||||
? json.get("currencyCode").getAsString() : "INR";
|
||||
|
||||
// Require reconciliation before settlement
|
||||
Integer reconCount = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_cash_reconciliation WHERE cashier_id = ? AND recon_date = CAST(? AS DATE)",
|
||||
Integer.class, fromCashierId, settlementDate.toString());
|
||||
if (reconCount == null || reconCount == 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashsettlement.reconciliation.required",
|
||||
"Reconciliation must be completed before settlement for cashier " + fromCashierId + " on " + settlementDate + ". Please complete today's reconciliation first.");
|
||||
}
|
||||
|
||||
// Prevent duplicate settlement for same cashier+date
|
||||
Integer existingCount = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_cash_settlement WHERE from_cashier_id = ? AND settlement_date = CAST(? AS DATE) AND (is_reversed IS NULL OR is_reversed = false)",
|
||||
Integer.class, fromCashierId, settlementDate.toString());
|
||||
if (existingCount != null && existingCount > 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashsettlement.duplicate",
|
||||
"A settlement for cashier " + fromCashierId + " on " + settlementDate + " already exists. Reverse the existing settlement first.");
|
||||
}
|
||||
|
||||
final JsonArray denominations = json.has("denominations") && json.get("denominations").isJsonArray()
|
||||
? json.getAsJsonArray("denominations") : new JsonArray();
|
||||
|
||||
if (!hasPositiveCount(denominations)) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashsettlement.empty",
|
||||
"At least one denomination with a positive count is required.");
|
||||
}
|
||||
|
||||
final BigDecimal total = sumTotal(denominations);
|
||||
|
||||
CashSettlement header = new CashSettlement();
|
||||
header.setSettlementDate(settlementDate);
|
||||
header.setFromCashierId(fromCashierId);
|
||||
header.setToTellerId(toTellerId);
|
||||
header.setFromParty(fromParty);
|
||||
header.setToParty(toParty);
|
||||
header.setSettlementAmount(total);
|
||||
header.setNote(note);
|
||||
header.setCreatedAt(LocalDateTime.now());
|
||||
header.setIsReversed(false);
|
||||
try {
|
||||
AppUser user = this.context.authenticatedUser();
|
||||
header.setPerformedBy(user.getId());
|
||||
header.setPerformedByUsername(user.getUsername());
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// Commit header + details first, then GL dispatch separately to avoid
|
||||
// EclipseLink flushing pending detail rows inside Fineract's GL transaction.
|
||||
final Long savedId = new TransactionTemplate(transactionManager).execute(status -> {
|
||||
CashSettlement saved = repository.saveAndFlush(header);
|
||||
saveDetails(saved.getId(), denominations);
|
||||
return saved.getId();
|
||||
});
|
||||
|
||||
String glWarning = null;
|
||||
try {
|
||||
// Use cashier's own teller_id from m_cashiers for GL dispatch so the
|
||||
// Fineract cashier-teller link is respected regardless of which vault teller
|
||||
// the user submitted cash to.
|
||||
Long glTellerId = toTellerId;
|
||||
try {
|
||||
Long resolved = jdbcTemplate.queryForObject(
|
||||
"SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, fromCashierId);
|
||||
if (resolved != null) glTellerId = resolved;
|
||||
} catch (Exception ignored) {}
|
||||
glDispatchService.settleCashFromCashier(glTellerId, fromCashierId, total,
|
||||
currencyCode, settlementDate.format(DateTimeFormatter.ISO_LOCAL_DATE),
|
||||
note != null ? note : "");
|
||||
} catch (Exception e) {
|
||||
glWarning = "Settlement saved but GL entry failed: " + e.getMessage();
|
||||
}
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", savedId);
|
||||
if (glWarning != null) result.addProperty("glWarning", glWarning);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{id}/reverse")
|
||||
@Transactional
|
||||
public String reverse(@PathParam("id") Long id, String body) {
|
||||
context.authenticatedUser().validateHasPermissionTo("REVERSE_CASHSETTLEMENT");
|
||||
|
||||
CashSettlement settlement = repository.findById(id)
|
||||
.orElseThrow(() -> new PlatformDataIntegrityException("error.msg.cashsettlement.not.found",
|
||||
"Settlement not found: " + id));
|
||||
if (Boolean.TRUE.equals(settlement.getIsReversed())) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashsettlement.already.reversed",
|
||||
"Settlement " + id + " is already reversed.");
|
||||
}
|
||||
|
||||
String reason = null;
|
||||
try {
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
reason = json.has("reason") ? json.get("reason").getAsString() : null;
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
AppUser user = null;
|
||||
try { user = this.context.authenticatedUser(); } catch (Exception ignored) {}
|
||||
|
||||
settlement.setIsReversed(true);
|
||||
settlement.setReversedAt(LocalDateTime.now());
|
||||
settlement.setReverseReason(reason);
|
||||
if (user != null) settlement.setReversedBy(user.getId());
|
||||
repository.save(settlement);
|
||||
|
||||
if (settlement.getToTellerId() != null && settlement.getFromCashierId() != null) {
|
||||
try {
|
||||
Long glTellerId = settlement.getToTellerId();
|
||||
try {
|
||||
Long resolved = jdbcTemplate.queryForObject(
|
||||
"SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, settlement.getFromCashierId());
|
||||
if (resolved != null) glTellerId = resolved;
|
||||
} catch (Exception ignored) {}
|
||||
glDispatchService.allocateCashToCashier(glTellerId,
|
||||
settlement.getFromCashierId(), settlement.getSettlementAmount(), "INR",
|
||||
LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE),
|
||||
"Reversal of cash settlement #" + id + (reason != null ? ": " + reason : ""));
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", id);
|
||||
result.addProperty("reversed", true);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("check")
|
||||
public String check(@QueryParam("cashierId") Long cashierId, @QueryParam("date") String date) {
|
||||
context.authenticatedUser().validateHasPermissionTo("READ_CASHSETTLEMENT");
|
||||
if (cashierId == null) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashsettlement.cashier.required",
|
||||
"cashierId query param is required.");
|
||||
}
|
||||
final String settlementDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
|
||||
Integer count = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_cash_settlement WHERE from_cashier_id = ? AND settlement_date = CAST(? AS DATE) AND (is_reversed IS NULL OR is_reversed = false)",
|
||||
Integer.class, cashierId, settlementDate);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("cashierId", cashierId);
|
||||
result.put("date", settlementDate);
|
||||
result.put("alreadySettled", count != null && count > 0);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
public String getAll() {
|
||||
List<CashSettlement> list = repository.findAllByOrderByCreatedAtDesc();
|
||||
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
public String getOne(@PathParam("id") Long id) {
|
||||
CashSettlement header = repository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Cash settlement not found: " + id));
|
||||
Map<String, Object> map = toMap(header);
|
||||
List<Map<String, Object>> details = detailRepository.findBySettlementId(id).stream()
|
||||
.map(this::detailToMap).collect(Collectors.toList());
|
||||
map.put("details", details);
|
||||
return gson.toJson(map);
|
||||
}
|
||||
|
||||
private BigDecimal sumTotal(JsonArray items) {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int value = item.has("denominationValue") ? item.get("denominationValue").getAsInt() : 0;
|
||||
int count = item.has("count") ? item.get("count").getAsInt() : 0;
|
||||
if (count > 0) total = total.add(BigDecimal.valueOf((long) value * count));
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private boolean hasPositiveCount(JsonArray items) {
|
||||
for (JsonElement el : items) {
|
||||
if (el.getAsJsonObject().get("count").getAsInt() > 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void saveDetails(Long settlementId, JsonArray items) {
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int count = item.has("count") ? item.get("count").getAsInt() : 0;
|
||||
if (count <= 0) continue;
|
||||
int value = item.has("denominationValue") ? item.get("denominationValue").getAsInt() : 0;
|
||||
Long denomId = item.has("denominationId") && !item.get("denominationId").isJsonNull()
|
||||
? item.get("denominationId").getAsLong() : null;
|
||||
CashSettlementDetail d = new CashSettlementDetail();
|
||||
d.setSettlementId(settlementId);
|
||||
d.setDenominationId(denomId);
|
||||
d.setDenominationValue(value);
|
||||
d.setCount(count);
|
||||
d.setTotalAmount(BigDecimal.valueOf((long) value * count));
|
||||
detailRepository.save(d);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap(CashSettlement e) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("id", e.getId());
|
||||
m.put("settlementDate", e.getSettlementDate() != null ? e.getSettlementDate().toString() : null);
|
||||
m.put("fromCashierId", e.getFromCashierId());
|
||||
m.put("toTellerId", e.getToTellerId());
|
||||
m.put("fromParty", e.getFromParty());
|
||||
m.put("toParty", e.getToParty());
|
||||
m.put("settlementAmount", e.getSettlementAmount());
|
||||
m.put("note", e.getNote());
|
||||
m.put("performedBy", e.getPerformedBy());
|
||||
m.put("performedByUsername", e.getPerformedByUsername());
|
||||
m.put("createdAt", e.getCreatedAt() != null ? e.getCreatedAt().toString() : null);
|
||||
m.put("isReversed", e.getIsReversed());
|
||||
m.put("reversedBy", e.getReversedBy());
|
||||
m.put("reversedAt", e.getReversedAt() != null ? e.getReversedAt().toString() : null);
|
||||
m.put("reverseReason", e.getReverseReason());
|
||||
return m;
|
||||
}
|
||||
|
||||
private Map<String, Object> detailToMap(CashSettlementDetail d) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("id", d.getId());
|
||||
m.put("denominationId", d.getDenominationId());
|
||||
m.put("denominationValue", d.getDenominationValue());
|
||||
m.put("count", d.getCount());
|
||||
m.put("totalAmount", d.getTotalAmount());
|
||||
return m;
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package org.apache.fineract.portfolio.cashsettlement.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_settlement")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashSettlement {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "settlement_date")
|
||||
private LocalDate settlementDate;
|
||||
|
||||
@Column(name = "from_cashier_id")
|
||||
private Long fromCashierId;
|
||||
|
||||
@Column(name = "to_teller_id")
|
||||
private Long toTellerId;
|
||||
|
||||
@Column(name = "from_party", length = 100)
|
||||
private String fromParty;
|
||||
|
||||
@Column(name = "to_party", length = 100)
|
||||
private String toParty;
|
||||
|
||||
@Column(name = "settlement_amount", nullable = false)
|
||||
private BigDecimal settlementAmount;
|
||||
|
||||
@Column(name = "note", length = 500)
|
||||
private String note;
|
||||
|
||||
@Column(name = "performed_by")
|
||||
private Long performedBy;
|
||||
|
||||
@Column(name = "performed_by_username", length = 100)
|
||||
private String performedByUsername;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "is_reversed")
|
||||
private Boolean isReversed = false;
|
||||
|
||||
@Column(name = "reversed_by")
|
||||
private Long reversedBy;
|
||||
|
||||
@Column(name = "reversed_at")
|
||||
private LocalDateTime reversedAt;
|
||||
|
||||
@Column(name = "reverse_reason", length = 500)
|
||||
private String reverseReason;
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package org.apache.fineract.portfolio.cashsettlement.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_settlement_detail")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashSettlementDetail {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "settlement_id", nullable = false)
|
||||
private Long settlementId;
|
||||
|
||||
@Column(name = "denomination_id")
|
||||
private Long denominationId;
|
||||
|
||||
@Column(name = "denomination_value", nullable = false)
|
||||
private Integer denominationValue;
|
||||
|
||||
@Column(name = "count", nullable = false)
|
||||
private Integer count;
|
||||
|
||||
@Column(name = "total_amount", nullable = false)
|
||||
private BigDecimal totalAmount;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package org.apache.fineract.portfolio.cashsettlement.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CashSettlementDetailRepository extends JpaRepository<CashSettlementDetail, Long> {
|
||||
List<CashSettlementDetail> findBySettlementId(Long settlementId);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.apache.fineract.portfolio.cashsettlement.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CashSettlementRepository extends JpaRepository<CashSettlement, Long> {
|
||||
List<CashSettlement> findAllByOrderByCreatedAtDesc();
|
||||
List<CashSettlement> findByFromCashierIdOrderByCreatedAtDesc(Long cashierId);
|
||||
}
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
package org.apache.fineract.portfolio.cashtransfer.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.portfolio.teller.service.GlDispatchService;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.cashtransfer.domain.CashTransfer;
|
||||
import org.apache.fineract.portfolio.cashtransfer.domain.CashTransferDetail;
|
||||
import org.apache.fineract.portfolio.cashtransfer.domain.CashTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.cashtransfer.domain.CashTransferRepository;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
@Path("/v1/cashtransfer")
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public class CashTransferApiResource {
|
||||
|
||||
private final CashTransferRepository repository;
|
||||
private final CashTransferDetailRepository detailRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final GlDispatchService glDispatchService;
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@POST
|
||||
public String create(String body) {
|
||||
this.context.authenticatedUser().validateHasPermissionTo("CREATE_CASHTRANSFER");
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
|
||||
final LocalDate transferDate = json.has("transferDate") && !json.get("transferDate").isJsonNull()
|
||||
? LocalDate.parse(json.get("transferDate").getAsString())
|
||||
: null;
|
||||
final String fromParty = json.has("fromParty") && !json.get("fromParty").isJsonNull() ? json.get("fromParty").getAsString()
|
||||
: null;
|
||||
final String toParty = json.has("toParty") && !json.get("toParty").isJsonNull() ? json.get("toParty").getAsString() : null;
|
||||
final String note = json.has("note") && !json.get("note").isJsonNull() ? json.get("note").getAsString() : null;
|
||||
|
||||
final Long fromTellerId = json.has("fromTellerId") && !json.get("fromTellerId").isJsonNull()
|
||||
? json.get("fromTellerId").getAsLong()
|
||||
: null;
|
||||
final Long toCashierId = json.has("toCashierId") && !json.get("toCashierId").isJsonNull()
|
||||
? json.get("toCashierId").getAsLong()
|
||||
: null;
|
||||
|
||||
if (fromTellerId == null || toCashierId == null) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashtransfer.parties.required",
|
||||
"fromTellerId and toCashierId are required.");
|
||||
}
|
||||
|
||||
final String currencyCode = json.has("currencyCode") && !json.get("currencyCode").isJsonNull()
|
||||
? json.get("currencyCode").getAsString()
|
||||
: "INR";
|
||||
|
||||
final JsonArray denominations = json.has("denominations") && json.get("denominations").isJsonArray()
|
||||
? json.getAsJsonArray("denominations")
|
||||
: new JsonArray();
|
||||
|
||||
if (!hasPositiveCount(denominations)) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashtransfer.empty",
|
||||
"At least one denomination with a positive count is required.");
|
||||
}
|
||||
|
||||
final BigDecimal total = sumTotal(denominations);
|
||||
if (total.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashtransfer.empty",
|
||||
"At least one denomination with a positive count is required.");
|
||||
}
|
||||
|
||||
CashTransfer header = new CashTransfer();
|
||||
header.setTransferDate(transferDate);
|
||||
header.setFromParty(fromParty);
|
||||
header.setToParty(toParty);
|
||||
header.setFromTellerId(fromTellerId);
|
||||
header.setToCashierId(toCashierId);
|
||||
header.setTransferAmount(total);
|
||||
header.setNote(note);
|
||||
header.setCreatedAt(LocalDateTime.now());
|
||||
try {
|
||||
AppUser user = this.context.authenticatedUser();
|
||||
if (user != null) {
|
||||
header.setPerformedBy(user.getId());
|
||||
header.setPerformedByUsername(user.getUsername());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// resolving the current user must never block recording the transfer
|
||||
}
|
||||
|
||||
// Save header + details in one committed transaction, then GL dispatch separately.
|
||||
// This prevents EclipseLink from flushing pending detail rows inside Fineract's
|
||||
// ALLOCATECASHTOCASHIER transaction, which caused null transfer_id constraint errors.
|
||||
final Long savedId = new TransactionTemplate(transactionManager).execute(status -> {
|
||||
CashTransfer saved = repository.saveAndFlush(header);
|
||||
saveDetails(saved.getId(), denominations);
|
||||
return saved.getId();
|
||||
});
|
||||
|
||||
String glWarning = null;
|
||||
try {
|
||||
// Use the cashier's own teller_id from m_cashiers for GL dispatch.
|
||||
// Fineract's allocateCashToCashier requires cashier and teller to be linked —
|
||||
// the user-selected fromTellerId is the business vault but may differ from the
|
||||
// cashier's registered teller_id. Always resolve from DB to avoid GL failures.
|
||||
Long glTellerId = fromTellerId;
|
||||
try {
|
||||
Long resolved = jdbcTemplate.queryForObject(
|
||||
"SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, toCashierId);
|
||||
if (resolved != null) glTellerId = resolved;
|
||||
} catch (Exception ignored) {}
|
||||
final LocalDate txnDate = transferDate != null ? transferDate : LocalDate.now();
|
||||
glDispatchService.allocateCashToCashier(glTellerId, toCashierId, total,
|
||||
currencyCode, txnDate.format(DateTimeFormatter.ISO_LOCAL_DATE),
|
||||
note != null ? note : "");
|
||||
} catch (Exception e) {
|
||||
glWarning = "Transfer saved but GL entry failed: " + e.getMessage();
|
||||
}
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", savedId);
|
||||
if (glWarning != null) result.addProperty("glWarning", glWarning);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{id}/reverse")
|
||||
@Transactional
|
||||
public String reverse(@PathParam("id") Long id, String body) {
|
||||
this.context.authenticatedUser().validateHasPermissionTo("REVERSE_CASHTRANSFER");
|
||||
|
||||
CashTransfer transfer = repository.findById(id)
|
||||
.orElseThrow(() -> new PlatformDataIntegrityException("error.msg.cashtransfer.not.found",
|
||||
"Cash transfer not found: " + id));
|
||||
if (Boolean.TRUE.equals(transfer.getIsReversed())) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashtransfer.already.reversed",
|
||||
"Cash transfer " + id + " is already reversed.");
|
||||
}
|
||||
|
||||
String reason = null;
|
||||
try {
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
reason = json.has("reason") ? json.get("reason").getAsString() : null;
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
try {
|
||||
AppUser user = this.context.authenticatedUser();
|
||||
transfer.setReversedBy(user.getId());
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
transfer.setIsReversed(true);
|
||||
transfer.setReversedAt(java.time.LocalDateTime.now());
|
||||
transfer.setReverseReason(reason);
|
||||
repository.save(transfer);
|
||||
|
||||
if (transfer.getFromTellerId() != null && transfer.getToCashierId() != null) {
|
||||
try {
|
||||
Long glTellerId = transfer.getFromTellerId();
|
||||
try {
|
||||
Long resolved = jdbcTemplate.queryForObject(
|
||||
"SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, transfer.getToCashierId());
|
||||
if (resolved != null) glTellerId = resolved;
|
||||
} catch (Exception ignored) {}
|
||||
glDispatchService.settleCashFromCashier(glTellerId,
|
||||
transfer.getToCashierId(), transfer.getTransferAmount(), "INR",
|
||||
java.time.LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE),
|
||||
"Reversal of cash transfer #" + id + (reason != null ? ": " + reason : ""));
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", id);
|
||||
result.addProperty("reversed", true);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
public String getAll() {
|
||||
List<CashTransfer> list = repository.findAllByOrderByCreatedAtDesc();
|
||||
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
public String getOne(@PathParam("id") Long id) {
|
||||
CashTransfer header = repository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Cash transfer not found: " + id));
|
||||
Map<String, Object> map = toMap(header);
|
||||
List<Map<String, Object>> details = detailRepository.findByTransferId(id).stream().map(this::detailToMap)
|
||||
.collect(Collectors.toList());
|
||||
map.put("details", details);
|
||||
return gson.toJson(map);
|
||||
}
|
||||
|
||||
private BigDecimal sumTotal(JsonArray items) {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int value = item.has("denominationValue") && !item.get("denominationValue").isJsonNull()
|
||||
? item.get("denominationValue").getAsInt()
|
||||
: 0;
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count > 0) {
|
||||
total = total.add(BigDecimal.valueOf((long) value * count));
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private boolean hasPositiveCount(JsonArray items) {
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void saveDetails(Long transferId, JsonArray items) {
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count <= 0) {
|
||||
continue;
|
||||
}
|
||||
int value = item.has("denominationValue") && !item.get("denominationValue").isJsonNull()
|
||||
? item.get("denominationValue").getAsInt()
|
||||
: 0;
|
||||
Long denominationId = item.has("denominationId") && !item.get("denominationId").isJsonNull()
|
||||
? item.get("denominationId").getAsLong()
|
||||
: null;
|
||||
CashTransferDetail detail = new CashTransferDetail();
|
||||
detail.setTransferId(transferId);
|
||||
detail.setDenominationId(denominationId);
|
||||
detail.setDenominationValue(value);
|
||||
detail.setCount(count);
|
||||
detail.setTotalAmount(BigDecimal.valueOf((long) value * count));
|
||||
detailRepository.save(detail);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap(CashTransfer e) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("id", e.getId());
|
||||
map.put("transferDate", e.getTransferDate() != null ? e.getTransferDate().toString() : null);
|
||||
map.put("fromParty", e.getFromParty());
|
||||
map.put("toParty", e.getToParty());
|
||||
map.put("fromTellerId", e.getFromTellerId());
|
||||
map.put("toCashierId", e.getToCashierId());
|
||||
map.put("transferAmount", e.getTransferAmount());
|
||||
map.put("note", e.getNote());
|
||||
map.put("performedBy", e.getPerformedBy());
|
||||
map.put("performedByUsername", e.getPerformedByUsername());
|
||||
map.put("createdAt", e.getCreatedAt() != null ? e.getCreatedAt().toString() : null);
|
||||
map.put("isReversed", Boolean.TRUE.equals(e.getIsReversed()));
|
||||
map.put("reversedBy", e.getReversedBy());
|
||||
map.put("reversedAt", e.getReversedAt() != null ? e.getReversedAt().toString() : null);
|
||||
map.put("reverseReason", e.getReverseReason());
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, Object> detailToMap(CashTransferDetail d) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("id", d.getId());
|
||||
map.put("denominationId", d.getDenominationId());
|
||||
map.put("denominationValue", d.getDenominationValue());
|
||||
map.put("count", d.getCount());
|
||||
map.put("totalAmount", d.getTotalAmount());
|
||||
return map;
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.cashtransfer.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_transfer")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashTransfer {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "transfer_date")
|
||||
private LocalDate transferDate;
|
||||
|
||||
@Column(name = "from_party", length = 100)
|
||||
private String fromParty;
|
||||
|
||||
@Column(name = "to_party", length = 100)
|
||||
private String toParty;
|
||||
|
||||
@Column(name = "transfer_amount", nullable = false)
|
||||
private BigDecimal transferAmount;
|
||||
|
||||
@Column(name = "note", length = 500)
|
||||
private String note;
|
||||
|
||||
@Column(name = "performed_by")
|
||||
private Long performedBy;
|
||||
|
||||
@Column(name = "performed_by_username", length = 100)
|
||||
private String performedByUsername;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "from_teller_id")
|
||||
private Long fromTellerId;
|
||||
|
||||
@Column(name = "to_cashier_id")
|
||||
private Long toCashierId;
|
||||
|
||||
@Column(name = "is_reversed")
|
||||
private Boolean isReversed = false;
|
||||
|
||||
@Column(name = "reversed_by")
|
||||
private Long reversedBy;
|
||||
|
||||
@Column(name = "reversed_at")
|
||||
private java.time.LocalDateTime reversedAt;
|
||||
|
||||
@Column(name = "reverse_reason", length = 500)
|
||||
private String reverseReason;
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.cashtransfer.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_transfer_detail")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashTransferDetail {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "transfer_id", nullable = false)
|
||||
private Long transferId;
|
||||
|
||||
@Column(name = "denomination_id")
|
||||
private Long denominationId;
|
||||
|
||||
@Column(name = "denomination_value", nullable = false)
|
||||
private Integer denominationValue;
|
||||
|
||||
@Column(name = "count", nullable = false)
|
||||
private Integer count;
|
||||
|
||||
@Column(name = "total_amount", nullable = false)
|
||||
private BigDecimal totalAmount;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package org.apache.fineract.portfolio.cashtransfer.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CashTransferDetailRepository extends JpaRepository<CashTransferDetail, Long> {
|
||||
List<CashTransferDetail> findByTransferId(Long transferId);
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package org.apache.fineract.portfolio.cashtransfer.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CashTransferRepository extends JpaRepository<CashTransfer, Long> {
|
||||
List<CashTransfer> findAllByOrderByCreatedAtDesc();
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
package org.apache.fineract.portfolio.denominationmaster.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.denominationmaster.domain.DenominationMaster;
|
||||
import org.apache.fineract.portfolio.denominationmaster.domain.DenominationMasterAudit;
|
||||
import org.apache.fineract.portfolio.denominationmaster.domain.DenominationMasterAuditRepository;
|
||||
import org.apache.fineract.portfolio.denominationmaster.domain.DenominationMasterRepository;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/denominationmaster")
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public class DenominationMasterApiResource {
|
||||
|
||||
private final DenominationMasterRepository repository;
|
||||
private final DenominationMasterAuditRepository auditRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@GET
|
||||
public String getAll() {
|
||||
List<DenominationMaster> list = repository.findByDeletedFalseOrderByKindDescDenominationDesc();
|
||||
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("active")
|
||||
public String getActive() {
|
||||
List<DenominationMaster> list = repository.findByIsActiveTrueAndDeletedFalseOrderByKindDescDenominationDesc();
|
||||
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("deleted")
|
||||
public String getDeleted() {
|
||||
List<DenominationMaster> list = repository.findByDeletedTrueOrderByKindDescDenominationDesc();
|
||||
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("audit")
|
||||
public String getAllAudit() {
|
||||
List<DenominationMasterAudit> list = auditRepository.findAllByOrderByPerformedOnDesc();
|
||||
return gson.toJson(list.stream().map(this::auditToMap).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}/audit")
|
||||
public String getAuditForDenomination(@PathParam("id") Long id) {
|
||||
List<DenominationMasterAudit> list = auditRepository.findByDenominationIdOrderByPerformedOnDesc(id);
|
||||
return gson.toJson(list.stream().map(this::auditToMap).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@POST
|
||||
public String create(String body) {
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
final int denomination = json.get("denomination").getAsInt();
|
||||
final String kind = json.get("kind").getAsString();
|
||||
if (repository.existsByDenominationAndKindAndDeletedFalse(denomination, kind)) {
|
||||
throw new PlatformDataIntegrityException("error.msg.denomination.duplicate",
|
||||
"A " + kind + " of denomination " + denomination + " already exists.", "denomination", denomination);
|
||||
}
|
||||
DenominationMaster dm = DenominationMaster.create(denomination, kind,
|
||||
json.has("description") ? json.get("description").getAsString() : null,
|
||||
json.has("isActive") ? json.get("isActive").getAsBoolean() : true,
|
||||
json.has("introducedOn") ? LocalDate.parse(json.get("introducedOn").getAsString()) : null, null);
|
||||
DenominationMaster saved = repository.save(dm);
|
||||
writeAudit("CREATE", saved, null);
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", saved.getId());
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("{id}")
|
||||
public String update(@PathParam("id") Long id, @QueryParam("command") String command, String body) {
|
||||
DenominationMaster dm = repository.findById(id).orElseThrow(() -> new RuntimeException("Denomination not found: " + id));
|
||||
|
||||
if ("activate".equals(command)) {
|
||||
dm.setIsActive(true);
|
||||
dm.setDiscontinuedOn(null);
|
||||
repository.save(dm);
|
||||
writeAudit("ACTIVATE", dm, null);
|
||||
} else if ("deactivate".equals(command)) {
|
||||
dm.setIsActive(false);
|
||||
dm.setDiscontinuedOn(LocalDate.now());
|
||||
repository.save(dm);
|
||||
writeAudit("DEACTIVATE", dm, null);
|
||||
} else if ("restore".equals(command)) {
|
||||
if (repository.existsByDenominationAndKindAndDeletedFalseAndIdNot(dm.getDenomination(), dm.getKind(), id)) {
|
||||
throw new PlatformDataIntegrityException("error.msg.denomination.duplicate.restore",
|
||||
"Cannot restore: a " + dm.getKind() + " of denomination " + dm.getDenomination() + " already exists.",
|
||||
"denomination", dm.getDenomination());
|
||||
}
|
||||
dm.setDeleted(false);
|
||||
repository.save(dm);
|
||||
writeAudit("RESTORE", dm, null);
|
||||
} else {
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
final int effectiveDenomination = json.has("denomination") ? json.get("denomination").getAsInt() : dm.getDenomination();
|
||||
final String effectiveKind = json.has("kind") ? json.get("kind").getAsString() : dm.getKind();
|
||||
if (repository.existsByDenominationAndKindAndDeletedFalseAndIdNot(effectiveDenomination, effectiveKind, id)) {
|
||||
throw new PlatformDataIntegrityException("error.msg.denomination.duplicate",
|
||||
"A " + effectiveKind + " of denomination " + effectiveDenomination + " already exists.", "denomination",
|
||||
effectiveDenomination);
|
||||
}
|
||||
List<String> changes = new ArrayList<>();
|
||||
if (json.has("denomination")) {
|
||||
int newVal = json.get("denomination").getAsInt();
|
||||
if (!java.util.Objects.equals(dm.getDenomination(), newVal)) {
|
||||
changes.add("denomination: " + dm.getDenomination() + " -> " + newVal);
|
||||
}
|
||||
dm.setDenomination(newVal);
|
||||
}
|
||||
if (json.has("kind")) {
|
||||
String newVal = json.get("kind").getAsString();
|
||||
if (!java.util.Objects.equals(dm.getKind(), newVal)) {
|
||||
changes.add("kind: " + dm.getKind() + " -> " + newVal);
|
||||
}
|
||||
dm.setKind(newVal);
|
||||
}
|
||||
if (json.has("description")) {
|
||||
String newVal = json.get("description").getAsString();
|
||||
if (!java.util.Objects.equals(dm.getDescription(), newVal)) {
|
||||
changes.add("description: " + dm.getDescription() + " -> " + newVal);
|
||||
}
|
||||
dm.setDescription(newVal);
|
||||
}
|
||||
if (json.has("isActive")) {
|
||||
boolean newVal = json.get("isActive").getAsBoolean();
|
||||
if (!java.util.Objects.equals(dm.getIsActive(), newVal)) {
|
||||
changes.add("isActive: " + dm.getIsActive() + " -> " + newVal);
|
||||
}
|
||||
dm.setIsActive(newVal);
|
||||
}
|
||||
if (json.has("introducedOn")) {
|
||||
LocalDate newVal = LocalDate.parse(json.get("introducedOn").getAsString());
|
||||
if (!java.util.Objects.equals(dm.getIntroducedOn(), newVal)) {
|
||||
changes.add("introducedOn: " + dm.getIntroducedOn() + " -> " + newVal);
|
||||
}
|
||||
dm.setIntroducedOn(newVal);
|
||||
}
|
||||
repository.save(dm);
|
||||
writeAudit("UPDATE", dm, changes.isEmpty() ? null : String.join("; ", changes));
|
||||
}
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", id);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{id}")
|
||||
public String delete(@PathParam("id") Long id) {
|
||||
DenominationMaster dm = repository.findById(id).orElseThrow(() -> new RuntimeException("Denomination not found: " + id));
|
||||
// soft delete: flag the row instead of removing it, so it can be restored and history is preserved
|
||||
dm.setDeleted(true);
|
||||
dm.setIsActive(false);
|
||||
repository.save(dm);
|
||||
writeAudit("DELETE", dm, null);
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", id);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
private void writeAudit(final String action, final DenominationMaster dm, final String changes) {
|
||||
Long userId = null;
|
||||
String username = null;
|
||||
try {
|
||||
AppUser user = this.context.authenticatedUser();
|
||||
if (user != null) {
|
||||
userId = user.getId();
|
||||
username = user.getUsername();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// audit should never block the main operation if the user cannot be resolved
|
||||
}
|
||||
auditRepository.save(DenominationMasterAudit.of(action, dm, changes, userId, username));
|
||||
}
|
||||
|
||||
private java.util.Map<String, Object> toMap(DenominationMaster dm) {
|
||||
java.util.Map<String, Object> map = new java.util.LinkedHashMap<>();
|
||||
map.put("id", dm.getId());
|
||||
map.put("denomination", dm.getDenomination());
|
||||
map.put("kind", dm.getKind());
|
||||
map.put("description", dm.getDescription());
|
||||
map.put("isActive", dm.getIsActive());
|
||||
map.put("introducedOn", dm.getIntroducedOn() != null ? dm.getIntroducedOn().toString() : null);
|
||||
map.put("discontinuedOn", dm.getDiscontinuedOn() != null ? dm.getDiscontinuedOn().toString() : null);
|
||||
map.put("deleted", dm.getDeleted());
|
||||
return map;
|
||||
}
|
||||
|
||||
private java.util.Map<String, Object> auditToMap(DenominationMasterAudit a) {
|
||||
java.util.Map<String, Object> map = new java.util.LinkedHashMap<>();
|
||||
map.put("id", a.getId());
|
||||
map.put("denominationId", a.getDenominationId());
|
||||
map.put("action", a.getAction());
|
||||
map.put("denomination", a.getDenomination());
|
||||
map.put("kind", a.getKind());
|
||||
map.put("description", a.getDescription());
|
||||
map.put("isActive", a.getIsActive());
|
||||
map.put("introducedOn", a.getIntroducedOn() != null ? a.getIntroducedOn().toString() : null);
|
||||
map.put("discontinuedOn", a.getDiscontinuedOn() != null ? a.getDiscontinuedOn().toString() : null);
|
||||
map.put("changes", a.getChanges());
|
||||
map.put("performedBy", a.getPerformedBy());
|
||||
map.put("performedByUsername", a.getPerformedByUsername());
|
||||
map.put("performedOn", a.getPerformedOn() != null ? a.getPerformedOn().toString() : null);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.denominationmaster.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_denomination_master")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class DenominationMaster {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "denomination", nullable = false)
|
||||
private Integer denomination;
|
||||
|
||||
@Column(name = "kind", nullable = false, length = 10)
|
||||
private String kind;
|
||||
|
||||
@Column(name = "description", length = 100)
|
||||
private String description;
|
||||
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private Boolean isActive = true;
|
||||
|
||||
@Column(name = "introduced_on")
|
||||
private LocalDate introducedOn;
|
||||
|
||||
@Column(name = "discontinued_on")
|
||||
private LocalDate discontinuedOn;
|
||||
|
||||
@Column(name = "deleted", nullable = false)
|
||||
private Boolean deleted = false;
|
||||
|
||||
public static DenominationMaster create(Integer denomination, String kind, String description,
|
||||
Boolean isActive, LocalDate introducedOn, LocalDate discontinuedOn) {
|
||||
DenominationMaster dm = new DenominationMaster();
|
||||
dm.denomination = denomination;
|
||||
dm.kind = kind;
|
||||
dm.description = description;
|
||||
dm.isActive = isActive != null ? isActive : true;
|
||||
dm.introducedOn = introducedOn;
|
||||
dm.discontinuedOn = discontinuedOn;
|
||||
dm.deleted = false;
|
||||
return dm;
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.denominationmaster.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_denomination_master_audit")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class DenominationMasterAudit {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "denomination_id")
|
||||
private Long denominationId;
|
||||
|
||||
@Column(name = "action", nullable = false, length = 20)
|
||||
private String action;
|
||||
|
||||
@Column(name = "denomination")
|
||||
private Integer denomination;
|
||||
|
||||
@Column(name = "kind", length = 10)
|
||||
private String kind;
|
||||
|
||||
@Column(name = "description", length = 100)
|
||||
private String description;
|
||||
|
||||
@Column(name = "is_active")
|
||||
private Boolean isActive;
|
||||
|
||||
@Column(name = "introduced_on")
|
||||
private LocalDate introducedOn;
|
||||
|
||||
@Column(name = "discontinued_on")
|
||||
private LocalDate discontinuedOn;
|
||||
|
||||
@Column(name = "changes", length = 1000)
|
||||
private String changes;
|
||||
|
||||
@Column(name = "performed_by")
|
||||
private Long performedBy;
|
||||
|
||||
@Column(name = "performed_by_username", length = 100)
|
||||
private String performedByUsername;
|
||||
|
||||
@Column(name = "performed_on", nullable = false)
|
||||
private LocalDateTime performedOn;
|
||||
|
||||
public static DenominationMasterAudit of(final String action, final DenominationMaster dm, final String changes,
|
||||
final Long performedBy, final String performedByUsername) {
|
||||
final DenominationMasterAudit a = new DenominationMasterAudit();
|
||||
a.action = action;
|
||||
a.denominationId = dm.getId();
|
||||
a.denomination = dm.getDenomination();
|
||||
a.kind = dm.getKind();
|
||||
a.description = dm.getDescription();
|
||||
a.isActive = dm.getIsActive();
|
||||
a.introducedOn = dm.getIntroducedOn();
|
||||
a.discontinuedOn = dm.getDiscontinuedOn();
|
||||
a.changes = changes;
|
||||
a.performedBy = performedBy;
|
||||
a.performedByUsername = performedByUsername;
|
||||
a.performedOn = LocalDateTime.now();
|
||||
return a;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.denominationmaster.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface DenominationMasterAuditRepository extends JpaRepository<DenominationMasterAudit, Long> {
|
||||
|
||||
List<DenominationMasterAudit> findByDenominationIdOrderByPerformedOnDesc(Long denominationId);
|
||||
|
||||
List<DenominationMasterAudit> findAllByOrderByPerformedOnDesc();
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package org.apache.fineract.portfolio.denominationmaster.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface DenominationMasterRepository extends JpaRepository<DenominationMaster, Long> {
|
||||
List<DenominationMaster> findByDeletedFalseOrderByKindDescDenominationDesc();
|
||||
List<DenominationMaster> findByIsActiveTrueAndDeletedFalseOrderByKindDescDenominationDesc();
|
||||
List<DenominationMaster> findByDeletedTrueOrderByKindDescDenominationDesc();
|
||||
|
||||
boolean existsByDenominationAndKindAndDeletedFalse(Integer denomination, String kind);
|
||||
boolean existsByDenominationAndKindAndDeletedFalseAndIdNot(Integer denomination, String kind, Long id);
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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.denominationmaster.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_savings_txn_denomination")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SavingsTxnDenomination {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "txn_id", nullable = false)
|
||||
private Long txnId;
|
||||
|
||||
@Column(name = "savings_account_id", nullable = false)
|
||||
private Long savingsAccountId;
|
||||
|
||||
@Column(name = "account_no", length = 50)
|
||||
private String accountNo;
|
||||
|
||||
@Column(name = "client_id")
|
||||
private Long clientId;
|
||||
|
||||
@Column(name = "cif_no", length = 50)
|
||||
private String cifNo;
|
||||
|
||||
@Column(name = "denomination_id", nullable = false)
|
||||
private Long denominationId;
|
||||
|
||||
@Column(name = "denomination_value", nullable = false)
|
||||
private Integer denominationValue;
|
||||
|
||||
@Column(name = "count", nullable = false)
|
||||
private Integer count;
|
||||
|
||||
@Column(name = "total_amount", nullable = false, precision = 19, scale = 6)
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
@Column(name = "txn_date")
|
||||
private LocalDate txnDate;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/** "DEPOSIT" or "WITHDRAWAL" — identifies the direction of the cash flow. */
|
||||
@Column(name = "txn_type", nullable = false, length = 20)
|
||||
private String txnType;
|
||||
|
||||
/** ID of the logged-in teller / officer who performed the transaction. */
|
||||
@Column(name = "officer_id")
|
||||
private Long officerId;
|
||||
|
||||
/** Username of the teller / officer who performed the transaction. */
|
||||
@Column(name = "officer_username", length = 100)
|
||||
private String officerUsername;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.denominationmaster.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface SavingsTxnDenominationRepository extends JpaRepository<SavingsTxnDenomination, Long> {
|
||||
List<SavingsTxnDenomination> findByTxnId(Long txnId);
|
||||
List<SavingsTxnDenomination> findBySavingsAccountId(Long savingsAccountId);
|
||||
}
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.denominationmaster.service;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.denominationmaster.domain.SavingsTxnDenomination;
|
||||
import org.apache.fineract.portfolio.denominationmaster.domain.SavingsTxnDenominationRepository;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Validates and persists the per-transaction cash denomination breakdown sent by the UI
|
||||
* on both deposits and withdrawals.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DenominationBreakdownWriter {
|
||||
|
||||
private final SavingsTxnDenominationRepository repository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Full validation of the denomination breakdown:
|
||||
* <ol>
|
||||
* <li>Each item must have a non-null {@code denominationId}.</li>
|
||||
* <li>Each item's {@code count} must be > 0.</li>
|
||||
* <li>Each item's {@code denominationValue} (or {@code denomination}) must be > 0.</li>
|
||||
* <li>The grand total (sum of value × count) must equal {@code expectedAmount}.</li>
|
||||
* </ol>
|
||||
* No-op when no breakdown is provided.
|
||||
*
|
||||
* @param command the JSON command containing the denominationBreakdown array
|
||||
* @param expectedAmount the transaction amount that the breakdown must sum to
|
||||
* @param txnType "DEPOSIT" or "WITHDRAWAL" — used in error messages
|
||||
*/
|
||||
public void validateBreakdown(final JsonCommand command, final BigDecimal expectedAmount, final String txnType) {
|
||||
if (command == null || !command.parameterExists("denominationBreakdown")) {
|
||||
return;
|
||||
}
|
||||
final JsonArray breakdown = command.arrayOfParameterNamed("denominationBreakdown");
|
||||
if (breakdown == null || breakdown.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
final DataValidatorBuilder validator = new DataValidatorBuilder(errors).resource("denominationBreakdown");
|
||||
BigDecimal computedTotal = BigDecimal.ZERO;
|
||||
final String label = (txnType != null && !txnType.isBlank()) ? txnType.toUpperCase() : "TRANSACTION";
|
||||
|
||||
for (int i = 0; i < breakdown.size(); i++) {
|
||||
final JsonObject obj = breakdown.get(i).getAsJsonObject();
|
||||
final String itemPrefix = "denominationBreakdown[" + i + "]";
|
||||
|
||||
// 1. denominationId is required
|
||||
if (!obj.has("denominationId") || obj.get("denominationId").isJsonNull()) {
|
||||
validator.reset().parameter(itemPrefix + ".denominationId")
|
||||
.value(null)
|
||||
.failWithCode("required", "denominationId is required for each denomination item.");
|
||||
continue; // cannot compute further without ID
|
||||
}
|
||||
final long denominationId = obj.get("denominationId").getAsLong();
|
||||
if (denominationId <= 0) {
|
||||
validator.reset().parameter(itemPrefix + ".denominationId")
|
||||
.value(denominationId)
|
||||
.failWithCode("invalid", "denominationId must be a positive number.");
|
||||
}
|
||||
|
||||
// 2. count is required and must be > 0
|
||||
if (!obj.has("count") || obj.get("count").isJsonNull()) {
|
||||
validator.reset().parameter(itemPrefix + ".count")
|
||||
.value(null)
|
||||
.failWithCode("required", "count is required for each denomination item.");
|
||||
continue;
|
||||
}
|
||||
final int count = obj.get("count").getAsInt();
|
||||
if (count <= 0) {
|
||||
// count = 0 is silently skipped (UI may send zero for unselected rows)
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. denominationValue (or denomination) must be present and > 0
|
||||
int denominationValue = 0;
|
||||
if (obj.has("denominationValue") && !obj.get("denominationValue").isJsonNull()) {
|
||||
denominationValue = obj.get("denominationValue").getAsInt();
|
||||
} else if (obj.has("denomination") && !obj.get("denomination").isJsonNull()) {
|
||||
denominationValue = obj.get("denomination").getAsInt();
|
||||
}
|
||||
if (denominationValue <= 0) {
|
||||
validator.reset().parameter(itemPrefix + ".denominationValue")
|
||||
.value(denominationValue)
|
||||
.failWithCode("invalid",
|
||||
"denominationValue must be greater than zero for denomination item " + i + ".");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. Accumulate total: value × count
|
||||
computedTotal = computedTotal.add(BigDecimal.valueOf((long) denominationValue * count));
|
||||
}
|
||||
|
||||
// Throw item-level validation errors first
|
||||
if (!errors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||
"Denomination breakdown validation errors exist.", errors);
|
||||
}
|
||||
|
||||
// 5. Grand total must match the transaction amount
|
||||
if (expectedAmount != null && computedTotal.compareTo(expectedAmount) != 0) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.denomination.total.mismatch",
|
||||
label + " denomination breakdown total (" + computedTotal
|
||||
+ ") does not match the transaction amount (" + expectedAmount
|
||||
+ "). Please re-check the note/coin counts.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience wrapper kept for backward-compatibility with any callers that do not
|
||||
* yet pass a {@code txnType}. Delegates to {@link #validateBreakdown}.
|
||||
*/
|
||||
public void validateTotal(final JsonCommand command, final BigDecimal expectedAmount) {
|
||||
validateBreakdown(command, expectedAmount, "TRANSACTION");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the cashier currently holds enough of each denomination
|
||||
* in the withdrawal breakdown. Auto-detects the cashier from the authenticated
|
||||
* user. If the user is not linked to a cashier (e.g. admin/back-office), the
|
||||
* check is skipped silently.
|
||||
*
|
||||
* @param command the JSON command containing the denominationBreakdown array
|
||||
*/
|
||||
public void validateWithdrawalStock(final JsonCommand command) {
|
||||
if (command == null || !command.parameterExists("denominationBreakdown")) return;
|
||||
final JsonArray breakdown = command.arrayOfParameterNamed("denominationBreakdown");
|
||||
if (breakdown == null || breakdown.size() == 0) return;
|
||||
|
||||
Long cashierId = null;
|
||||
try {
|
||||
final AppUser user = context.authenticatedUser();
|
||||
cashierId = jdbcTemplate.queryForObject(
|
||||
"SELECT mc.id FROM m_cashiers mc JOIN m_staff ms ON ms.id = mc.staff_id "
|
||||
+ "JOIN m_appuser au ON au.staff_id = ms.id WHERE au.id = ?",
|
||||
Long.class, user.getId());
|
||||
} catch (Exception e) {
|
||||
return; // not a cashier user — skip stock check
|
||||
}
|
||||
if (cashierId == null) return;
|
||||
|
||||
final Map<Long, Long> stock = getCashierNetPosition(cashierId);
|
||||
final List<String> shortages = new ArrayList<>();
|
||||
|
||||
for (final JsonElement el : breakdown) {
|
||||
final JsonObject item = el.getAsJsonObject();
|
||||
final int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count <= 0) continue;
|
||||
if (!item.has("denominationId") || item.get("denominationId").isJsonNull()) continue;
|
||||
|
||||
final long denomId = item.get("denominationId").getAsLong();
|
||||
final long available = stock.getOrDefault(denomId, 0L);
|
||||
if (count > available) {
|
||||
final int denomValue = item.has("denominationValue") && !item.get("denominationValue").isJsonNull()
|
||||
? item.get("denominationValue").getAsInt() : 0;
|
||||
shortages.add("₹" + denomValue + " (need " + count + ", available " + available + ")");
|
||||
}
|
||||
}
|
||||
|
||||
if (!shortages.isEmpty()) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.cashier.denomination.insufficient",
|
||||
"Insufficient denomination stock for withdrawal: " + String.join("; ", shortages));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the cashier's current net denomination-wise stock by summing all IN
|
||||
* transactions and subtracting all OUT transactions for that cashier.
|
||||
*
|
||||
* @param cashierId the cashier whose position to compute
|
||||
* @return map of denominationId → net count available
|
||||
*/
|
||||
public Map<Long, Long> getCashierNetPosition(final Long cashierId) {
|
||||
final String sql = "SELECT denomination_id, SUM(net_count) AS available FROM ("
|
||||
+ " SELECT ctd.denomination_id, CAST(SUM(ctd.count) AS BIGINT) AS net_count"
|
||||
+ " FROM m_cash_transfer_detail ctd JOIN m_cash_transfer ct ON ct.id = ctd.transfer_id"
|
||||
+ " WHERE ct.to_cashier_id = ? AND (ct.is_reversed IS NULL OR ct.is_reversed = false)"
|
||||
+ " GROUP BY ctd.denomination_id"
|
||||
+ " UNION ALL"
|
||||
+ " SELECT icd.denomination_id, CAST(SUM(icd.count) AS BIGINT)"
|
||||
+ " FROM m_cash_inter_cashier_detail icd JOIN m_cash_inter_cashier ic ON ic.id = icd.inter_cashier_id"
|
||||
+ " WHERE ic.to_cashier_id = ? AND (ic.is_reversed IS NULL OR ic.is_reversed = false)"
|
||||
+ " GROUP BY icd.denomination_id"
|
||||
+ " UNION ALL"
|
||||
+ " SELECT std.denomination_id, CAST(SUM(std.count) AS BIGINT)"
|
||||
+ " FROM m_savings_txn_denomination std"
|
||||
+ " WHERE std.txn_type = 'DEPOSIT'"
|
||||
+ " AND std.officer_id IN (SELECT au.id FROM m_appuser au"
|
||||
+ " JOIN m_cashiers mc ON mc.staff_id = au.staff_id WHERE mc.id = ?)"
|
||||
+ " GROUP BY std.denomination_id"
|
||||
+ " UNION ALL"
|
||||
+ " SELECT ced.denomination_id, CAST(SUM(ced.count) AS BIGINT)"
|
||||
+ " FROM m_cash_exchange_detail ced JOIN m_cash_exchange ce ON ce.id = ced.exchange_id"
|
||||
+ " WHERE ce.cashier_id = ? AND ced.direction = 'RECEIVED'"
|
||||
+ " AND (ce.is_reversed IS NULL OR ce.is_reversed = false)"
|
||||
+ " GROUP BY ced.denomination_id"
|
||||
+ " UNION ALL"
|
||||
+ " SELECT csd.denomination_id, CAST(-SUM(csd.count) AS BIGINT)"
|
||||
+ " FROM m_cash_settlement_detail csd JOIN m_cash_settlement cs ON cs.id = csd.settlement_id"
|
||||
+ " WHERE cs.from_cashier_id = ? AND (cs.is_reversed IS NULL OR cs.is_reversed = false)"
|
||||
+ " GROUP BY csd.denomination_id"
|
||||
+ " UNION ALL"
|
||||
+ " SELECT icd.denomination_id, CAST(-SUM(icd.count) AS BIGINT)"
|
||||
+ " FROM m_cash_inter_cashier_detail icd JOIN m_cash_inter_cashier ic ON ic.id = icd.inter_cashier_id"
|
||||
+ " WHERE ic.from_cashier_id = ? AND (ic.is_reversed IS NULL OR ic.is_reversed = false)"
|
||||
+ " GROUP BY icd.denomination_id"
|
||||
+ " UNION ALL"
|
||||
+ " SELECT std.denomination_id, CAST(-SUM(std.count) AS BIGINT)"
|
||||
+ " FROM m_savings_txn_denomination std"
|
||||
+ " WHERE std.txn_type = 'WITHDRAWAL'"
|
||||
+ " AND std.officer_id IN (SELECT au.id FROM m_appuser au"
|
||||
+ " JOIN m_cashiers mc ON mc.staff_id = au.staff_id WHERE mc.id = ?)"
|
||||
+ " GROUP BY std.denomination_id"
|
||||
+ " UNION ALL"
|
||||
+ " SELECT ced.denomination_id, CAST(-SUM(ced.count) AS BIGINT)"
|
||||
+ " FROM m_cash_exchange_detail ced JOIN m_cash_exchange ce ON ce.id = ced.exchange_id"
|
||||
+ " WHERE ce.cashier_id = ? AND ced.direction = 'GIVEN'"
|
||||
+ " AND (ce.is_reversed IS NULL OR ce.is_reversed = false)"
|
||||
+ " GROUP BY ced.denomination_id"
|
||||
+ ") t GROUP BY denomination_id";
|
||||
|
||||
final Map<Long, Long> stock = new HashMap<>();
|
||||
jdbcTemplate.query(sql, rs -> {
|
||||
stock.put(rs.getLong("denomination_id"), rs.getLong("available"));
|
||||
}, cashierId, cashierId, cashierId, cashierId, cashierId, cashierId, cashierId, cashierId);
|
||||
return stock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist each valid denomination row for the given transaction.
|
||||
* <p>
|
||||
* Items with {@code count <= 0} are silently skipped (UI sends zero for
|
||||
* unselected rows). Items missing {@code denominationId} are also skipped
|
||||
* (they should have already been rejected by {@link #validateBreakdown}).
|
||||
* <p>
|
||||
* The logged-in officer (teller) is automatically resolved from the
|
||||
* security context; if resolution fails (e.g. batch job) the officer fields
|
||||
* are left {@code null}.
|
||||
*
|
||||
* @param command the JSON command containing the denominationBreakdown array
|
||||
* @param txnId ID of the saved savings / current / RD / FD transaction
|
||||
* @param savingsAccountId savings / current / RD / FD account ID
|
||||
* @param accountNo account number string
|
||||
* @param clientId client ID (may be null for group accounts)
|
||||
* @param cifNo CIF number (may be null)
|
||||
* @param txnDate transaction date
|
||||
* @param txnType "DEPOSIT" or "WITHDRAWAL"
|
||||
*/
|
||||
public void save(final JsonCommand command, final Long txnId, final Long savingsAccountId, final String accountNo,
|
||||
final Long clientId, final String cifNo, final LocalDate txnDate, final String txnType) {
|
||||
|
||||
if (command == null || txnId == null || !command.parameterExists("denominationBreakdown")) {
|
||||
return;
|
||||
}
|
||||
final JsonArray breakdown = command.arrayOfParameterNamed("denominationBreakdown");
|
||||
if (breakdown == null || breakdown.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve the officer / teller from the security context
|
||||
Long officerId = null;
|
||||
String officerUsername = null;
|
||||
try {
|
||||
final AppUser user = this.context.authenticatedUser();
|
||||
if (user != null) {
|
||||
officerId = user.getId();
|
||||
officerUsername = user.getUsername();
|
||||
}
|
||||
} catch (final Exception ignored) {
|
||||
// If user cannot be resolved (e.g. batch / async job), leave officer fields null
|
||||
}
|
||||
|
||||
final String resolvedTxnType = (txnType != null && !txnType.isBlank()) ? txnType.toUpperCase() : "DEPOSIT";
|
||||
|
||||
for (final JsonElement entry : breakdown) {
|
||||
final JsonObject obj = entry.getAsJsonObject();
|
||||
|
||||
// denominationId is mandatory (NOT NULL in DB)
|
||||
if (!obj.has("denominationId") || obj.get("denominationId").isJsonNull() || !obj.has("count")) {
|
||||
continue;
|
||||
}
|
||||
final int count = obj.get("count").getAsInt();
|
||||
if (count <= 0) {
|
||||
continue; // zero-count rows sent by UI are silently ignored
|
||||
}
|
||||
|
||||
final long denominationId = obj.get("denominationId").getAsLong();
|
||||
|
||||
// Resolve denomination face value
|
||||
int denominationValue = 0;
|
||||
if (obj.has("denominationValue") && !obj.get("denominationValue").isJsonNull()) {
|
||||
denominationValue = obj.get("denominationValue").getAsInt();
|
||||
} else if (obj.has("denomination") && !obj.get("denomination").isJsonNull()) {
|
||||
denominationValue = obj.get("denomination").getAsInt();
|
||||
}
|
||||
|
||||
final BigDecimal lineTotal = BigDecimal.valueOf((long) denominationValue * count);
|
||||
|
||||
final SavingsTxnDenomination row = SavingsTxnDenomination.builder()
|
||||
.txnId(txnId)
|
||||
.savingsAccountId(savingsAccountId)
|
||||
.accountNo(accountNo)
|
||||
.clientId(clientId)
|
||||
.cifNo(cifNo)
|
||||
.denominationId(denominationId)
|
||||
.denominationValue(denominationValue)
|
||||
.count(count)
|
||||
.totalAmount(lineTotal)
|
||||
.txnDate(txnDate)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.txnType(resolvedTxnType)
|
||||
.officerId(officerId)
|
||||
.officerUsername(officerUsername)
|
||||
.build();
|
||||
|
||||
this.repository.save(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
+257
-257
@@ -1,257 +1,257 @@
|
||||
/**
|
||||
* 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.savings.data;
|
||||
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.activatedOnDateParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.bankNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.checkNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.closedOnDateParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.paymentTypeIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.receiptNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.routingCodeParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.toSavingsAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionAccountNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionAmountParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionDateParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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.api.JsonCommand;
|
||||
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.FromJsonHelper;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DepositAccountTransactionDataValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
|
||||
DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, transactionDateParamName,
|
||||
transactionAmountParamName, paymentTypeIdParamName, transactionAccountNumberParamName, checkNumberParamName,
|
||||
routingCodeParamName, receiptNumberParamName, bankNumberParamName, DepositsApiConstants.amountParamName,
|
||||
DepositsApiConstants.accountIdParamName, DepositsApiConstants.dateParamName, DepositsApiConstants.submittedOnDateParamName,
|
||||
DepositsApiConstants.lienTransaction, DepositsApiConstants.isManualTransaction, DepositsApiConstants.chargesPaidByData,
|
||||
DepositsApiConstants.accountNoParamName, DepositsApiConstants.noteParamName));
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_RECOMMENDED_DEPOSIT_AMOUNT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName,
|
||||
DepositsApiConstants.mandatoryRecommendedDepositAmountParamName, DepositsApiConstants.effectiveDateParamName));
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName,
|
||||
DepositsApiConstants.noteParamName, onAccountClosureIdParamName, paymentTypeIdParamName,
|
||||
transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName,
|
||||
bankNumberParamName, DepositsApiConstants.transferDescriptionParamName, toSavingsAccountIdParamName));
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName));
|
||||
|
||||
@Autowired
|
||||
public DepositAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validate(final JsonCommand command, DepositAccountType depositAccountType) {
|
||||
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, DEPOSIT_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(depositAccountType.resourceName());
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element);
|
||||
baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull();
|
||||
|
||||
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transactionAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(transactionAmountParamName).value(transactionAmount).notNull().positiveAmount();
|
||||
|
||||
// Validate all string payment detail fields for max length
|
||||
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
|
||||
final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName,
|
||||
routingCodeParamName, receiptNumberParamName, bankNumberParamName));
|
||||
for (final String paymentDetailParameterName : paymentDetailParameters) {
|
||||
final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
|
||||
baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
|
||||
.notExceedingLengthOf(50);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateDepositAmountUpdate(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
DEPOSIT_ACCOUNT_RECOMMENDED_DEPOSIT_AMOUNT_UPDATE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate effectiveDate = this.fromApiJsonHelper.extractLocalDateNamed(DepositsApiConstants.effectiveDateParamName, element);
|
||||
baseDataValidator.reset().parameter(DepositsApiConstants.effectiveDateParamName).value(effectiveDate).notNull();
|
||||
|
||||
final BigDecimal mandatoryRecommendedDepositAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(DepositsApiConstants.mandatoryRecommendedDepositAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(DepositsApiConstants.mandatoryRecommendedDepositAmountParamName)
|
||||
.value(mandatoryRecommendedDepositAmount).notNull().positiveAmount();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateActivation(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
SavingsAccountConstant.SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(activatedOnDateParamName, element);
|
||||
baseDataValidator.reset().parameter(activatedOnDateParamName).value(activationDate).notNull();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validatePreMatureAmountCalculation(final String json, final DepositAccountType depositAccountType) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(depositAccountType.resourceName());
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final LocalDate closeDate = this.fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
|
||||
baseDataValidator.reset().parameter(closedOnDateParamName).value(closeDate).notNull();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateClosing(final JsonCommand command, DepositAccountType depositAccountType, final boolean isPreMatureClose) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, DEPOSIT_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(depositAccountType.resourceName());
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
|
||||
baseDataValidator.reset().parameter(closedOnDateParamName).value(activationDate).notNull();
|
||||
|
||||
final Integer onAccountClosureId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(onAccountClosureIdParamName, element);
|
||||
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId).notBlank()
|
||||
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
||||
|
||||
if (onAccountClosureId != null) {
|
||||
final DepositAccountOnClosureType accountOnClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
|
||||
if (accountOnClosureType.isTransferToSavings()) {
|
||||
final Long toSavingsAccountId = this.fromApiJsonHelper.extractLongNamed(toSavingsAccountIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toSavingsAccountIdParamName).value(toSavingsAccountId)
|
||||
.cantBeBlankWhenParameterProvidedIs(onAccountClosureIdParamName,
|
||||
DepositAccountOnClosureType.fromInt(onAccountClosureId).getCode());
|
||||
} else if (accountOnClosureType.isReinvest() && isPreMatureClose) {
|
||||
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId)
|
||||
.failWithCode("reinvest.not.allowed", "Re-Invest is not supported for account pre mature close");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate all string payment detail fields for max length
|
||||
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
|
||||
final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName,
|
||||
routingCodeParamName, receiptNumberParamName, bankNumberParamName));
|
||||
for (final String paymentDetailParameterName : paymentDetailParameters) {
|
||||
final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
|
||||
baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
|
||||
.notExceedingLengthOf(50);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
//
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.savings.data;
|
||||
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.activatedOnDateParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.bankNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.checkNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.closedOnDateParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.paymentTypeIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.receiptNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.routingCodeParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.toSavingsAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionAccountNumberParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionAmountParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transactionDateParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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.api.JsonCommand;
|
||||
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.FromJsonHelper;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DepositAccountTransactionDataValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
|
||||
DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, transactionDateParamName,
|
||||
transactionAmountParamName, paymentTypeIdParamName, transactionAccountNumberParamName, checkNumberParamName,
|
||||
routingCodeParamName, receiptNumberParamName, bankNumberParamName, DepositsApiConstants.amountParamName,
|
||||
DepositsApiConstants.accountIdParamName, DepositsApiConstants.dateParamName, DepositsApiConstants.submittedOnDateParamName,
|
||||
DepositsApiConstants.lienTransaction, DepositsApiConstants.isManualTransaction, DepositsApiConstants.chargesPaidByData,
|
||||
DepositsApiConstants.accountNoParamName, DepositsApiConstants.noteParamName, "denominationBreakdown"));
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_RECOMMENDED_DEPOSIT_AMOUNT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName,
|
||||
DepositsApiConstants.mandatoryRecommendedDepositAmountParamName, DepositsApiConstants.effectiveDateParamName));
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName,
|
||||
DepositsApiConstants.noteParamName, onAccountClosureIdParamName, paymentTypeIdParamName,
|
||||
transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName,
|
||||
bankNumberParamName, DepositsApiConstants.transferDescriptionParamName, toSavingsAccountIdParamName));
|
||||
|
||||
private static final Set<String> DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName));
|
||||
|
||||
@Autowired
|
||||
public DepositAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validate(final JsonCommand command, DepositAccountType depositAccountType) {
|
||||
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, DEPOSIT_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(depositAccountType.resourceName());
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element);
|
||||
baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull();
|
||||
|
||||
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transactionAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(transactionAmountParamName).value(transactionAmount).notNull().positiveAmount();
|
||||
|
||||
// Validate all string payment detail fields for max length
|
||||
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
|
||||
final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName,
|
||||
routingCodeParamName, receiptNumberParamName, bankNumberParamName));
|
||||
for (final String paymentDetailParameterName : paymentDetailParameters) {
|
||||
final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
|
||||
baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
|
||||
.notExceedingLengthOf(50);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateDepositAmountUpdate(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
DEPOSIT_ACCOUNT_RECOMMENDED_DEPOSIT_AMOUNT_UPDATE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate effectiveDate = this.fromApiJsonHelper.extractLocalDateNamed(DepositsApiConstants.effectiveDateParamName, element);
|
||||
baseDataValidator.reset().parameter(DepositsApiConstants.effectiveDateParamName).value(effectiveDate).notNull();
|
||||
|
||||
final BigDecimal mandatoryRecommendedDepositAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(DepositsApiConstants.mandatoryRecommendedDepositAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(DepositsApiConstants.mandatoryRecommendedDepositAmountParamName)
|
||||
.value(mandatoryRecommendedDepositAmount).notNull().positiveAmount();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateActivation(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
SavingsAccountConstant.SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(activatedOnDateParamName, element);
|
||||
baseDataValidator.reset().parameter(activatedOnDateParamName).value(activationDate).notNull();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validatePreMatureAmountCalculation(final String json, final DepositAccountType depositAccountType) {
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||
DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(depositAccountType.resourceName());
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final LocalDate closeDate = this.fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
|
||||
baseDataValidator.reset().parameter(closedOnDateParamName).value(closeDate).notNull();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateClosing(final JsonCommand command, DepositAccountType depositAccountType, final boolean isPreMatureClose) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, DEPOSIT_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(depositAccountType.resourceName());
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final LocalDate activationDate = this.fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
|
||||
baseDataValidator.reset().parameter(closedOnDateParamName).value(activationDate).notNull();
|
||||
|
||||
final Integer onAccountClosureId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(onAccountClosureIdParamName, element);
|
||||
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId).notBlank()
|
||||
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
||||
|
||||
if (onAccountClosureId != null) {
|
||||
final DepositAccountOnClosureType accountOnClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
|
||||
if (accountOnClosureType.isTransferToSavings()) {
|
||||
final Long toSavingsAccountId = this.fromApiJsonHelper.extractLongNamed(toSavingsAccountIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toSavingsAccountIdParamName).value(toSavingsAccountId)
|
||||
.cantBeBlankWhenParameterProvidedIs(onAccountClosureIdParamName,
|
||||
DepositAccountOnClosureType.fromInt(onAccountClosureId).getCode());
|
||||
} else if (accountOnClosureType.isReinvest() && isPreMatureClose) {
|
||||
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId)
|
||||
.failWithCode("reinvest.not.allowed", "Re-Invest is not supported for account pre mature close");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate all string payment detail fields for max length
|
||||
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
|
||||
final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName,
|
||||
routingCodeParamName, receiptNumberParamName, bankNumberParamName));
|
||||
for (final String paymentDetailParameterName : paymentDetailParameters) {
|
||||
final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
|
||||
baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
|
||||
.notExceedingLengthOf(50);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
//
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -31,6 +31,8 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.deposit
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.expectedFirstDepositOnDateParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isCalendarInheritedParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isMandatoryDepositParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isSeniorCitizenParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.seniorCitizenInterestRateParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.mandatoryRecommendedDepositAmountParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maturityInstructionIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferInterestToSavingsParamName;
|
||||
@@ -230,6 +232,16 @@ public class DepositAccountAssembler {
|
||||
interestRate = product.nominalAnnualInterestRate();
|
||||
}
|
||||
|
||||
// Senior citizen interest rate override: use the higher rate when applicable
|
||||
if (command.parameterExists(isSeniorCitizenParamName) && command.booleanPrimitiveValueOfParameterNamed(isSeniorCitizenParamName)) {
|
||||
if (command.parameterExists(seniorCitizenInterestRateParamName)) {
|
||||
final BigDecimal scRate = command.bigDecimalValueOfParameterNamed(seniorCitizenInterestRateParamName);
|
||||
if (scRate != null && scRate.compareTo(BigDecimal.ZERO) > 0) {
|
||||
interestRate = scRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SavingsCompoundingInterestPeriodType interestCompoundingPeriodType = null;
|
||||
final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName);
|
||||
if (interestPeriodTypeValue != null) {
|
||||
|
||||
+75
-75
@@ -1,75 +1,75 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_fd_base_interest_rate_effective")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class FdBaseInterestRateEffective {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "fd_type", length = 20)
|
||||
private String fdType;
|
||||
|
||||
@Column(name = "version_label")
|
||||
private String versionLabel;
|
||||
|
||||
@Column(name = "effective_from_date")
|
||||
private LocalDate effectiveFromDate;
|
||||
|
||||
@Column(name = "effective_to_date")
|
||||
private LocalDate effectiveToDate;
|
||||
|
||||
@Column(name = "is_active")
|
||||
private Boolean isActive;
|
||||
|
||||
@Column(name = "remarks")
|
||||
private String remarks;
|
||||
|
||||
@Column(name = "created_by_id")
|
||||
private Long createdById;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private LocalDate createdDate;
|
||||
|
||||
public static FdBaseInterestRateEffective create(String fdType, String versionLabel, LocalDate effectiveFromDate, String remarks,
|
||||
Long createdById) {
|
||||
FdBaseInterestRateEffective e = new FdBaseInterestRateEffective();
|
||||
e.fdType = fdType;
|
||||
e.versionLabel = versionLabel;
|
||||
e.effectiveFromDate = effectiveFromDate;
|
||||
e.isActive = true;
|
||||
e.remarks = remarks;
|
||||
e.createdById = createdById;
|
||||
e.createdDate = LocalDate.now();
|
||||
return e;
|
||||
}
|
||||
|
||||
public void deactivate(LocalDate toDate) {
|
||||
this.isActive = false;
|
||||
this.effectiveToDate = toDate;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cbs_base_interest_rate_effective")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class FdBaseInterestRateEffective {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "fd_type", length = 20)
|
||||
private String fdType;
|
||||
|
||||
@Column(name = "version_label")
|
||||
private String versionLabel;
|
||||
|
||||
@Column(name = "effective_from_date")
|
||||
private LocalDate effectiveFromDate;
|
||||
|
||||
@Column(name = "effective_to_date")
|
||||
private LocalDate effectiveToDate;
|
||||
|
||||
@Column(name = "is_active")
|
||||
private Boolean isActive;
|
||||
|
||||
@Column(name = "remarks")
|
||||
private String remarks;
|
||||
|
||||
@Column(name = "created_by_id")
|
||||
private Long createdById;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private LocalDate createdDate;
|
||||
|
||||
public static FdBaseInterestRateEffective create(String fdType, String versionLabel, LocalDate effectiveFromDate, String remarks,
|
||||
Long createdById) {
|
||||
FdBaseInterestRateEffective e = new FdBaseInterestRateEffective();
|
||||
e.fdType = fdType;
|
||||
e.versionLabel = versionLabel;
|
||||
e.effectiveFromDate = effectiveFromDate;
|
||||
e.isActive = true;
|
||||
e.remarks = remarks;
|
||||
e.createdById = createdById;
|
||||
e.createdDate = LocalDate.now();
|
||||
return e;
|
||||
}
|
||||
|
||||
public void deactivate(LocalDate toDate) {
|
||||
this.isActive = false;
|
||||
this.effectiveToDate = toDate;
|
||||
}
|
||||
}
|
||||
|
||||
+76
-76
@@ -1,76 +1,76 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_fd_base_interest_rate_slab_history")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class FdBaseInterestRateSlabHistory {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "effective_id")
|
||||
private Long effectiveId;
|
||||
|
||||
@Column(name = "sr_no")
|
||||
private Integer srNo;
|
||||
|
||||
@Column(name = "description")
|
||||
private String description;
|
||||
|
||||
@Column(name = "period_type_enum")
|
||||
private Integer periodTypeEnum;
|
||||
|
||||
@Column(name = "from_period")
|
||||
private Integer fromPeriod;
|
||||
|
||||
@Column(name = "to_period")
|
||||
private Integer toPeriod;
|
||||
|
||||
@Column(name = "annual_interest_rate")
|
||||
private BigDecimal annualInterestRate;
|
||||
|
||||
@Column(name = "senior_citizen_rate")
|
||||
private BigDecimal seniorCitizenRate;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private LocalDate createdDate;
|
||||
|
||||
public static FdBaseInterestRateSlabHistory create(Long effectiveId, Integer srNo, String description, Integer periodTypeEnum,
|
||||
Integer fromPeriod, Integer toPeriod, BigDecimal annualInterestRate, BigDecimal seniorCitizenRate) {
|
||||
FdBaseInterestRateSlabHistory s = new FdBaseInterestRateSlabHistory();
|
||||
s.effectiveId = effectiveId;
|
||||
s.srNo = srNo;
|
||||
s.description = description;
|
||||
s.periodTypeEnum = periodTypeEnum != null ? periodTypeEnum : 1;
|
||||
s.fromPeriod = fromPeriod;
|
||||
s.toPeriod = toPeriod;
|
||||
s.annualInterestRate = annualInterestRate;
|
||||
s.seniorCitizenRate = seniorCitizenRate;
|
||||
s.createdDate = LocalDate.now();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cbs_base_interest_rate_slab_history")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class FdBaseInterestRateSlabHistory {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "effective_id")
|
||||
private Long effectiveId;
|
||||
|
||||
@Column(name = "sr_no")
|
||||
private Integer srNo;
|
||||
|
||||
@Column(name = "description")
|
||||
private String description;
|
||||
|
||||
@Column(name = "period_type_enum")
|
||||
private Integer periodTypeEnum;
|
||||
|
||||
@Column(name = "from_period")
|
||||
private Integer fromPeriod;
|
||||
|
||||
@Column(name = "to_period")
|
||||
private Integer toPeriod;
|
||||
|
||||
@Column(name = "annual_interest_rate")
|
||||
private BigDecimal annualInterestRate;
|
||||
|
||||
@Column(name = "senior_citizen_rate")
|
||||
private BigDecimal seniorCitizenRate;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private LocalDate createdDate;
|
||||
|
||||
public static FdBaseInterestRateSlabHistory create(Long effectiveId, Integer srNo, String description, Integer periodTypeEnum,
|
||||
Integer fromPeriod, Integer toPeriod, BigDecimal annualInterestRate, BigDecimal seniorCitizenRate) {
|
||||
FdBaseInterestRateSlabHistory s = new FdBaseInterestRateSlabHistory();
|
||||
s.effectiveId = effectiveId;
|
||||
s.srNo = srNo;
|
||||
s.description = description;
|
||||
s.periodTypeEnum = periodTypeEnum != null ? periodTypeEnum : 1;
|
||||
s.fromPeriod = fromPeriod;
|
||||
s.toPeriod = toPeriod;
|
||||
s.annualInterestRate = annualInterestRate;
|
||||
s.seniorCitizenRate = seniorCitizenRate;
|
||||
s.createdDate = LocalDate.now();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
+910
-894
File diff suppressed because it is too large
Load Diff
+1285
-1269
File diff suppressed because it is too large
Load Diff
+641
-623
File diff suppressed because it is too large
Load Diff
+356
-333
@@ -1,333 +1,356 @@
|
||||
/**
|
||||
* 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.savings.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsDepositBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsWithdrawalBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
|
||||
import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
|
||||
import org.apache.fineract.portfolio.savings.exception.DepositAccountTransactionNotAllowedException;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainService {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final SavingsAccountRepositoryWrapper savingsAccountRepository;
|
||||
private final SavingsAccountTransactionRepository savingsAccountTransactionRepository;
|
||||
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
|
||||
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
|
||||
@Autowired
|
||||
public SavingsAccountDomainServiceJpa(final SavingsAccountRepositoryWrapper savingsAccountRepository,
|
||||
final SavingsAccountTransactionRepository savingsAccountTransactionRepository,
|
||||
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
|
||||
final JournalEntryWritePlatformService journalEntryWritePlatformService,
|
||||
final ConfigurationDomainService configurationDomainService, final PlatformSecurityContext context,
|
||||
final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository,
|
||||
final BusinessEventNotifierService businessEventNotifierService) {
|
||||
this.savingsAccountRepository = savingsAccountRepository;
|
||||
this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
|
||||
this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
|
||||
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
|
||||
this.configurationDomainService = configurationDomainService;
|
||||
this.context = context;
|
||||
this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository;
|
||||
this.businessEventNotifierService = businessEventNotifierService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt,
|
||||
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
|
||||
final SavingsTransactionBooleanValues transactionBooleanValues, final boolean backdatedTxnsAllowedTill) {
|
||||
context.authenticatedUser();
|
||||
account.validateForAccountBlock();
|
||||
account.validateForDebitBlock();
|
||||
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
|
||||
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||
final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate();
|
||||
final boolean postReversals = this.configurationDomainService.isReversalTransactionAllowed();
|
||||
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||
if (transactionBooleanValues.isRegularTransaction() && !account.allowWithdrawal()) {
|
||||
throw new DepositAccountTransactionNotAllowedException(account.getId(), "withdraw", account.depositAccountType());
|
||||
}
|
||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||
final LocalDate postInterestOnDate = null;
|
||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
} else {
|
||||
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
|
||||
Integer accountType = null;
|
||||
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
|
||||
paymentDetail, null, accountType);
|
||||
UUID refNo = UUID.randomUUID();
|
||||
final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, transactionBooleanValues.isApplyWithdrawFee(),
|
||||
backdatedTxnsAllowedTill, relaxingDaysConfigForPivotDate, refNo.toString());
|
||||
final MathContext mc = MathContext.DECIMAL64;
|
||||
|
||||
final LocalDate today = DateUtils.getBusinessLocalDate();
|
||||
|
||||
if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) {
|
||||
account.postInterest(mc, today, transactionBooleanValues.isInterestTransfer(), isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals);
|
||||
} else {
|
||||
account.calculateInterestUsing(mc, today, transactionBooleanValues.isInterestTransfer(),
|
||||
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill,
|
||||
postReversals);
|
||||
}
|
||||
|
||||
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
|
||||
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
|
||||
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
|
||||
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
|
||||
}
|
||||
|
||||
account.validateAccountBalanceDoesNotBecomeNegative(transactionAmount, transactionBooleanValues.isExceptionForBalanceCheck(),
|
||||
depositAccountOnHoldTransactions, backdatedTxnsAllowedTill);
|
||||
|
||||
saveTransactionToGenerateTransactionId(withdrawal);
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
// Update transactions separately
|
||||
saveUpdatedTransactionsOfSavingsAccount(account.getSavingsAccountTransactionsWithPivotConfig());
|
||||
}
|
||||
this.savingsAccountRepository.save(account);
|
||||
|
||||
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, transactionBooleanValues.isAccountTransfer(),
|
||||
backdatedTxnsAllowedTill);
|
||||
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new SavingsWithdrawalBusinessEvent(withdrawal));
|
||||
return withdrawal;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
|
||||
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
|
||||
final boolean isAccountTransfer, final boolean isRegularTransaction, final boolean backdatedTxnsAllowedTill) {
|
||||
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DEPOSIT;
|
||||
return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction,
|
||||
savingsAccountTransactionType, backdatedTxnsAllowedTill);
|
||||
}
|
||||
|
||||
private SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
|
||||
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
|
||||
final boolean isAccountTransfer, final boolean isRegularTransaction,
|
||||
final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill) {
|
||||
context.authenticatedUser();
|
||||
account.validateForAccountBlock();
|
||||
account.validateForCreditBlock();
|
||||
|
||||
// Global configurations
|
||||
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
|
||||
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||
final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate();
|
||||
if (isRegularTransaction && !account.allowDeposit()) {
|
||||
throw new DepositAccountTransactionNotAllowedException(account.getId(), "deposit", account.depositAccountType());
|
||||
}
|
||||
boolean isInterestTransfer = false;
|
||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
} else {
|
||||
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
|
||||
Integer accountType = null;
|
||||
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
|
||||
paymentDetail, null, accountType);
|
||||
UUID refNo = UUID.randomUUID();
|
||||
final SavingsAccountTransaction deposit = account.deposit(transactionDTO, savingsAccountTransactionType, backdatedTxnsAllowedTill,
|
||||
relaxingDaysConfigForPivotDate, refNo.toString());
|
||||
final LocalDate postInterestOnDate = null;
|
||||
final MathContext mc = MathContext.DECIMAL64;
|
||||
|
||||
final LocalDate today = DateUtils.getBusinessLocalDate();
|
||||
boolean postReversals = this.configurationDomainService.isReversalTransactionAllowed();
|
||||
if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) {
|
||||
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
|
||||
postInterestOnDate, backdatedTxnsAllowedTill, postReversals);
|
||||
} else {
|
||||
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals);
|
||||
}
|
||||
|
||||
saveTransactionToGenerateTransactionId(deposit);
|
||||
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
// Update transactions separately
|
||||
saveUpdatedTransactionsOfSavingsAccount(account.getSavingsAccountTransactionsWithPivotConfig());
|
||||
}
|
||||
|
||||
this.savingsAccountRepository.saveAndFlush(account);
|
||||
|
||||
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new SavingsDepositBusinessEvent(deposit));
|
||||
return deposit;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public SavingsAccountTransaction handleHold(final SavingsAccount account, BigDecimal amount, LocalDate transactionDate,
|
||||
Boolean lienAllowed) {
|
||||
return SavingsAccountTransaction.holdAmount(account, account.office(), null, transactionDate,
|
||||
Money.of(account.getCurrency(), amount), lienAllowed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavingsAccountTransaction handleDividendPayout(final SavingsAccount account, final LocalDate transactionDate,
|
||||
final BigDecimal transactionAmount, final boolean backdatedTxnsAllowedTill) {
|
||||
final DateTimeFormatter fmt = null;
|
||||
final PaymentDetail paymentDetail = null;
|
||||
final boolean isAccountTransfer = false;
|
||||
final boolean isRegularTransaction = true;
|
||||
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DIVIDEND_PAYOUT;
|
||||
return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction,
|
||||
savingsAccountTransactionType, backdatedTxnsAllowedTill);
|
||||
}
|
||||
|
||||
private void updateExistingTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds,
|
||||
Set<Long> existingReversedTransactionIds) {
|
||||
existingTransactionIds.addAll(account.findExistingTransactionIds());
|
||||
existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
|
||||
}
|
||||
|
||||
private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) {
|
||||
this.savingsAccountTransactionRepository.saveAndFlush(transaction);
|
||||
return transaction.getId();
|
||||
}
|
||||
|
||||
private void saveUpdatedTransactionsOfSavingsAccount(final List<SavingsAccountTransaction> savingsAccountTransactions) {
|
||||
this.savingsAccountTransactionRepository.saveAll(savingsAccountTransactions);
|
||||
}
|
||||
|
||||
private void updateTransactionDetailsWithPivotConfig(final SavingsAccount account, Set<Long> existingTransactionIds,
|
||||
Set<Long> existingReversedTransactionIds) {
|
||||
existingTransactionIds.addAll(account.findCurrentTransactionIdsWithPivotDateConfig());
|
||||
existingReversedTransactionIds.addAll(account.findCurrentReversedTransactionIdsWithPivotDateConfig());
|
||||
}
|
||||
|
||||
private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
|
||||
final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer, final boolean backdatedTxnsAllowedTill) {
|
||||
|
||||
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(savingsAccount.getCurrency().getCode(),
|
||||
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
|
||||
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void postJournalEntries(final SavingsAccount account, final Set<Long> existingTransactionIds,
|
||||
final Set<Long> existingReversedTransactionIds, final boolean backdatedTxnsAllowedTill) {
|
||||
|
||||
final boolean isAccountTransfer = false;
|
||||
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavingsAccountTransaction handleReversal(SavingsAccount account, List<SavingsAccountTransaction> savingsAccountTransactions,
|
||||
boolean backdatedTxnsAllowedTill) {
|
||||
|
||||
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
|
||||
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||
final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate();
|
||||
final boolean postReversals = true;
|
||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
} else {
|
||||
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
List<SavingsAccountTransaction> newTransactions = new ArrayList<>();
|
||||
SavingsAccountTransaction reversal = null;
|
||||
|
||||
Set<SavingsAccountChargePaidBy> chargePaidBySet = null;
|
||||
for (SavingsAccountTransaction savingsAccountTransaction : savingsAccountTransactions) {
|
||||
reversal = SavingsAccountTransaction.reversal(savingsAccountTransaction);
|
||||
chargePaidBySet = savingsAccountTransaction.getSavingsAccountChargesPaid();
|
||||
reversal.getSavingsAccountChargesPaid().addAll(chargePaidBySet);
|
||||
account.undoTransaction(savingsAccountTransaction);
|
||||
if (postReversals) {
|
||||
newTransactions.add(reversal);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isInterestTransfer = false;
|
||||
LocalDate postInterestOnDate = null;
|
||||
final LocalDate today = DateUtils.getBusinessLocalDate();
|
||||
final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode());
|
||||
for (SavingsAccountTransaction savingsAccountTransaction : savingsAccountTransactions) {
|
||||
if (savingsAccountTransaction.isPostInterestCalculationRequired()
|
||||
&& account.isBeforeLastPostingPeriod(savingsAccountTransaction.getTransactionDate(), backdatedTxnsAllowedTill)) {
|
||||
|
||||
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
|
||||
postInterestOnDate, backdatedTxnsAllowedTill, postReversals);
|
||||
} else {
|
||||
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals);
|
||||
}
|
||||
account.validatePivotDateTransaction(savingsAccountTransaction.getTransactionDate(), backdatedTxnsAllowedTill,
|
||||
relaxingDaysConfigForPivotDate, "savingsaccount");
|
||||
account.validateAccountBalanceDoesNotBecomeNegativeMinimal(savingsAccountTransaction.getAmount(), false);
|
||||
account.activateAccountBasedOnBalance();
|
||||
}
|
||||
this.savingsAccountRepository.save(account);
|
||||
newTransactions.addAll(account.getSavingsAccountTransactionsWithPivotConfig());
|
||||
this.savingsAccountTransactionRepository.saveAll(newTransactions);
|
||||
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false, backdatedTxnsAllowedTill);
|
||||
|
||||
return reversal;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.savings.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsDepositBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsWithdrawalBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
|
||||
import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
|
||||
import org.apache.fineract.portfolio.savings.exception.DepositAccountTransactionNotAllowedException;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainService {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final SavingsAccountRepositoryWrapper savingsAccountRepository;
|
||||
private final SavingsAccountTransactionRepository savingsAccountTransactionRepository;
|
||||
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
|
||||
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
|
||||
@Autowired
|
||||
public SavingsAccountDomainServiceJpa(final SavingsAccountRepositoryWrapper savingsAccountRepository,
|
||||
final SavingsAccountTransactionRepository savingsAccountTransactionRepository,
|
||||
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
|
||||
final JournalEntryWritePlatformService journalEntryWritePlatformService,
|
||||
final ConfigurationDomainService configurationDomainService, final PlatformSecurityContext context,
|
||||
final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository,
|
||||
final BusinessEventNotifierService businessEventNotifierService) {
|
||||
this.savingsAccountRepository = savingsAccountRepository;
|
||||
this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
|
||||
this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
|
||||
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
|
||||
this.configurationDomainService = configurationDomainService;
|
||||
this.context = context;
|
||||
this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository;
|
||||
this.businessEventNotifierService = businessEventNotifierService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt,
|
||||
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
|
||||
final SavingsTransactionBooleanValues transactionBooleanValues, final boolean backdatedTxnsAllowedTill) {
|
||||
context.authenticatedUser();
|
||||
account.validateForAccountBlock();
|
||||
account.validateForDebitBlock();
|
||||
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
|
||||
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||
final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate();
|
||||
final boolean postReversals = this.configurationDomainService.isReversalTransactionAllowed();
|
||||
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||
if (transactionBooleanValues.isRegularTransaction() && !account.allowWithdrawal()) {
|
||||
throw new DepositAccountTransactionNotAllowedException(account.getId(), "withdraw", account.depositAccountType());
|
||||
}
|
||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||
final LocalDate postInterestOnDate = null;
|
||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
} else {
|
||||
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
|
||||
Integer accountType = null;
|
||||
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
|
||||
paymentDetail, null, accountType);
|
||||
UUID refNo = UUID.randomUUID();
|
||||
final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, transactionBooleanValues.isApplyWithdrawFee(),
|
||||
backdatedTxnsAllowedTill, relaxingDaysConfigForPivotDate, refNo.toString());
|
||||
final MathContext mc = MathContext.DECIMAL64;
|
||||
|
||||
final LocalDate today = DateUtils.getBusinessLocalDate();
|
||||
|
||||
if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) {
|
||||
account.postInterest(mc, today, transactionBooleanValues.isInterestTransfer(), isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals);
|
||||
} else {
|
||||
account.calculateInterestUsing(mc, today, transactionBooleanValues.isInterestTransfer(),
|
||||
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill,
|
||||
postReversals);
|
||||
}
|
||||
|
||||
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
|
||||
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
|
||||
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
|
||||
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
|
||||
}
|
||||
|
||||
account.validateAccountBalanceDoesNotBecomeNegative(transactionAmount, transactionBooleanValues.isExceptionForBalanceCheck(),
|
||||
depositAccountOnHoldTransactions, backdatedTxnsAllowedTill);
|
||||
|
||||
saveTransactionToGenerateTransactionId(withdrawal);
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
// Update transactions separately
|
||||
saveUpdatedTransactionsOfSavingsAccount(account.getSavingsAccountTransactionsWithPivotConfig());
|
||||
}
|
||||
this.savingsAccountRepository.save(account);
|
||||
|
||||
if (!transactionBooleanValues.isSkipAccountingPostings()) {
|
||||
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds,
|
||||
transactionBooleanValues.isAccountTransfer(), backdatedTxnsAllowedTill);
|
||||
}
|
||||
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new SavingsWithdrawalBusinessEvent(withdrawal));
|
||||
return withdrawal;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
|
||||
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
|
||||
final boolean isAccountTransfer, final boolean isRegularTransaction, final boolean backdatedTxnsAllowedTill) {
|
||||
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DEPOSIT;
|
||||
return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction,
|
||||
savingsAccountTransactionType, backdatedTxnsAllowedTill, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
|
||||
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
|
||||
final boolean isAccountTransfer, final boolean isRegularTransaction, final boolean backdatedTxnsAllowedTill,
|
||||
final boolean skipAccountingPostings) {
|
||||
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DEPOSIT;
|
||||
return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction,
|
||||
savingsAccountTransactionType, backdatedTxnsAllowedTill, skipAccountingPostings);
|
||||
}
|
||||
|
||||
private SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
|
||||
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
|
||||
final boolean isAccountTransfer, final boolean isRegularTransaction,
|
||||
final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill) {
|
||||
return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction,
|
||||
savingsAccountTransactionType, backdatedTxnsAllowedTill, false);
|
||||
}
|
||||
|
||||
private SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
|
||||
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
|
||||
final boolean isAccountTransfer, final boolean isRegularTransaction,
|
||||
final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill,
|
||||
final boolean skipAccountingPostings) {
|
||||
context.authenticatedUser();
|
||||
account.validateForAccountBlock();
|
||||
account.validateForCreditBlock();
|
||||
|
||||
// Global configurations
|
||||
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
|
||||
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||
final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate();
|
||||
if (isRegularTransaction && !account.allowDeposit()) {
|
||||
throw new DepositAccountTransactionNotAllowedException(account.getId(), "deposit", account.depositAccountType());
|
||||
}
|
||||
boolean isInterestTransfer = false;
|
||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
} else {
|
||||
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
|
||||
Integer accountType = null;
|
||||
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
|
||||
paymentDetail, null, accountType);
|
||||
UUID refNo = UUID.randomUUID();
|
||||
final SavingsAccountTransaction deposit = account.deposit(transactionDTO, savingsAccountTransactionType, backdatedTxnsAllowedTill,
|
||||
relaxingDaysConfigForPivotDate, refNo.toString());
|
||||
final LocalDate postInterestOnDate = null;
|
||||
final MathContext mc = MathContext.DECIMAL64;
|
||||
|
||||
final LocalDate today = DateUtils.getBusinessLocalDate();
|
||||
boolean postReversals = this.configurationDomainService.isReversalTransactionAllowed();
|
||||
if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) {
|
||||
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
|
||||
postInterestOnDate, backdatedTxnsAllowedTill, postReversals);
|
||||
} else {
|
||||
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals);
|
||||
}
|
||||
|
||||
saveTransactionToGenerateTransactionId(deposit);
|
||||
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
// Update transactions separately
|
||||
saveUpdatedTransactionsOfSavingsAccount(account.getSavingsAccountTransactionsWithPivotConfig());
|
||||
}
|
||||
|
||||
this.savingsAccountRepository.saveAndFlush(account);
|
||||
|
||||
if (!skipAccountingPostings) {
|
||||
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
|
||||
}
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new SavingsDepositBusinessEvent(deposit));
|
||||
return deposit;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public SavingsAccountTransaction handleHold(final SavingsAccount account, BigDecimal amount, LocalDate transactionDate,
|
||||
Boolean lienAllowed) {
|
||||
return SavingsAccountTransaction.holdAmount(account, account.office(), null, transactionDate,
|
||||
Money.of(account.getCurrency(), amount), lienAllowed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavingsAccountTransaction handleDividendPayout(final SavingsAccount account, final LocalDate transactionDate,
|
||||
final BigDecimal transactionAmount, final boolean backdatedTxnsAllowedTill) {
|
||||
final DateTimeFormatter fmt = null;
|
||||
final PaymentDetail paymentDetail = null;
|
||||
final boolean isAccountTransfer = false;
|
||||
final boolean isRegularTransaction = true;
|
||||
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DIVIDEND_PAYOUT;
|
||||
return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction,
|
||||
savingsAccountTransactionType, backdatedTxnsAllowedTill);
|
||||
}
|
||||
|
||||
private void updateExistingTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds,
|
||||
Set<Long> existingReversedTransactionIds) {
|
||||
existingTransactionIds.addAll(account.findExistingTransactionIds());
|
||||
existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
|
||||
}
|
||||
|
||||
private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) {
|
||||
this.savingsAccountTransactionRepository.saveAndFlush(transaction);
|
||||
return transaction.getId();
|
||||
}
|
||||
|
||||
private void saveUpdatedTransactionsOfSavingsAccount(final List<SavingsAccountTransaction> savingsAccountTransactions) {
|
||||
this.savingsAccountTransactionRepository.saveAll(savingsAccountTransactions);
|
||||
}
|
||||
|
||||
private void updateTransactionDetailsWithPivotConfig(final SavingsAccount account, Set<Long> existingTransactionIds,
|
||||
Set<Long> existingReversedTransactionIds) {
|
||||
existingTransactionIds.addAll(account.findCurrentTransactionIdsWithPivotDateConfig());
|
||||
existingReversedTransactionIds.addAll(account.findCurrentReversedTransactionIdsWithPivotDateConfig());
|
||||
}
|
||||
|
||||
private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
|
||||
final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer, final boolean backdatedTxnsAllowedTill) {
|
||||
|
||||
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(savingsAccount.getCurrency().getCode(),
|
||||
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
|
||||
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void postJournalEntries(final SavingsAccount account, final Set<Long> existingTransactionIds,
|
||||
final Set<Long> existingReversedTransactionIds, final boolean backdatedTxnsAllowedTill) {
|
||||
|
||||
final boolean isAccountTransfer = false;
|
||||
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavingsAccountTransaction handleReversal(SavingsAccount account, List<SavingsAccountTransaction> savingsAccountTransactions,
|
||||
boolean backdatedTxnsAllowedTill) {
|
||||
|
||||
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
|
||||
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||
final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate();
|
||||
final boolean postReversals = true;
|
||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
} else {
|
||||
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
List<SavingsAccountTransaction> newTransactions = new ArrayList<>();
|
||||
SavingsAccountTransaction reversal = null;
|
||||
|
||||
Set<SavingsAccountChargePaidBy> chargePaidBySet = null;
|
||||
for (SavingsAccountTransaction savingsAccountTransaction : savingsAccountTransactions) {
|
||||
reversal = SavingsAccountTransaction.reversal(savingsAccountTransaction);
|
||||
chargePaidBySet = savingsAccountTransaction.getSavingsAccountChargesPaid();
|
||||
reversal.getSavingsAccountChargesPaid().addAll(chargePaidBySet);
|
||||
account.undoTransaction(savingsAccountTransaction);
|
||||
if (postReversals) {
|
||||
newTransactions.add(reversal);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isInterestTransfer = false;
|
||||
LocalDate postInterestOnDate = null;
|
||||
final LocalDate today = DateUtils.getBusinessLocalDate();
|
||||
final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode());
|
||||
for (SavingsAccountTransaction savingsAccountTransaction : savingsAccountTransactions) {
|
||||
if (savingsAccountTransaction.isPostInterestCalculationRequired()
|
||||
&& account.isBeforeLastPostingPeriod(savingsAccountTransaction.getTransactionDate(), backdatedTxnsAllowedTill)) {
|
||||
|
||||
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
|
||||
postInterestOnDate, backdatedTxnsAllowedTill, postReversals);
|
||||
} else {
|
||||
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals);
|
||||
}
|
||||
account.validatePivotDateTransaction(savingsAccountTransaction.getTransactionDate(), backdatedTxnsAllowedTill,
|
||||
relaxingDaysConfigForPivotDate, "savingsaccount");
|
||||
account.validateAccountBalanceDoesNotBecomeNegativeMinimal(savingsAccountTransaction.getAmount(), false);
|
||||
account.activateAccountBasedOnBalance();
|
||||
}
|
||||
this.savingsAccountRepository.save(account);
|
||||
newTransactions.addAll(account.getSavingsAccountTransactionsWithPivotConfig());
|
||||
this.savingsAccountTransactionRepository.saveAll(newTransactions);
|
||||
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false, backdatedTxnsAllowedTill);
|
||||
|
||||
return reversal;
|
||||
}
|
||||
}
|
||||
|
||||
+16
@@ -43,6 +43,7 @@ import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotF
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService;
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
|
||||
import org.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -60,6 +61,7 @@ public class CurrentAccountTransactionWritePlatformServiceImpl implements Curren
|
||||
private final CurrentProductRepository currentProductRepository;
|
||||
private final ProductToGLAccountMappingRepository productToGLAccountMappingRepository;
|
||||
private final JournalEntryRepository journalEntryRepository;
|
||||
private final DenominationBreakdownWriter denominationBreakdownWriter;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -74,6 +76,7 @@ public class CurrentAccountTransactionWritePlatformServiceImpl implements Curren
|
||||
|
||||
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("transactionAmount");
|
||||
validatePositiveAmount(amount, accountId, "deposit");
|
||||
this.denominationBreakdownWriter.validateBreakdown(command, amount, "DEPOSIT");
|
||||
|
||||
final String refNo = command.stringValueOfParameterNamed("accountNumber");
|
||||
|
||||
@@ -85,6 +88,11 @@ public class CurrentAccountTransactionWritePlatformServiceImpl implements Curren
|
||||
|
||||
currentAccountTransactionRepository.saveAndFlush(txn);
|
||||
|
||||
this.denominationBreakdownWriter.save(command, txn.getId(), account.getId(), account.getAccountNo(),
|
||||
account.getClient() != null ? account.getClient().getId() : null,
|
||||
account.getClient() != null ? account.getClient().getCifNumber() : null, txnDate,
|
||||
"DEPOSIT");
|
||||
|
||||
account.setAccountBalanceDerived(newBalance);
|
||||
account.setTotalDepositsDerived(safeBalance(account.getTotalDepositsDerived()).add(amount));
|
||||
currentAccountRepository.save(account);
|
||||
@@ -135,6 +143,14 @@ public class CurrentAccountTransactionWritePlatformServiceImpl implements Curren
|
||||
account.setTotalWithdrawalsDerived(safeBalance(account.getTotalWithdrawalsDerived()).add(amount));
|
||||
currentAccountRepository.save(account);
|
||||
|
||||
// Validate denomination stock availability then validate totals before saving breakdown
|
||||
this.denominationBreakdownWriter.validateWithdrawalStock(command);
|
||||
this.denominationBreakdownWriter.validateBreakdown(command, amount, "WITHDRAWAL");
|
||||
this.denominationBreakdownWriter.save(command, txn.getId(), account.getId(), account.getAccountNo(),
|
||||
account.getClient() != null ? account.getClient().getId() : null,
|
||||
account.getClient() != null ? account.getClient().getCifNumber() : null, txnDate,
|
||||
"WITHDRAWAL");
|
||||
|
||||
// Withdrawal GL: DR SAVINGS_CONTROL (liability down), CR SAVINGS_REFERENCE (cash out)
|
||||
postTransactionGl(account, txn, JournalEntryType.CREDIT, JournalEntryType.DEBIT, "Current Account Withdrawal");
|
||||
|
||||
|
||||
+1420
-1401
File diff suppressed because it is too large
Load Diff
+1970
-1955
File diff suppressed because it is too large
Load Diff
+9
-4
@@ -98,6 +98,7 @@ import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfig
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimit;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimitRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
|
||||
import org.apache.fineract.portfolio.denominationmaster.domain.SavingsTxnDenominationRepository;
|
||||
import org.apache.fineract.portfolio.savings.service.FacilityChargeAutoApplyService;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformService;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformServiceImpl;
|
||||
@@ -107,6 +108,7 @@ import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalc
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalculationPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformService;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountWritePlatformServiceJpaRepositoryImpl;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositApplicationProcessWritePlatformService;
|
||||
@@ -267,7 +269,8 @@ public class SavingsConfiguration {
|
||||
DepositAccountReadPlatformService depositAccountReadPlatformService, CalendarInstanceRepository calendarInstanceRepository,
|
||||
ConfigurationDomainService configurationDomainService, HolidayRepositoryWrapper holidayRepository,
|
||||
WorkingDaysRepositoryWrapper workingDaysRepository,
|
||||
DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository
|
||||
DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository,
|
||||
DenominationBreakdownWriter denominationBreakdownWriter
|
||||
|
||||
) {
|
||||
return new DepositAccountWritePlatformServiceJpaRepositoryImpl(context, savingAccountRepositoryWrapper,
|
||||
@@ -276,7 +279,8 @@ public class SavingsConfiguration {
|
||||
journalEntryWritePlatformService, depositAccountDomainService, noteRepository, accountTransfersReadPlatformService,
|
||||
chargeRepository, savingsAccountChargeRepository, accountAssociationsReadPlatformService,
|
||||
accountTransfersWritePlatformService, depositAccountReadPlatformService, calendarInstanceRepository,
|
||||
configurationDomainService, holidayRepository, workingDaysRepository, depositAccountOnHoldTransactionRepository);
|
||||
configurationDomainService, holidayRepository, workingDaysRepository, depositAccountOnHoldTransactionRepository,
|
||||
denominationBreakdownWriter);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -424,7 +428,8 @@ public class SavingsConfiguration {
|
||||
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, AppUserRepositoryWrapper appuserRepository,
|
||||
StandingInstructionRepository standingInstructionRepository, BusinessEventNotifierService businessEventNotifierService,
|
||||
GSIMRepositoy gsimRepository, SavingsAccountInterestPostingService savingsAccountInterestPostingService,
|
||||
ErrorHandler errorHandler, FacilityChargeAutoApplyService facilityChargeAutoApplyService) {
|
||||
ErrorHandler errorHandler, FacilityChargeAutoApplyService facilityChargeAutoApplyService,
|
||||
DenominationBreakdownWriter denominationBreakdownWriter) {
|
||||
return new SavingsAccountWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, savingAccountRepositoryWrapper,
|
||||
staffRepository, savingsAccountTransactionRepository, savingAccountAssembler, savingsAccountTransactionDataValidator,
|
||||
savingsAccountChargeDataValidator, paymentDetailWritePlatformService, journalEntryWritePlatformService,
|
||||
@@ -432,7 +437,7 @@ public class SavingsConfiguration {
|
||||
chargeRepository, savingsAccountChargeRepository, holidayRepository, workingDaysRepository, configurationDomainService,
|
||||
depositAccountOnHoldTransactionRepository, entityDatatableChecksWritePlatformService, appuserRepository,
|
||||
standingInstructionRepository, businessEventNotifierService, gsimRepository, savingsAccountInterestPostingService,
|
||||
errorHandler, facilityChargeAutoApplyService);
|
||||
errorHandler, facilityChargeAutoApplyService, denominationBreakdownWriter);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package org.apache.fineract.portfolio.teller.service;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.math.BigDecimal;
|
||||
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.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GlDispatchService {
|
||||
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* Vault → Cashier GL entry. Runs in its own transaction so a failure
|
||||
* never rolls back the caller's (already-committed) transfer record.
|
||||
*/
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void allocateCashToCashier(Long tellerId, Long cashierId,
|
||||
BigDecimal amount, String currencyCode, String txnDate, String notes) {
|
||||
JsonObject body = new JsonObject();
|
||||
body.addProperty("locale", "en");
|
||||
body.addProperty("dateFormat", "yyyy-MM-dd");
|
||||
body.addProperty("txnDate", txnDate);
|
||||
body.addProperty("txnAmount", amount);
|
||||
body.addProperty("currencyCode", currencyCode);
|
||||
body.addProperty("txnNote", notes != null ? notes : "");
|
||||
CommandWrapper wrapper = new CommandWrapperBuilder().withJson(gson.toJson(body))
|
||||
.allocateCashToCashier(tellerId, cashierId).build();
|
||||
commandsSourceWritePlatformService.logCommandSource(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cashier → Vault GL entry (settlement / transfer reversal).
|
||||
*/
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void settleCashFromCashier(Long tellerId, Long cashierId,
|
||||
BigDecimal amount, String currencyCode, String txnDate, String notes) {
|
||||
JsonObject body = new JsonObject();
|
||||
body.addProperty("locale", "en");
|
||||
body.addProperty("dateFormat", "yyyy-MM-dd");
|
||||
body.addProperty("txnDate", txnDate);
|
||||
body.addProperty("txnAmount", amount);
|
||||
body.addProperty("currencyCode", currencyCode);
|
||||
body.addProperty("txnNote", notes != null ? notes : "");
|
||||
CommandWrapper wrapper = new CommandWrapperBuilder().withJson(gson.toJson(body))
|
||||
.settleCashFromCashier(tellerId, cashierId).build();
|
||||
commandsSourceWritePlatformService.logCommandSource(wrapper);
|
||||
}
|
||||
}
|
||||
@@ -316,4 +316,15 @@
|
||||
<include file="parts/0297_fix_unique_nominee_per_account.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0298_unique_external_id_on_current_account.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0299_seed_indian_states.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0300_rename_fd_base_interest_rate_tables_to_cbs.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0301_add_current_account_to_transfer_details.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0012_denomination_master.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0013_cash_exchange.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0014_cash_transfer.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0015_cash_transfer_add_ids.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0016_cash_settlement.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0017_cash_inter_cashier.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0018_cash_reconciliation.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0019_cash_reversal_columns.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0020_cash_permissions.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet id="denomination_master_table_001" author="CBS">
|
||||
<createTable tableName="m_denomination_master">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="denomination" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="kind" type="VARCHAR(10)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="description" type="VARCHAR(100)"/>
|
||||
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="introduced_on" type="DATE"/>
|
||||
<column name="discontinued_on" type="DATE"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="denomination_master_initial_data_001" author="CBS">
|
||||
<insert tableName="m_denomination_master">
|
||||
<column name="denomination" value="2000"/>
|
||||
<column name="kind" value="note"/>
|
||||
<column name="description" value="2000 Rupee Note"/>
|
||||
<column name="is_active" valueBoolean="false"/>
|
||||
<column name="introduced_on" value="2016-11-08"/>
|
||||
<column name="discontinued_on" value="2023-09-30"/>
|
||||
</insert>
|
||||
<insert tableName="m_denomination_master">
|
||||
<column name="denomination" value="500"/>
|
||||
<column name="kind" value="note"/>
|
||||
<column name="description" value="500 Rupee Note"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="introduced_on" value="2016-11-08"/>
|
||||
</insert>
|
||||
<insert tableName="m_denomination_master">
|
||||
<column name="denomination" value="200"/>
|
||||
<column name="kind" value="note"/>
|
||||
<column name="description" value="200 Rupee Note"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="introduced_on" value="2017-08-25"/>
|
||||
</insert>
|
||||
<insert tableName="m_denomination_master">
|
||||
<column name="denomination" value="100"/>
|
||||
<column name="kind" value="note"/>
|
||||
<column name="description" value="100 Rupee Note"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="introduced_on" value="2018-07-19"/>
|
||||
</insert>
|
||||
<insert tableName="m_denomination_master">
|
||||
<column name="denomination" value="50"/>
|
||||
<column name="kind" value="note"/>
|
||||
<column name="description" value="50 Rupee Note"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="introduced_on" value="2017-08-25"/>
|
||||
</insert>
|
||||
<insert tableName="m_denomination_master">
|
||||
<column name="denomination" value="20"/>
|
||||
<column name="kind" value="note"/>
|
||||
<column name="description" value="20 Rupee Note"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="introduced_on" value="2019-08-26"/>
|
||||
</insert>
|
||||
<insert tableName="m_denomination_master">
|
||||
<column name="denomination" value="10"/>
|
||||
<column name="kind" value="note"/>
|
||||
<column name="description" value="10 Rupee Note"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="introduced_on" value="2018-01-01"/>
|
||||
</insert>
|
||||
<insert tableName="m_denomination_master">
|
||||
<column name="denomination" value="20"/>
|
||||
<column name="kind" value="coin"/>
|
||||
<column name="description" value="20 Rupee Coin"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="introduced_on" value="2019-01-01"/>
|
||||
</insert>
|
||||
<insert tableName="m_denomination_master">
|
||||
<column name="denomination" value="10"/>
|
||||
<column name="kind" value="coin"/>
|
||||
<column name="description" value="10 Rupee Coin"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="introduced_on" value="2009-01-01"/>
|
||||
</insert>
|
||||
<insert tableName="m_denomination_master">
|
||||
<column name="denomination" value="5"/>
|
||||
<column name="kind" value="coin"/>
|
||||
<column name="description" value="5 Rupee Coin"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="introduced_on" value="2009-01-01"/>
|
||||
</insert>
|
||||
<insert tableName="m_denomination_master">
|
||||
<column name="denomination" value="2"/>
|
||||
<column name="kind" value="coin"/>
|
||||
<column name="description" value="2 Rupee Coin"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="introduced_on" value="2009-01-01"/>
|
||||
</insert>
|
||||
<insert tableName="m_denomination_master">
|
||||
<column name="denomination" value="1"/>
|
||||
<column name="kind" value="coin"/>
|
||||
<column name="description" value="1 Rupee Coin"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="introduced_on" value="2009-01-01"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="savings_txn_denomination_table_001" author="CBS">
|
||||
<createTable tableName="m_savings_txn_denomination">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="txn_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="savings_account_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="account_no" type="VARCHAR(50)"/>
|
||||
<column name="client_id" type="BIGINT"/>
|
||||
<column name="cif_no" type="VARCHAR(50)"/>
|
||||
<column name="denomination_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="denomination_value" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="count" type="INT" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="total_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="txn_date" type="DATE"/>
|
||||
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="denomination_master_audit_table_001" author="CBS">
|
||||
<createTable tableName="m_denomination_master_audit">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="denomination_id" type="BIGINT"/>
|
||||
<column name="action" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="denomination" type="INT"/>
|
||||
<column name="kind" type="VARCHAR(10)"/>
|
||||
<column name="description" type="VARCHAR(100)"/>
|
||||
<column name="is_active" type="BOOLEAN"/>
|
||||
<column name="introduced_on" type="DATE"/>
|
||||
<column name="discontinued_on" type="DATE"/>
|
||||
<column name="changes" type="VARCHAR(1000)"/>
|
||||
<column name="performed_by" type="BIGINT"/>
|
||||
<column name="performed_by_username" type="VARCHAR(100)"/>
|
||||
<column name="performed_on" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createIndex tableName="m_denomination_master_audit" indexName="idx_denom_audit_denom_id">
|
||||
<column name="denomination_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="denomination_master_soft_delete_001" author="CBS">
|
||||
<addColumn tableName="m_denomination_master">
|
||||
<column name="deleted" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<!-- =====================================================================
|
||||
Migration: Add txn_type, officer_id, officer_username to
|
||||
m_savings_txn_denomination.
|
||||
txn_type distinguishes DEPOSIT from WITHDRAWAL.
|
||||
officer_id / officer_username record who performed the transaction.
|
||||
===================================================================== -->
|
||||
<changeSet id="savings_txn_denomination_add_txn_type_officer_001" author="CBS">
|
||||
<addColumn tableName="m_savings_txn_denomination">
|
||||
<column name="txn_type" type="VARCHAR(20)" defaultValue="DEPOSIT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="officer_id" type="BIGINT"/>
|
||||
<column name="officer_username" type="VARCHAR(100)"/>
|
||||
</addColumn>
|
||||
<createIndex tableName="m_savings_txn_denomination"
|
||||
indexName="idx_savings_txn_denom_txntype">
|
||||
<column name="txn_type"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="m_savings_txn_denomination"
|
||||
indexName="idx_savings_txn_denom_officer">
|
||||
<column name="officer_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet id="cash_exchange_table_001" author="CBS">
|
||||
<createTable tableName="m_cash_exchange">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="account_id" type="BIGINT"/>
|
||||
<column name="account_no" type="VARCHAR(50)"/>
|
||||
<column name="client_id" type="BIGINT"/>
|
||||
<column name="exchange_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="exchange_date" type="DATE"/>
|
||||
<column name="note" type="VARCHAR(500)"/>
|
||||
<column name="performed_by" type="BIGINT"/>
|
||||
<column name="performed_by_username" type="VARCHAR(100)"/>
|
||||
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="cash_exchange_detail_table_001" author="CBS">
|
||||
<createTable tableName="m_cash_exchange_detail">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="exchange_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="direction" type="VARCHAR(10)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="denomination_id" type="BIGINT"/>
|
||||
<column name="denomination_value" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="count" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="total_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createIndex tableName="m_cash_exchange_detail" indexName="idx_cash_exchange_detail_eid">
|
||||
<column name="exchange_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet id="cash_transfer_table_001" author="CBS">
|
||||
<createTable tableName="m_cash_transfer">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="transfer_date" type="DATE"/>
|
||||
<column name="from_party" type="VARCHAR(100)"/>
|
||||
<column name="to_party" type="VARCHAR(100)"/>
|
||||
<column name="transfer_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="note" type="VARCHAR(500)"/>
|
||||
<column name="performed_by" type="BIGINT"/>
|
||||
<column name="performed_by_username" type="VARCHAR(100)"/>
|
||||
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="cash_transfer_detail_table_001" author="CBS">
|
||||
<createTable tableName="m_cash_transfer_detail">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="transfer_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="denomination_id" type="BIGINT"/>
|
||||
<column name="denomination_value" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="count" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="total_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createIndex tableName="m_cash_transfer_detail" indexName="idx_cash_transfer_detail_tid">
|
||||
<column name="transfer_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet id="cash_transfer_add_party_ids_001" author="CBS">
|
||||
<addColumn tableName="m_cash_transfer">
|
||||
<column name="from_teller_id" type="BIGINT"/>
|
||||
<column name="to_cashier_id" type="BIGINT"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet id="cash_settlement_table_001" author="CBS">
|
||||
<createTable tableName="m_cash_settlement">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="settlement_date" type="DATE"/>
|
||||
<column name="from_cashier_id" type="BIGINT"/>
|
||||
<column name="to_teller_id" type="BIGINT"/>
|
||||
<column name="from_party" type="VARCHAR(100)"/>
|
||||
<column name="to_party" type="VARCHAR(100)"/>
|
||||
<column name="settlement_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="note" type="VARCHAR(500)"/>
|
||||
<column name="performed_by" type="BIGINT"/>
|
||||
<column name="performed_by_username" type="VARCHAR(100)"/>
|
||||
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
|
||||
<column name="is_reversed" type="BOOLEAN" defaultValueBoolean="false"/>
|
||||
<column name="reversed_by" type="BIGINT"/>
|
||||
<column name="reversed_at" type="TIMESTAMP"/>
|
||||
<column name="reverse_reason" type="VARCHAR(500)"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="cash_settlement_detail_table_001" author="CBS">
|
||||
<createTable tableName="m_cash_settlement_detail">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="settlement_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="denomination_id" type="BIGINT"/>
|
||||
<column name="denomination_value" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="count" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="total_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createIndex tableName="m_cash_settlement_detail" indexName="idx_cash_settlement_detail_sid">
|
||||
<column name="settlement_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet id="cash_inter_cashier_table_001" author="CBS">
|
||||
<createTable tableName="m_cash_inter_cashier">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="transfer_date" type="DATE"/>
|
||||
<column name="teller_id" type="BIGINT"/>
|
||||
<column name="from_cashier_id" type="BIGINT"/>
|
||||
<column name="to_cashier_id" type="BIGINT"/>
|
||||
<column name="from_party" type="VARCHAR(100)"/>
|
||||
<column name="to_party" type="VARCHAR(100)"/>
|
||||
<column name="transfer_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="note" type="VARCHAR(500)"/>
|
||||
<column name="performed_by" type="BIGINT"/>
|
||||
<column name="performed_by_username" type="VARCHAR(100)"/>
|
||||
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
|
||||
<column name="is_reversed" type="BOOLEAN" defaultValueBoolean="false"/>
|
||||
<column name="reversed_by" type="BIGINT"/>
|
||||
<column name="reversed_at" type="TIMESTAMP"/>
|
||||
<column name="reverse_reason" type="VARCHAR(500)"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="cash_inter_cashier_detail_table_001" author="CBS">
|
||||
<createTable tableName="m_cash_inter_cashier_detail">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="inter_cashier_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="denomination_id" type="BIGINT"/>
|
||||
<column name="denomination_value" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="count" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="total_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createIndex tableName="m_cash_inter_cashier_detail" indexName="idx_cash_inter_cashier_detail_pid">
|
||||
<column name="inter_cashier_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet id="cash_reconciliation_table_001" author="CBS">
|
||||
<createTable tableName="m_cash_reconciliation">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="recon_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="cashier_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="cashier_name" type="VARCHAR(150)"/>
|
||||
<column name="teller_id" type="BIGINT"/>
|
||||
<column name="expected_amount" type="DECIMAL(19,6)" defaultValueNumeric="0"/>
|
||||
<column name="physical_amount" type="DECIMAL(19,6)" defaultValueNumeric="0"/>
|
||||
<column name="variance" type="DECIMAL(19,6)" defaultValueNumeric="0"/>
|
||||
<column name="status" type="VARCHAR(10)" defaultValue="OK"/>
|
||||
<column name="note" type="VARCHAR(500)"/>
|
||||
<column name="performed_by" type="BIGINT"/>
|
||||
<column name="performed_by_username" type="VARCHAR(100)"/>
|
||||
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
|
||||
</createTable>
|
||||
<createIndex tableName="m_cash_reconciliation" indexName="idx_cash_recon_cashier_date">
|
||||
<column name="cashier_id"/>
|
||||
<column name="recon_date"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="cash_reconciliation_detail_table_001" author="CBS">
|
||||
<createTable tableName="m_cash_reconciliation_detail">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="reconciliation_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="denomination_id" type="BIGINT"/>
|
||||
<column name="denomination_value" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="expected_count" type="INT" defaultValueNumeric="0"/>
|
||||
<column name="physical_count" type="INT" defaultValueNumeric="0"/>
|
||||
<column name="variance_count" type="INT" defaultValueNumeric="0"/>
|
||||
<column name="variance_amount" type="DECIMAL(19,6)" defaultValueNumeric="0"/>
|
||||
</createTable>
|
||||
<createIndex tableName="m_cash_reconciliation_detail" indexName="idx_cash_recon_detail_rid">
|
||||
<column name="reconciliation_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet id="cash_transfer_reversal_cols_001" author="CBS">
|
||||
<addColumn tableName="m_cash_transfer">
|
||||
<column name="is_reversed" type="BOOLEAN" defaultValueBoolean="false"/>
|
||||
<column name="reversed_by" type="BIGINT"/>
|
||||
<column name="reversed_at" type="TIMESTAMP"/>
|
||||
<column name="reverse_reason" type="VARCHAR(500)"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet id="cash_module_permissions_001" author="CBS" runOnChange="false">
|
||||
<sql>
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'cashier', 'CREATE_CASHTRANSFER', 'CASHTRANSFER', 'CREATE', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_CASHTRANSFER');
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'cashier', 'READ_CASHTRANSFER', 'CASHTRANSFER', 'READ', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'READ_CASHTRANSFER');
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'cashier', 'REVERSE_CASHTRANSFER', 'CASHTRANSFER', 'REVERSE', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'REVERSE_CASHTRANSFER');
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'cashier', 'CREATE_CASHSETTLEMENT', 'CASHSETTLEMENT', 'CREATE', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_CASHSETTLEMENT');
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'cashier', 'READ_CASHSETTLEMENT', 'CASHSETTLEMENT', 'READ', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'READ_CASHSETTLEMENT');
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'cashier', 'REVERSE_CASHSETTLEMENT', 'CASHSETTLEMENT', 'REVERSE', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'REVERSE_CASHSETTLEMENT');
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'cashier', 'CREATE_CASHINTERCASHIER', 'CASHINTERCASHIER', 'CREATE', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_CASHINTERCASHIER');
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'cashier', 'READ_CASHINTERCASHIER', 'CASHINTERCASHIER', 'READ', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'READ_CASHINTERCASHIER');
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'cashier', 'REVERSE_CASHINTERCASHIER', 'CASHINTERCASHIER', 'REVERSE', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'REVERSE_CASHINTERCASHIER');
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'cashier', 'CREATE_CASHRECONCILIATION', 'CASHRECONCILIATION', 'CREATE', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_CASHRECONCILIATION');
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'cashier', 'READ_CASHRECONCILIATION', 'CASHRECONCILIATION', 'READ', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'READ_CASHRECONCILIATION');
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet id="0300-1" author="cbs">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<tableExists tableName="m_fd_base_interest_rate_effective"/>
|
||||
</preConditions>
|
||||
<renameTable oldTableName="m_fd_base_interest_rate_effective"
|
||||
newTableName="m_cbs_base_interest_rate_effective"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0300-2" author="cbs">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<tableExists tableName="m_fd_base_interest_rate_slab_history"/>
|
||||
</preConditions>
|
||||
<renameTable oldTableName="m_fd_base_interest_rate_slab_history"
|
||||
newTableName="m_cbs_base_interest_rate_slab_history"/>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<!-- Add from_current_account_id column to m_account_transfer_details -->
|
||||
<changeSet id="0301-1" author="cbs">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not>
|
||||
<columnExists tableName="m_account_transfer_details" columnName="from_current_account_id"/>
|
||||
</not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_account_transfer_details">
|
||||
<column name="from_current_account_id" type="BIGINT">
|
||||
<constraints nullable="true"
|
||||
foreignKeyName="FK_atd_from_current_account"
|
||||
references="m_current_account(id)"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<!-- Add to_current_account_id column to m_account_transfer_details -->
|
||||
<changeSet id="0301-2" author="cbs">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not>
|
||||
<columnExists tableName="m_account_transfer_details" columnName="to_current_account_id"/>
|
||||
</not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_account_transfer_details">
|
||||
<column name="to_current_account_id" type="BIGINT">
|
||||
<constraints nullable="true"
|
||||
foreignKeyName="FK_atd_to_current_account"
|
||||
references="m_current_account(id)"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+70
-59
@@ -1,59 +1,70 @@
|
||||
/**
|
||||
* 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.savings;
|
||||
|
||||
public class SavingsTransactionBooleanValues {
|
||||
|
||||
private final boolean isAccountTransfer;
|
||||
private final boolean isRegularTransaction;
|
||||
private final boolean isApplyWithdrawFee;
|
||||
private final boolean isInterestTransfer;
|
||||
private final boolean isExceptionForBalanceCheck;
|
||||
|
||||
public SavingsTransactionBooleanValues(final boolean isAccountTransfer, final boolean isRegularTransaction,
|
||||
final boolean isApplyWithdrawFee, final boolean isInterestTransfer, final boolean isExceptionForBalanceCheck) {
|
||||
|
||||
this.isAccountTransfer = isAccountTransfer;
|
||||
this.isRegularTransaction = isRegularTransaction;
|
||||
this.isApplyWithdrawFee = isApplyWithdrawFee;
|
||||
this.isInterestTransfer = isInterestTransfer;
|
||||
this.isExceptionForBalanceCheck = isExceptionForBalanceCheck;
|
||||
}
|
||||
|
||||
public boolean isAccountTransfer() {
|
||||
return this.isAccountTransfer;
|
||||
}
|
||||
|
||||
public boolean isRegularTransaction() {
|
||||
return this.isRegularTransaction;
|
||||
}
|
||||
|
||||
public boolean isApplyWithdrawFee() {
|
||||
return this.isApplyWithdrawFee;
|
||||
}
|
||||
|
||||
public boolean isInterestTransfer() {
|
||||
return this.isInterestTransfer;
|
||||
}
|
||||
|
||||
public boolean isExceptionForBalanceCheck() {
|
||||
return this.isExceptionForBalanceCheck;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 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.savings;
|
||||
|
||||
public class SavingsTransactionBooleanValues {
|
||||
|
||||
private final boolean isAccountTransfer;
|
||||
private final boolean isRegularTransaction;
|
||||
private final boolean isApplyWithdrawFee;
|
||||
private final boolean isInterestTransfer;
|
||||
private final boolean isExceptionForBalanceCheck;
|
||||
private final boolean skipAccountingPostings;
|
||||
|
||||
public SavingsTransactionBooleanValues(final boolean isAccountTransfer, final boolean isRegularTransaction,
|
||||
final boolean isApplyWithdrawFee, final boolean isInterestTransfer, final boolean isExceptionForBalanceCheck) {
|
||||
this(isAccountTransfer, isRegularTransaction, isApplyWithdrawFee, isInterestTransfer, isExceptionForBalanceCheck, false);
|
||||
}
|
||||
|
||||
public SavingsTransactionBooleanValues(final boolean isAccountTransfer, final boolean isRegularTransaction,
|
||||
final boolean isApplyWithdrawFee, final boolean isInterestTransfer, final boolean isExceptionForBalanceCheck,
|
||||
final boolean skipAccountingPostings) {
|
||||
this.isAccountTransfer = isAccountTransfer;
|
||||
this.isRegularTransaction = isRegularTransaction;
|
||||
this.isApplyWithdrawFee = isApplyWithdrawFee;
|
||||
this.isInterestTransfer = isInterestTransfer;
|
||||
this.isExceptionForBalanceCheck = isExceptionForBalanceCheck;
|
||||
this.skipAccountingPostings = skipAccountingPostings;
|
||||
}
|
||||
|
||||
public boolean isAccountTransfer() {
|
||||
return this.isAccountTransfer;
|
||||
}
|
||||
|
||||
public boolean isRegularTransaction() {
|
||||
return this.isRegularTransaction;
|
||||
}
|
||||
|
||||
public boolean isApplyWithdrawFee() {
|
||||
return this.isApplyWithdrawFee;
|
||||
}
|
||||
|
||||
public boolean isInterestTransfer() {
|
||||
return this.isInterestTransfer;
|
||||
}
|
||||
|
||||
public boolean isExceptionForBalanceCheck() {
|
||||
return this.isExceptionForBalanceCheck;
|
||||
}
|
||||
|
||||
public boolean isSkipAccountingPostings() {
|
||||
return this.skipAccountingPostings;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+83
-82
@@ -1,82 +1,83 @@
|
||||
/**
|
||||
* 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.savings.data;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||
|
||||
public class SavingsAccountConstant extends SavingsApiConstants {
|
||||
|
||||
/**
|
||||
* These parameters will match the class level parameters of {@link SavingsProductData}. Where possible, we try to
|
||||
* get response parameters to match those of request parameters.
|
||||
*/
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
|
||||
dateFormatParamName, monthDayFormatParamName, staffIdParamName, isGSIM, isParentAccount, accountNoParamName,
|
||||
externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName,
|
||||
nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName,
|
||||
interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName,
|
||||
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
|
||||
// withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
|
||||
withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, chargesParamName, allowOverdraftParamName,
|
||||
overdraftLimitParamName, minRequiredBalanceParamName, enforceMinRequiredBalanceParamName, lienAllowedParamName,
|
||||
maxAllowedLienLimitParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName,
|
||||
withHoldTaxParamName, datatables, gsimApplicationId, gsimLastApplication,
|
||||
// CBS Account fields
|
||||
"accountType", "customerSegment", accountStatusParamName, modeOfOperationParamName,
|
||||
specialCategoryParamName, weakerSectionParamName,
|
||||
guardianNameParamName, guardianCifNumberParamName, guardianRelationshipParamName,
|
||||
guardianClientIdParamName, minorDateOfBirthParamName,
|
||||
introducerIdParamName, introducerNameParamName, introducerCifNumberParamName,
|
||||
nomineeParamName, "nominees", linkedAccountIdParamName, linkedAccountTypeParamName, linkedAccountNoParamName,
|
||||
accountFacilityConfigParamName, jointHoldersParamName,
|
||||
balanceSwapOptionParamName, msmeParamName, autoDebitFacilityParamName));
|
||||
|
||||
/**
|
||||
* These parameters will match the class level parameters of {@link SavingsAccountData}. Where possible, we try to
|
||||
* get response parameters to match those of request parameters.
|
||||
*/
|
||||
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
|
||||
dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName,
|
||||
transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName,
|
||||
retailEntriesParamName, childAccountIdParamName, noteParamName, amountParamName, dateParamName, isManualTransaction,
|
||||
lienTransaction, chargesPaidByData, submittedOnDateParamName, accountIdParamName, accountNoParamName));
|
||||
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(idParamName, accountNoParamName));
|
||||
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(localeParamName, dateFormatParamName, activatedOnDateParamName, "openingBalance"));
|
||||
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(localeParamName, dateFormatParamName, closedOnDateParamName, noteParamName, paymentTypeIdParamName,
|
||||
withdrawBalanceParamName, transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName,
|
||||
receiptNumberParamName, bankNumberParamName, postInterestValidationOnClosure));
|
||||
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName,
|
||||
feeOnMonthDayParamName, monthDayFormatParamName, feeIntervalParamName));
|
||||
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, noteParamName));
|
||||
|
||||
}
|
||||
/**
|
||||
* 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.savings.data;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||
|
||||
public class SavingsAccountConstant extends SavingsApiConstants {
|
||||
|
||||
/**
|
||||
* These parameters will match the class level parameters of {@link SavingsProductData}. Where possible, we try to
|
||||
* get response parameters to match those of request parameters.
|
||||
*/
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
|
||||
dateFormatParamName, monthDayFormatParamName, staffIdParamName, isGSIM, isParentAccount, accountNoParamName,
|
||||
externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName,
|
||||
nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName,
|
||||
interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName,
|
||||
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
|
||||
// withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
|
||||
withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, chargesParamName, allowOverdraftParamName,
|
||||
overdraftLimitParamName, minRequiredBalanceParamName, enforceMinRequiredBalanceParamName, lienAllowedParamName,
|
||||
maxAllowedLienLimitParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName,
|
||||
withHoldTaxParamName, datatables, gsimApplicationId, gsimLastApplication,
|
||||
// CBS Account fields
|
||||
"accountType", "customerSegment", accountStatusParamName, modeOfOperationParamName,
|
||||
specialCategoryParamName, weakerSectionParamName,
|
||||
guardianNameParamName, guardianCifNumberParamName, guardianRelationshipParamName,
|
||||
guardianClientIdParamName, minorDateOfBirthParamName,
|
||||
introducerIdParamName, introducerNameParamName, introducerCifNumberParamName,
|
||||
nomineeParamName, "nominees", linkedAccountIdParamName, linkedAccountTypeParamName, linkedAccountNoParamName,
|
||||
accountFacilityConfigParamName, jointHoldersParamName,
|
||||
balanceSwapOptionParamName, msmeParamName, autoDebitFacilityParamName));
|
||||
|
||||
/**
|
||||
* These parameters will match the class level parameters of {@link SavingsAccountData}. Where possible, we try to
|
||||
* get response parameters to match those of request parameters.
|
||||
*/
|
||||
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
|
||||
dateFormatParamName, transactionDateParamName, transactionAmountParamName, paymentTypeIdParamName,
|
||||
transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName,
|
||||
retailEntriesParamName, childAccountIdParamName, noteParamName, amountParamName, dateParamName, isManualTransaction,
|
||||
lienTransaction, chargesPaidByData, submittedOnDateParamName, accountIdParamName, accountNoParamName,
|
||||
"denominationBreakdown"));
|
||||
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(idParamName, accountNoParamName));
|
||||
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(localeParamName, dateFormatParamName, activatedOnDateParamName, "openingBalance"));
|
||||
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(localeParamName, dateFormatParamName, closedOnDateParamName, noteParamName, paymentTypeIdParamName,
|
||||
withdrawBalanceParamName, transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName,
|
||||
receiptNumberParamName, bankNumberParamName, postInterestValidationOnClosure));
|
||||
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName,
|
||||
feeOnMonthDayParamName, monthDayFormatParamName, feeIntervalParamName));
|
||||
|
||||
protected static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList(amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, noteParamName));
|
||||
|
||||
}
|
||||
|
||||
+18
@@ -3969,6 +3969,12 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom<Long>
|
||||
@Column(name = "auto_debit_facility", nullable = false, columnDefinition = "boolean default false")
|
||||
private Boolean autoDebitFacility = false;
|
||||
|
||||
@Column(name = "is_senior_citizen", nullable = false, columnDefinition = "boolean default false")
|
||||
private boolean isSeniorCitizen = false;
|
||||
|
||||
@Column(name = "senior_citizen_interest_rate", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal seniorCitizenInterestRate;
|
||||
|
||||
|
||||
@jakarta.persistence.OneToOne(cascade = jakarta.persistence.CascadeType.ALL, mappedBy = "savingsAccount", orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
private SavingsAccountFacilityConfig facilityConfig;
|
||||
@@ -4041,4 +4047,16 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom<Long>
|
||||
|
||||
public Boolean getAutoDebitFacility() { return autoDebitFacility; }
|
||||
public void setAutoDebitFacility(Boolean v) { this.autoDebitFacility = v; }
|
||||
|
||||
public boolean isSeniorCitizen() { return isSeniorCitizen; }
|
||||
public void setSeniorCitizen(boolean v) { this.isSeniorCitizen = v; }
|
||||
|
||||
public BigDecimal getSeniorCitizenInterestRate() { return seniorCitizenInterestRate; }
|
||||
public void setSeniorCitizenInterestRate(BigDecimal v) { this.seniorCitizenInterestRate = v; }
|
||||
|
||||
public void updateNominalAnnualInterestRate(BigDecimal rate) {
|
||||
if (rate != null && rate.compareTo(BigDecimal.ZERO) > 0) {
|
||||
this.nominalAnnualInterestRate = rate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+55
-51
@@ -1,51 +1,55 @@
|
||||
/**
|
||||
* 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.savings.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
|
||||
|
||||
public interface SavingsAccountDomainService {
|
||||
|
||||
SavingsAccountTransaction handleWithdrawal(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
|
||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, SavingsTransactionBooleanValues transactionBooleanValues,
|
||||
boolean backdatedTxnsAllowedTill);
|
||||
|
||||
SavingsAccountTransaction handleDeposit(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
|
||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isAccountTransfer, boolean isRegularTransaction,
|
||||
boolean backdatedTxnsAllowedTill);
|
||||
|
||||
void postJournalEntries(SavingsAccount savingsAccount, Set<Long> existingTransactionIds, Set<Long> existingReversedTransactionIds,
|
||||
boolean backdatedTxnsAllowedTill);
|
||||
|
||||
SavingsAccountTransaction handleDividendPayout(SavingsAccount account, LocalDate transactionDate, BigDecimal transactionAmount,
|
||||
boolean backdatedTxnsAllowedTill);
|
||||
|
||||
SavingsAccountTransaction handleReversal(SavingsAccount account, List<SavingsAccountTransaction> savingsAccountTransactions,
|
||||
boolean backdatedTxnsAllowedTill);
|
||||
|
||||
SavingsAccountTransaction handleHold(SavingsAccount account, BigDecimal amount, LocalDate transactionDate, Boolean lienAllowed);
|
||||
}
|
||||
/**
|
||||
* 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.savings.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
|
||||
|
||||
public interface SavingsAccountDomainService {
|
||||
|
||||
SavingsAccountTransaction handleWithdrawal(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
|
||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, SavingsTransactionBooleanValues transactionBooleanValues,
|
||||
boolean backdatedTxnsAllowedTill);
|
||||
|
||||
SavingsAccountTransaction handleDeposit(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
|
||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isAccountTransfer, boolean isRegularTransaction,
|
||||
boolean backdatedTxnsAllowedTill);
|
||||
|
||||
SavingsAccountTransaction handleDeposit(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
|
||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isAccountTransfer, boolean isRegularTransaction,
|
||||
boolean backdatedTxnsAllowedTill, boolean skipAccountingPostings);
|
||||
|
||||
void postJournalEntries(SavingsAccount savingsAccount, Set<Long> existingTransactionIds, Set<Long> existingReversedTransactionIds,
|
||||
boolean backdatedTxnsAllowedTill);
|
||||
|
||||
SavingsAccountTransaction handleDividendPayout(SavingsAccount account, LocalDate transactionDate, BigDecimal transactionAmount,
|
||||
boolean backdatedTxnsAllowedTill);
|
||||
|
||||
SavingsAccountTransaction handleReversal(SavingsAccount account, List<SavingsAccountTransaction> savingsAccountTransactions,
|
||||
boolean backdatedTxnsAllowedTill);
|
||||
|
||||
SavingsAccountTransaction handleHold(SavingsAccount account, BigDecimal amount, LocalDate transactionDate, Boolean lienAllowed);
|
||||
}
|
||||
|
||||
+41
-40
@@ -1,40 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
<!-- Sequence is starting from 2000 to make it easier to move existing liquibase changesets here -->
|
||||
<include file="parts/2001_add_savings_accrual_job.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2002_add_savings_accrual_permission.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2003_add_accrued_till_date_to_savings_account.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2004_add_savings_account_cob_infrastructure.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2005_add_savings_product_classification_code_values.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2006_add_period_frequency_options.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2007_add_interest_calculation_type_option.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2008_add_other_gl_mappings_to_savings_product.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2009_add_gl_account_id_to_savings_product.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2010_add_savings_account_cbs_fields.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2011_add_facility_charge_mapping.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2012_add_current_account_permissions.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2013_add_missing_current_account_columns.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2014_add_is_mandatory_to_joint_holders.xml" relativeToChangelogFile="true" />
|
||||
</databaseChangeLog>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
<!-- Sequence is starting from 2000 to make it easier to move existing liquibase changesets here -->
|
||||
<include file="parts/2001_add_savings_accrual_job.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2002_add_savings_accrual_permission.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2003_add_accrued_till_date_to_savings_account.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2004_add_savings_account_cob_infrastructure.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2005_add_savings_product_classification_code_values.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2006_add_period_frequency_options.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2007_add_interest_calculation_type_option.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2008_add_other_gl_mappings_to_savings_product.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2009_add_gl_account_id_to_savings_product.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2010_add_savings_account_cbs_fields.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2011_add_facility_charge_mapping.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2012_add_current_account_permissions.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2013_add_missing_current_account_columns.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2014_add_is_mandatory_to_joint_holders.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2015_add_senior_citizen_to_savings_account.xml" relativeToChangelogFile="true" />
|
||||
</databaseChangeLog>
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership.
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet id="2015_add_senior_citizen_to_savings_account" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<tableExists tableName="m_savings_account"/>
|
||||
<not><columnExists tableName="m_savings_account" columnName="is_senior_citizen"/></not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_savings_account">
|
||||
<column name="is_senior_citizen" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="senior_citizen_interest_rate" type="DECIMAL(19,6)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user