From ce0fb18eeb0cd90325f05c2f5798de637d7b6bca Mon Sep 17 00:00:00 2001 From: Jaanu423 Date: Thu, 21 May 2026 11:20:30 +0530 Subject: [PATCH] feat: implement full current account transaction write service - New CurrentAccountTransactionWritePlatformService interface with deposit, withdrawal, holdAmount, releaseAmount, undoTransaction, reverseTransaction, and adjustTransaction operations - New CurrentAccountTransactionWritePlatformServiceImpl using m_current_account tables directly (not m_savings_account), with GL journal entry posting - Add holdAmount, releaseAmount, reversal factory methods to CurrentAccountTransaction - Wire all 7 current account transaction command handlers to new write service - Fix CurrentAccountTransactionsApiResource template SQL column: currency_multiplesof - Fix account number search in SearchParameters Co-Authored-By: Claude Sonnet 4.6 --- .../core/service/SearchParameters.java | 289 +- .../domain/CurrentAccountTransaction.java | 342 +- .../CurrentAccountReadPlatformService.java | 60 +- ...ccountTransactionWritePlatformService.java | 39 + ...CurrentAccountTransactionsApiResource.java | 57 + .../api/CurrentAccountsApiResource.java | 430 +- .../api/FixedDepositAccountsApiResource.java | 1042 ++--- .../RecurringDepositAccountsApiResource.java | 942 ++-- .../api/SavingsAccountsApiResource.java | 1289 +++--- ...ntTransactionAdjustmentCommandHandler.java | 8 +- .../DepositCurrentAccountCommandHandler.java | 6 +- ...oldAmountCurrentAccountCommandHandler.java | 6 +- ...aseAmountCurrentAccountCommandHandler.java | 6 +- ...ansactionCurrentAccountCommandHandler.java | 6 +- ...ansactionCurrentAccountCommandHandler.java | 6 +- .../WithdrawCurrentAccountCommandHandler.java | 6 +- ...CurrentAccountReadPlatformServiceImpl.java | 1106 ++--- ...ntTransactionWritePlatformServiceImpl.java | 468 ++ .../DepositAccountReadPlatformService.java | 115 +- ...DepositAccountReadPlatformServiceImpl.java | 3912 +++++++++-------- ...SavingsAccountReadPlatformServiceImpl.java | 3078 ++++++------- 21 files changed, 6961 insertions(+), 6252 deletions(-) create mode 100644 fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentaccount/service/CurrentAccountTransactionWritePlatformService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/CurrentAccountTransactionWritePlatformServiceImpl.java diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java index 2f09780ab..e9b83b854 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java @@ -1,144 +1,145 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.service; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import org.apache.commons.lang3.StringUtils; - -@Builder -@Getter -@AllArgsConstructor -public class SearchParameters { - - public static final int DEFAULT_MAX_LIMIT = 200; - - private Long officeId; - private String externalId; - private String name; - private String hierarchy; - private String firstname; - private String lastname; - private String status; - private Integer offset; - @Getter(AccessLevel.NONE) - private Integer limit; - private String orderBy; - private String sortOrder; - private String accountNo; - private String currencyCode; - private Long staffId; - private Long loanId; - private Long clientId; - private Long savingsId; - @Getter(AccessLevel.NONE) - private Boolean orphansOnly; - private Long provisioningEntryId; - private Long productId; - private Long categoryId; - @Getter(AccessLevel.NONE) - private Boolean isSelfUser; - private Integer legalForm; - - public Integer getLimit() { - if (limit == null) { - return DEFAULT_MAX_LIMIT; - } - - if (limit > 0) { - return limit; - } - - return null; // unlimited (0 or less) - } - - public Boolean getOrphansOnly() { - return Boolean.TRUE.equals(orphansOnly); - } - - public Boolean getIsSelfUser() { - return Boolean.TRUE.equals(isSelfUser); - } - - public boolean hasOrderBy() { - return StringUtils.isNotBlank(this.orderBy); - } - - public boolean hasSortOrder() { - return StringUtils.isNotBlank(this.sortOrder); - } - - public boolean hasOfficeId() { - return this.officeId != null && this.officeId != 0; - } - - public boolean hasCurrencyCode() { - return StringUtils.isNotBlank(this.currencyCode); - } - - public boolean hasLimit() { - return this.limit != null && this.limit > 0; - } - - public boolean hasOffset() { - return this.offset != null; - } - - public boolean hasHierarchy() { - return StringUtils.isNotBlank(this.hierarchy); - } - - public boolean hasStaffId() { - return this.staffId != null && this.staffId != 0; - } - - public boolean hasLoanId() { - return this.loanId != null && this.loanId != 0; - } - - public boolean hasSavingsId() { - return this.savingsId != null && this.savingsId != 0; - } - - public boolean hasProvisioningEntryId() { - return this.provisioningEntryId != null && this.provisioningEntryId != 0; - } - - public boolean hasProductId() { - return this.productId != null && this.productId != 0; - } - - public boolean hasCategoryId() { - return this.categoryId != null && this.categoryId != 0; - } - - public boolean isPerson() { - return this.legalForm != null && this.legalForm == 1; - } - - public boolean isEntity() { - return this.legalForm != null && this.legalForm == 2; - } - - public boolean hasLegalForm() { - return this.legalForm != null; - } -} +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.core.service; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +@Builder +@Getter +@AllArgsConstructor +public class SearchParameters { + + public static final int DEFAULT_MAX_LIMIT = 200; + + private Long officeId; + private String externalId; + private String name; + private String hierarchy; + private String firstname; + private String lastname; + private String status; + private Integer offset; + @Getter(AccessLevel.NONE) + private Integer limit; + private String orderBy; + private String sortOrder; + private String accountNo; + private Integer depositType; + private String currencyCode; + private Long staffId; + private Long loanId; + private Long clientId; + private Long savingsId; + @Getter(AccessLevel.NONE) + private Boolean orphansOnly; + private Long provisioningEntryId; + private Long productId; + private Long categoryId; + @Getter(AccessLevel.NONE) + private Boolean isSelfUser; + private Integer legalForm; + + public Integer getLimit() { + if (limit == null) { + return DEFAULT_MAX_LIMIT; + } + + if (limit > 0) { + return limit; + } + + return null; // unlimited (0 or less) + } + + public Boolean getOrphansOnly() { + return Boolean.TRUE.equals(orphansOnly); + } + + public Boolean getIsSelfUser() { + return Boolean.TRUE.equals(isSelfUser); + } + + public boolean hasOrderBy() { + return StringUtils.isNotBlank(this.orderBy); + } + + public boolean hasSortOrder() { + return StringUtils.isNotBlank(this.sortOrder); + } + + public boolean hasOfficeId() { + return this.officeId != null && this.officeId != 0; + } + + public boolean hasCurrencyCode() { + return StringUtils.isNotBlank(this.currencyCode); + } + + public boolean hasLimit() { + return this.limit != null && this.limit > 0; + } + + public boolean hasOffset() { + return this.offset != null; + } + + public boolean hasHierarchy() { + return StringUtils.isNotBlank(this.hierarchy); + } + + public boolean hasStaffId() { + return this.staffId != null && this.staffId != 0; + } + + public boolean hasLoanId() { + return this.loanId != null && this.loanId != 0; + } + + public boolean hasSavingsId() { + return this.savingsId != null && this.savingsId != 0; + } + + public boolean hasProvisioningEntryId() { + return this.provisioningEntryId != null && this.provisioningEntryId != 0; + } + + public boolean hasProductId() { + return this.productId != null && this.productId != 0; + } + + public boolean hasCategoryId() { + return this.categoryId != null && this.categoryId != 0; + } + + public boolean isPerson() { + return this.legalForm != null && this.legalForm == 1; + } + + public boolean isEntity() { + return this.legalForm != null && this.legalForm == 2; + } + + public boolean hasLegalForm() { + return this.legalForm != null; + } +} diff --git a/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentaccount/domain/CurrentAccountTransaction.java b/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentaccount/domain/CurrentAccountTransaction.java index f85a7242b..60f4eb073 100644 --- a/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentaccount/domain/CurrentAccountTransaction.java +++ b/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentaccount/domain/CurrentAccountTransaction.java @@ -1,149 +1,193 @@ -/** - * 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.currentaccount.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import lombok.Getter; -import lombok.Setter; -import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; -import org.apache.fineract.organisation.office.domain.Office; - -@Entity -@Table(name = "m_current_account_transaction") -@Getter -@Setter -public class CurrentAccountTransaction extends AbstractPersistableCustom { - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "current_account_id", nullable = false) - private CurrentAccount currentAccount; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "office_id", nullable = false) - private Office office; - - // 1=Deposit, 2=Withdrawal, 3=InterestPosting, 4=Charge, 5=ChargeWaiver, 6=Transfer, 7=Reversal - @Column(name = "transaction_type_enum", nullable = false) - private Integer transactionTypeEnum; - - @Column(name = "transaction_date", nullable = false) - private LocalDate transactionDate; - - @Column(name = "amount", nullable = false, precision = 19, scale = 6) - private BigDecimal amount; - - @Column(name = "ref_no", length = 100) - private String refNo; - - @Column(name = "remarks", length = 500) - private String remarks; - - @Column(name = "is_reversed", nullable = false) - private boolean reversed = false; - - @Column(name = "is_reversal", nullable = false) - private boolean reversal = false; - - @Column(name = "original_transaction_id") - private Long originalTransactionId; - - @Column(name = "is_manual", nullable = false) - private boolean manual = false; - - @Column(name = "running_balance_derived", precision = 19, scale = 6) - private BigDecimal runningBalanceDerived; - - @Column(name = "cumulative_balance_derived", precision = 19, scale = 6) - private BigDecimal cumulativeBalanceDerived; - - @Column(name = "balance_end_date_derived") - private LocalDate balanceEndDateDerived; - - @Column(name = "balance_number_of_days_derived") - private Integer balanceNumberOfDaysDerived; - - @Column(name = "overdraft_amount_derived", precision = 19, scale = 6) - private BigDecimal overdraftAmountDerived; - - @Column(name = "is_lien_transaction", nullable = false) - private boolean lienTransaction = false; - - @Column(name = "release_id_of_hold_amount_transaction") - private Long releaseIdOfHoldAmountTransaction; - - @Column(name = "reason_for_block", length = 200) - private String reasonForBlock; - - @Column(name = "submitted_on_date") - private LocalDate submittedOnDate; - - @Column(name = "created_date") - private LocalDateTime createdDate; - - @Column(name = "created_by") - private Long createdBy; - - protected CurrentAccountTransaction() {} - - public static CurrentAccountTransaction deposit(CurrentAccount account, Office office, - BigDecimal amount, LocalDate transactionDate, String refNo) { - CurrentAccountTransaction txn = new CurrentAccountTransaction(); - txn.currentAccount = account; - txn.office = office; - txn.transactionTypeEnum = 1; - txn.amount = amount; - txn.transactionDate = transactionDate; - txn.refNo = refNo; - txn.submittedOnDate = transactionDate; - return txn; - } - - public static CurrentAccountTransaction chargeDeduction(CurrentAccount account, Office office, - BigDecimal amount, LocalDate transactionDate) { - CurrentAccountTransaction txn = new CurrentAccountTransaction(); - txn.currentAccount = account; - txn.office = office; - txn.transactionTypeEnum = 7; // PAY_CHARGE - txn.amount = amount; - txn.transactionDate = transactionDate; - txn.submittedOnDate = transactionDate; - return txn; - } - - public static CurrentAccountTransaction withdrawal(CurrentAccount account, Office office, - BigDecimal amount, LocalDate transactionDate, String refNo) { - CurrentAccountTransaction txn = new CurrentAccountTransaction(); - txn.currentAccount = account; - txn.office = office; - txn.transactionTypeEnum = 2; - txn.amount = amount; - txn.transactionDate = transactionDate; - txn.refNo = refNo; - txn.submittedOnDate = transactionDate; - return txn; - } -} +/** + * 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.currentaccount.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.organisation.office.domain.Office; + +@Entity +@Table(name = "m_current_account_transaction") +@Getter +@Setter +public class CurrentAccountTransaction extends AbstractPersistableCustom { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "current_account_id", nullable = false) + private CurrentAccount currentAccount; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "office_id", nullable = false) + private Office office; + + // 1=Deposit, 2=Withdrawal, 3=InterestPosting, 4=Charge, 5=ChargeWaiver, 6=Transfer, 7=Reversal + @Column(name = "transaction_type_enum", nullable = false) + private Integer transactionTypeEnum; + + @Column(name = "transaction_date", nullable = false) + private LocalDate transactionDate; + + @Column(name = "amount", nullable = false, precision = 19, scale = 6) + private BigDecimal amount; + + @Column(name = "ref_no", length = 100) + private String refNo; + + @Column(name = "remarks", length = 500) + private String remarks; + + @Column(name = "is_reversed", nullable = false) + private boolean reversed = false; + + @Column(name = "is_reversal", nullable = false) + private boolean reversal = false; + + @Column(name = "original_transaction_id") + private Long originalTransactionId; + + @Column(name = "is_manual", nullable = false) + private boolean manual = false; + + @Column(name = "running_balance_derived", precision = 19, scale = 6) + private BigDecimal runningBalanceDerived; + + @Column(name = "cumulative_balance_derived", precision = 19, scale = 6) + private BigDecimal cumulativeBalanceDerived; + + @Column(name = "balance_end_date_derived") + private LocalDate balanceEndDateDerived; + + @Column(name = "balance_number_of_days_derived") + private Integer balanceNumberOfDaysDerived; + + @Column(name = "overdraft_amount_derived", precision = 19, scale = 6) + private BigDecimal overdraftAmountDerived; + + @Column(name = "is_lien_transaction", nullable = false) + private boolean lienTransaction = false; + + @Column(name = "release_id_of_hold_amount_transaction") + private Long releaseIdOfHoldAmountTransaction; + + @Column(name = "reason_for_block", length = 200) + private String reasonForBlock; + + @Column(name = "submitted_on_date") + private LocalDate submittedOnDate; + + @Column(name = "created_date") + private LocalDateTime createdDate; + + @Column(name = "created_by") + private Long createdBy; + + protected CurrentAccountTransaction() {} + + public static CurrentAccountTransaction deposit(CurrentAccount account, Office office, + BigDecimal amount, LocalDate transactionDate, String refNo) { + CurrentAccountTransaction txn = new CurrentAccountTransaction(); + txn.currentAccount = account; + txn.office = office; + txn.transactionTypeEnum = 1; + txn.amount = amount; + txn.transactionDate = transactionDate; + txn.refNo = refNo; + txn.submittedOnDate = transactionDate; + return txn; + } + + public static CurrentAccountTransaction chargeDeduction(CurrentAccount account, Office office, + BigDecimal amount, LocalDate transactionDate) { + CurrentAccountTransaction txn = new CurrentAccountTransaction(); + txn.currentAccount = account; + txn.office = office; + txn.transactionTypeEnum = 7; // PAY_CHARGE + txn.amount = amount; + txn.transactionDate = transactionDate; + txn.submittedOnDate = transactionDate; + return txn; + } + + public static CurrentAccountTransaction withdrawal(CurrentAccount account, Office office, + BigDecimal amount, LocalDate transactionDate, String refNo) { + CurrentAccountTransaction txn = new CurrentAccountTransaction(); + txn.currentAccount = account; + txn.office = office; + txn.transactionTypeEnum = 2; + txn.amount = amount; + txn.transactionDate = transactionDate; + txn.refNo = refNo; + txn.submittedOnDate = transactionDate; + return txn; + } + + // transactionTypeEnum = 8 (Hold Amount) + public static CurrentAccountTransaction holdAmount(CurrentAccount account, Office office, + BigDecimal amount, LocalDate transactionDate, String reasonForBlock) { + CurrentAccountTransaction txn = new CurrentAccountTransaction(); + txn.currentAccount = account; + txn.office = office; + txn.transactionTypeEnum = 8; + txn.amount = amount; + txn.transactionDate = transactionDate; + txn.lienTransaction = true; + txn.reasonForBlock = reasonForBlock; + txn.submittedOnDate = transactionDate; + return txn; + } + + // transactionTypeEnum = 9 (Release Amount) + public static CurrentAccountTransaction releaseAmount(CurrentAccount account, Office office, + BigDecimal amount, LocalDate transactionDate, Long holdTransactionId) { + CurrentAccountTransaction txn = new CurrentAccountTransaction(); + txn.currentAccount = account; + txn.office = office; + txn.transactionTypeEnum = 9; + txn.amount = amount; + txn.transactionDate = transactionDate; + txn.releaseIdOfHoldAmountTransaction = holdTransactionId; + txn.submittedOnDate = transactionDate; + return txn; + } + + // transactionTypeEnum = 7 (Reversal / Undo) + public static CurrentAccountTransaction reversal(CurrentAccount account, Office office, + BigDecimal amount, LocalDate transactionDate, Long originalTransactionId) { + CurrentAccountTransaction txn = new CurrentAccountTransaction(); + txn.currentAccount = account; + txn.office = office; + txn.transactionTypeEnum = 7; + txn.amount = amount; + txn.transactionDate = transactionDate; + txn.reversal = true; + txn.originalTransactionId = originalTransactionId; + txn.submittedOnDate = transactionDate; + return txn; + } +} diff --git a/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentaccount/service/CurrentAccountReadPlatformService.java b/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentaccount/service/CurrentAccountReadPlatformService.java index ebc85ed53..bda1ddc5b 100644 --- a/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentaccount/service/CurrentAccountReadPlatformService.java +++ b/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentaccount/service/CurrentAccountReadPlatformService.java @@ -1,29 +1,31 @@ -/** - * 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.currentaccount.service; - -import java.util.List; -import java.util.Map; - -public interface CurrentAccountReadPlatformService { - - Map retrieveOne(Long accountId); - - List> retrieveAll(); -} +/** + * 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.currentaccount.service; + +import java.util.List; +import java.util.Map; + +public interface CurrentAccountReadPlatformService { + + Map retrieveOne(Long accountId); + + List> retrieveAll(); + + List> retrieveAll(Integer offset, Integer limit, String accountNo, String clientName); +} diff --git a/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentaccount/service/CurrentAccountTransactionWritePlatformService.java b/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentaccount/service/CurrentAccountTransactionWritePlatformService.java new file mode 100644 index 000000000..d860b7fb0 --- /dev/null +++ b/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentaccount/service/CurrentAccountTransactionWritePlatformService.java @@ -0,0 +1,39 @@ +/** + * 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.currentaccount.service; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; + +public interface CurrentAccountTransactionWritePlatformService { + + CommandProcessingResult deposit(Long accountId, JsonCommand command); + + CommandProcessingResult withdrawal(Long accountId, JsonCommand command); + + CommandProcessingResult holdAmount(Long accountId, JsonCommand command); + + CommandProcessingResult releaseAmount(Long accountId, Long transactionId); + + CommandProcessingResult undoTransaction(Long accountId, Long transactionId, boolean isReversal); + + CommandProcessingResult reverseTransaction(Long accountId, Long transactionId, boolean isReversal, JsonCommand command); + + CommandProcessingResult adjustTransaction(Long accountId, Long transactionId, JsonCommand command); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentAccountTransactionsApiResource.java index f5b15b94e..36bf1ecd5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentAccountTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentAccountTransactionsApiResource.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.savings.api; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -32,6 +34,10 @@ 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.Collection; +import java.util.LinkedHashMap; +import java.util.Map; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.commands.domain.CommandWrapper; @@ -43,10 +49,14 @@ import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamE import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotFoundException; +import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; +import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService; import org.apache.fineract.portfolio.savings.DepositsApiConstants; import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; @Path("/v1/currentaccounts/{accountId}/transactions") @@ -55,11 +65,58 @@ import org.springframework.stereotype.Component; @RequiredArgsConstructor public class CurrentAccountTransactionsApiResource { + private static final Gson GSON = new GsonBuilder().serializeNulls().create(); + private final PlatformSecurityContext context; private final DefaultToApiJsonSerializer toApiJsonSerializer; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final ApiRequestParameterHelper apiRequestParameterHelper; private final SavingsAccountReadPlatformService savingsAccountReadPlatformService; + private final PaymentTypeReadService paymentTypeReadService; + private final JdbcTemplate jdbcTemplate; + + @GET + @Path("template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve current account transaction template") + @ApiResponse(responseCode = "200", description = "OK") + public String retrieveTemplate(@PathParam("accountId") final Long accountId, @Context final UriInfo uriInfo) { + + context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME); + + final String sql = "SELECT ca.currency_code, ca.currency_digits, ca.currency_multiplesof, " + + "mc.display_symbol, mc.name AS currency_name, mc.internationalized_name_code " + + "FROM m_current_account ca " + + "LEFT JOIN m_currency mc ON mc.code = ca.currency_code " + + "WHERE ca.id = ?"; + + final Map template; + try { + template = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { + final Map map = new LinkedHashMap<>(); + final LocalDate today = LocalDate.now(); + map.put("date", new int[] { today.getYear(), today.getMonthValue(), today.getDayOfMonth() }); + + final Map currency = new LinkedHashMap<>(); + currency.put("code", rs.getString("currency_code")); + currency.put("decimalPlaces", rs.getInt("currency_digits")); + currency.put("inMultiplesOf", rs.getInt("currency_multiplesof")); + currency.put("displaySymbol", rs.getString("display_symbol")); + currency.put("name", rs.getString("currency_name")); + currency.put("nameCode", rs.getString("internationalized_name_code")); + map.put("currency", currency); + return map; + }, accountId); + } catch (org.springframework.dao.EmptyResultDataAccessException e) { + throw new CurrentAccountNotFoundException(accountId); + } + + final Collection paymentTypeOptions = paymentTypeReadService.retrieveAllPaymentTypes(); + template.put("paymentTypeOptions", paymentTypeOptions); + + return GSON.toJson(template); + } @GET @Path("{transactionId}") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentAccountsApiResource.java index 4991170d2..6da4821dc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentAccountsApiResource.java @@ -1,214 +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.savings.api; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.DefaultValue; -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 com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonParser; -import java.util.List; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; -import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; -import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.infrastructure.security.service.SqlValidator; -import org.apache.fineract.portfolio.savings.DepositsApiConstants; -import org.apache.fineract.portfolio.savings.data.SavingsAccountData; -import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountReadPlatformService; -import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWritePlatformService; -import org.apache.fineract.portfolio.savings.service.CurrentAccountTemplateReadPlatformService; -import org.springframework.stereotype.Component; - -@Path("/v1/currentaccounts") -@Component -@Tag(name = "Current Account", description = "Current accounts are instances of a particular current product created for an individual or group.") -@RequiredArgsConstructor -public class CurrentAccountsApiResource { - - private static final Gson GSON = new GsonBuilder().serializeNulls().create(); - - private final CurrentAccountTemplateReadPlatformService currentAccountTemplateReadPlatformService; - private final PlatformSecurityContext context; - private final DefaultToApiJsonSerializer toApiJsonSerializer; - private final ApiRequestParameterHelper apiRequestParameterHelper; - private final SqlValidator sqlValidator; - private final CurrentAccountWritePlatformService currentAccountWritePlatformService; - private final CurrentAccountReadPlatformService currentAccountReadPlatformService; - - @GET - @Path("template") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Current Account Template") - @ApiResponse(responseCode = "200", description = "OK") - public String template(@QueryParam("clientId") @Parameter(description = "clientId") final Long clientId, - @QueryParam("groupId") @Parameter(description = "groupId") final Long groupId, - @QueryParam("productId") @Parameter(description = "productId") final Long productId, - @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @Context final UriInfo uriInfo) { - - context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME); - final SavingsAccountData savingsAccount = currentAccountTemplateReadPlatformService.retrieveTemplate(clientId, groupId, productId, - staffInSelectedOfficeOnly); - final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - return toApiJsonSerializer.serialize(settings, savingsAccount, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - @GET - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List current accounts") - @ApiResponse(responseCode = "200", description = "OK") - public String retrieveAll(@Context final UriInfo uriInfo, - @QueryParam("externalId") final String externalId, - @QueryParam("offset") final Integer offset, - @QueryParam("limit") final Integer limit, - @QueryParam("orderBy") final String orderBy, - @QueryParam("sortOrder") final String sortOrder) { - - context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME); - final List> accounts = currentAccountReadPlatformService.retrieveAll(); - return GSON.toJson(accounts); - } - - @POST - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Submit new current account application") - @ApiResponse(responseCode = "200", description = "OK") - public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) { - final org.apache.fineract.infrastructure.core.api.JsonCommand command = - org.apache.fineract.infrastructure.core.api.JsonCommand.fromJsonElement(null, - JsonParser.parseString(apiRequestBodyAsJson != null ? apiRequestBodyAsJson : "{}")); - final CommandProcessingResult result = currentAccountWritePlatformService.submitApplication(command); - return toApiJsonSerializer.serialize(result); - } - - @GET - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a current account") - @ApiResponse(responseCode = "200", description = "OK") - public String retrieveOne(@PathParam("accountId") final Long accountId, - @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, - @Context final UriInfo uriInfo) { - - context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME); - final Map account = currentAccountReadPlatformService.retrieveOne(accountId); - return GSON.toJson(account); - } - - @PUT - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Modify a current account application") - @ApiResponse(responseCode = "200", description = "OK") - public String update(@PathParam("accountId") final Long accountId, - @Parameter(hidden = true) final String apiRequestBodyAsJson, - @QueryParam("command") final String commandParam) { - - final org.apache.fineract.infrastructure.core.api.JsonCommand command = - org.apache.fineract.infrastructure.core.api.JsonCommand.fromJsonElement(null, - JsonParser.parseString(apiRequestBodyAsJson != null ? apiRequestBodyAsJson : "{}")); - final CommandProcessingResult result = currentAccountWritePlatformService.modifyApplication(accountId, command); - return toApiJsonSerializer.serialize(result); - } - - @POST - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Approve/Reject/Activate/Close/Deposit/Withdrawal and other current account commands") - @ApiResponse(responseCode = "200", description = "OK") - public String handleCommands(@PathParam("accountId") final Long accountId, - @QueryParam("command") final String commandParam, - @Parameter(hidden = true) final String apiRequestBodyAsJson) { - - String jsonApiRequest = apiRequestBodyAsJson; - if (StringUtils.isBlank(jsonApiRequest)) { - jsonApiRequest = "{}"; - } - - final org.apache.fineract.infrastructure.core.api.JsonCommand command = - org.apache.fineract.infrastructure.core.api.JsonCommand.fromJsonElement(null, - JsonParser.parseString(jsonApiRequest)); - - CommandProcessingResult result = null; - if (is(commandParam, "reject")) { - result = currentAccountWritePlatformService.rejectApplication(accountId, command); - } else if (is(commandParam, "withdrawnByApplicant")) { - result = currentAccountWritePlatformService.withdrawApplication(accountId, command); - } else if (is(commandParam, "approve")) { - result = currentAccountWritePlatformService.approveApplication(accountId, command); - } else if (is(commandParam, "undoapproval")) { - result = currentAccountWritePlatformService.undoApproval(accountId, command); - } else if (is(commandParam, "activate")) { - result = currentAccountWritePlatformService.activate(accountId, command); - } else if (is(commandParam, "close")) { - result = currentAccountWritePlatformService.closeAccount(accountId, command); - } - - if (result == null) { - throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "close" }); - } - - return toApiJsonSerializer.serialize(result); - } - - @DELETE - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a current account application") - @ApiResponse(responseCode = "200", description = "OK") - public String delete(@PathParam("accountId") final Long accountId) { - final CommandProcessingResult result = currentAccountWritePlatformService.deleteApplication(accountId); - return toApiJsonSerializer.serialize(result); - } - - private boolean is(final String commandParam, final String commandValue) { - return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); - } - -} +/** + * 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.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.DefaultValue; +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 com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParser; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; +import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.service.SqlValidator; +import org.apache.fineract.portfolio.savings.DepositsApiConstants; +import org.apache.fineract.portfolio.savings.data.SavingsAccountData; +import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountReadPlatformService; +import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWritePlatformService; +import org.apache.fineract.portfolio.savings.service.CurrentAccountTemplateReadPlatformService; +import org.springframework.stereotype.Component; + +@Path("/v1/currentaccounts") +@Component +@Tag(name = "Current Account", description = "Current accounts are instances of a particular current product created for an individual or group.") +@RequiredArgsConstructor +public class CurrentAccountsApiResource { + + private static final Gson GSON = new GsonBuilder().serializeNulls().create(); + + private final CurrentAccountTemplateReadPlatformService currentAccountTemplateReadPlatformService; + private final PlatformSecurityContext context; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final SqlValidator sqlValidator; + private final CurrentAccountWritePlatformService currentAccountWritePlatformService; + private final CurrentAccountReadPlatformService currentAccountReadPlatformService; + + @GET + @Path("template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve Current Account Template") + @ApiResponse(responseCode = "200", description = "OK") + public String template(@QueryParam("clientId") @Parameter(description = "clientId") final Long clientId, + @QueryParam("groupId") @Parameter(description = "groupId") final Long groupId, + @QueryParam("productId") @Parameter(description = "productId") final Long productId, + @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, + @Context final UriInfo uriInfo) { + + context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME); + final SavingsAccountData savingsAccount = currentAccountTemplateReadPlatformService.retrieveTemplate(clientId, groupId, productId, + staffInSelectedOfficeOnly); + final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return toApiJsonSerializer.serialize(settings, savingsAccount, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "List current accounts") + @ApiResponse(responseCode = "200", description = "OK") + public String retrieveAll(@Context final UriInfo uriInfo, + @QueryParam("externalId") final String externalId, + @QueryParam("offset") final Integer offset, + @QueryParam("limit") final Integer limit, + @QueryParam("orderBy") final String orderBy, + @QueryParam("sortOrder") final String sortOrder, + @QueryParam("accountNo") @Parameter(description = "accountNo") final String accountNo, + @QueryParam("clientName") @Parameter(description = "clientName") final String clientName) { + + context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME); + final List> accounts = currentAccountReadPlatformService.retrieveAll(offset, limit, accountNo, clientName); + return GSON.toJson(accounts); + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Submit new current account application") + @ApiResponse(responseCode = "200", description = "OK") + public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) { + final org.apache.fineract.infrastructure.core.api.JsonCommand command = + org.apache.fineract.infrastructure.core.api.JsonCommand.fromJsonElement(null, + JsonParser.parseString(apiRequestBodyAsJson != null ? apiRequestBodyAsJson : "{}")); + final CommandProcessingResult result = currentAccountWritePlatformService.submitApplication(command); + return toApiJsonSerializer.serialize(result); + } + + @GET + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve a current account") + @ApiResponse(responseCode = "200", description = "OK") + public String retrieveOne(@PathParam("accountId") final Long accountId, + @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, + @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, + @Context final UriInfo uriInfo) { + + context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME); + final Map account = currentAccountReadPlatformService.retrieveOne(accountId); + return GSON.toJson(account); + } + + @PUT + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Modify a current account application") + @ApiResponse(responseCode = "200", description = "OK") + public String update(@PathParam("accountId") final Long accountId, + @Parameter(hidden = true) final String apiRequestBodyAsJson, + @QueryParam("command") final String commandParam) { + + final org.apache.fineract.infrastructure.core.api.JsonCommand command = + org.apache.fineract.infrastructure.core.api.JsonCommand.fromJsonElement(null, + JsonParser.parseString(apiRequestBodyAsJson != null ? apiRequestBodyAsJson : "{}")); + final CommandProcessingResult result = currentAccountWritePlatformService.modifyApplication(accountId, command); + return toApiJsonSerializer.serialize(result); + } + + @POST + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Approve/Reject/Activate/Close/Deposit/Withdrawal and other current account commands") + @ApiResponse(responseCode = "200", description = "OK") + public String handleCommands(@PathParam("accountId") final Long accountId, + @QueryParam("command") final String commandParam, + @Parameter(hidden = true) final String apiRequestBodyAsJson) { + + String jsonApiRequest = apiRequestBodyAsJson; + if (StringUtils.isBlank(jsonApiRequest)) { + jsonApiRequest = "{}"; + } + + final org.apache.fineract.infrastructure.core.api.JsonCommand command = + org.apache.fineract.infrastructure.core.api.JsonCommand.fromJsonElement(null, + JsonParser.parseString(jsonApiRequest)); + + CommandProcessingResult result = null; + if (is(commandParam, "reject")) { + result = currentAccountWritePlatformService.rejectApplication(accountId, command); + } else if (is(commandParam, "withdrawnByApplicant")) { + result = currentAccountWritePlatformService.withdrawApplication(accountId, command); + } else if (is(commandParam, "approve")) { + result = currentAccountWritePlatformService.approveApplication(accountId, command); + } else if (is(commandParam, "undoapproval")) { + result = currentAccountWritePlatformService.undoApproval(accountId, command); + } else if (is(commandParam, "activate")) { + result = currentAccountWritePlatformService.activate(accountId, command); + } else if (is(commandParam, "close")) { + result = currentAccountWritePlatformService.closeAccount(accountId, command); + } + + if (result == null) { + throw new UnrecognizedQueryParamException("command", commandParam, + new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "close" }); + } + + return toApiJsonSerializer.serialize(result); + } + + @DELETE + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Delete a current account application") + @ApiResponse(responseCode = "200", description = "OK") + public String delete(@PathParam("accountId") final Long accountId) { + final CommandProcessingResult result = currentAccountWritePlatformService.deleteApplication(accountId); + return toApiJsonSerializer.serialize(result); + } + + private boolean is(final String commandParam, final String commandValue) { + return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java index 3c538c66f..ab67363e6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java @@ -1,520 +1,522 @@ -/** - * 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.api; - -import com.google.gson.JsonElement; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.ArraySchema; -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.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.DefaultValue; -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.Response; -import jakarta.ws.rs.core.UriInfo; -import java.io.InputStream; -import java.math.BigDecimal; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; -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.bulkimport.data.GlobalEntityType; -import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookPopulatorService; -import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookService; -import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; -import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; -import org.apache.fineract.infrastructure.core.api.JsonQuery; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.data.PaginationParameters; -import org.apache.fineract.infrastructure.core.data.UploadRequest; -import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; -import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; -import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; -import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; -import org.apache.fineract.infrastructure.core.service.Page; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.infrastructure.security.service.SqlValidator; -import org.apache.fineract.portfolio.account.data.PortfolioAccountData; -import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService; -import org.apache.fineract.portfolio.savings.DepositAccountType; -import org.apache.fineract.portfolio.savings.DepositsApiConstants; -import org.apache.fineract.portfolio.savings.SavingsApiConstants; -import org.apache.fineract.portfolio.savings.data.DepositAccountData; -import org.apache.fineract.portfolio.savings.data.FixedDepositAccountData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; -import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalculationPlatformService; -import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService; -import org.apache.fineract.portfolio.savings.service.FixedDepositAccountInterestCalculationService; -import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService; -import org.glassfish.jersey.media.multipart.FormDataContentDisposition; -import org.glassfish.jersey.media.multipart.FormDataParam; -import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; - -@Path("/v1/fixeddepositaccounts") -@Component -@Tag(name = "Fixed Deposit Account", description = "Fixed Deposit accounts are instances of a praticular fixed deposit product created. An application process around the creation of accounts is also supported.") -@RequiredArgsConstructor -public class FixedDepositAccountsApiResource { - - private final DepositAccountReadPlatformService depositAccountReadPlatformService; - private final PlatformSecurityContext context; - private final DefaultToApiJsonSerializer toApiJsonSerializer; - private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; - private final ApiRequestParameterHelper apiRequestParameterHelper; - private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService; - private final FromJsonHelper fromJsonHelper; - private final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService; - private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService; - private final BulkImportWorkbookService bulkImportWorkbookService; - private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; - private final FixedDepositAccountInterestCalculationService fixedDepositAccountInterestCalculationService; - private final SqlValidator sqlValidator; - - @GET - @Path("template") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Fixed Deposit Account Template", description = """ - This is a convenience resource. It can be useful when building maintenance user interface screens for fixed deposit applications. The template data returned consists of any or all of: - - - - Field Defaults - - Allowed Value ListsExample Requests: - - - - fixeddepositaccounts/template?clientId=1""") - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.GetFixedDepositAccountsTemplateResponse.class))) - public String template(@QueryParam("clientId") @Parameter(description = "clientId") final Long clientId, - @QueryParam("groupId") @Parameter(description = "groupId") final Long groupId, - @QueryParam("productId") @Parameter(description = "productId") final Long productId, - @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @Context final UriInfo uriInfo) { - - this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); - - final DepositAccountData account = this.depositAccountReadPlatformService.retrieveTemplate(DepositAccountType.FIXED_DEPOSIT, - clientId, groupId, productId, staffInSelectedOfficeOnly); - - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - return this.toApiJsonSerializer.serialize(settings, account, DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - @GET - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Fixed deposit applications/accounts", description = """ - Lists Fixed Deposit Accounts - - Example Requests: - - - - fixeddepositaccounts - - - - fixeddepositaccounts?fields=name""") - @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.GetFixedDepositAccountsResponse.class)))) - public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("paged") @Parameter(description = "paged") final Boolean paged, - @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) { - - this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); - sqlValidator.validate(orderBy); - sqlValidator.validate(sortOrder); - final PaginationParameters paginationParameters = PaginationParameters.builder().paged(Boolean.TRUE.equals(paged)).limit(limit) - .offset(offset).orderBy(orderBy).sortOrder(sortOrder).build(); - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - - if (paginationParameters.isPaged()) { - final Page account = this.depositAccountReadPlatformService - .retrieveAllPaged(DepositAccountType.FIXED_DEPOSIT, paginationParameters); - return this.toApiJsonSerializer.serialize(settings, account, - DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - final Collection account = this.depositAccountReadPlatformService.retrieveAll(DepositAccountType.FIXED_DEPOSIT, - paginationParameters); - - return this.toApiJsonSerializer.serialize(settings, account, DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - @POST - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Submit new fixed deposit application", description = """ - Submits a new fixed deposit application - Mandatory Fields: clientId or groupId, productId, submittedOnDate, depositAmount, depositPeriod, depositPeriodFrequencyId - - Optional Fields: accountNo, externalId, fieldOfficerId,linkAccountId(if provided initial deposit amount will be collected from this account),transferInterestToSavings(By enabling this flag all interest postings will be transferred to linked saving account )""") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PostFixedDepositAccountsRequest.class))) - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PostFixedDepositAccountsResponse.class))) - public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) { - - final CommandWrapper commandRequest = new CommandWrapperBuilder().createFixedDepositAccount().withJson(apiRequestBodyAsJson) - .build(); - - final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return this.toApiJsonSerializer.serialize(result); - } - - @GET - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a fixed deposit application/account", description = """ - Retrieves a fixed deposit application/account - - Example Requests : - - - - fixeddepositaccounts/1 - - fixeddepositaccounts/1?associations=all""") - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.GetFixedDepositAccountsAccountIdResponse.class))) - public String retrieveOne(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, - @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @DefaultValue("all") @QueryParam("chargeStatus") @Parameter(description = "chargeStatus") final String chargeStatus, - @Context final UriInfo uriInfo) { - - this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); - - if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { - throw new UnrecognizedQueryParamException("status", chargeStatus, new Object[] { "all", "active", "inactive" }); - } - - final FixedDepositAccountData account = (FixedDepositAccountData) this.depositAccountReadPlatformService - .retrieveOneWithChartSlabs(DepositAccountType.FIXED_DEPOSIT, accountId); - - final Set mandatoryResponseParameters = new HashSet<>(); - final FixedDepositAccountData accountTemplate = populateTemplateAndAssociations(accountId, account, staffInSelectedOfficeOnly, - chargeStatus, uriInfo, mandatoryResponseParameters); - accountTemplate.setActivationCharge(getActivationCharge(accountId)); - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), - mandatoryResponseParameters); - return this.toApiJsonSerializer.serialize(settings, accountTemplate, - DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - @GET - @Path("calculate-fd-interest") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.CalculateFixedDepositInterestResponse.class))) - public String calculateFixedDepositInterest(@Context final UriInfo uriInfo, - @QueryParam("principalAmount") @Parameter(description = "BigDecimal principalAmount") final BigDecimal principalAmount, - @QueryParam("annualInterestRate") @Parameter(description = "annualInterestRate") final BigDecimal annualInterestRate, - @QueryParam("tenureInMonths") @Parameter(description = "tenureInMonths") final Long tenureInMonths, - @QueryParam("interestCompoundingPeriodInMonths") @Parameter(description = "interestCompoundingPeriodInMonths") final Long interestCompoundingPeriodInMonths, - @QueryParam("interestPostingPeriodInMonths") @Parameter(description = "interestPostingPeriodInMonths") final Long interestPostingPeriodInMonths) { - HashMap request = new HashMap<>(); - request.put("annualInterestRate", annualInterestRate); - request.put("tenureInMonths", tenureInMonths); - request.put("interestCompoundingPeriodInMonths", interestCompoundingPeriodInMonths); - request.put("interestPostingPeriodInMonths", interestPostingPeriodInMonths); - request.put("principalAmount", principalAmount); - String apiRequestBodyAsJson = toApiJsonSerializer.serialize(request); - JsonElement jsonElement = fromJsonHelper.parse(apiRequestBodyAsJson); - HashMap result = fixedDepositAccountInterestCalculationService - .calculateInterest(new JsonQuery(apiRequestBodyAsJson, jsonElement, fromJsonHelper)); - - return toApiJsonSerializer.serializeResult(result); - - } - - private BigDecimal getActivationCharge(Long accountId) { - BigDecimal activationCharge = BigDecimal.ZERO; - Collection savingCharges = this.savingsAccountChargeReadPlatformService - .retrieveSavingsAccountCharges(accountId, "active"); - for (SavingsAccountChargeData savingsAccountChargeData : savingCharges) { - if (savingsAccountChargeData.isSavingsActivation()) { - activationCharge = activationCharge.add(savingsAccountChargeData.getAmountOutstanding()); - } - } - return activationCharge; - } - - private FixedDepositAccountData populateTemplateAndAssociations(final Long accountId, final FixedDepositAccountData savingsAccount, - final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo, - final Set mandatoryResponseParameters) { - - Collection transactions = null; - Collection charges = null; - PortfolioAccountData linkedAccount = null; - PortfolioAccountData transferToSavingsAccount = null; - - final Set associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); - if (!associationParameters.isEmpty()) { - - if (associationParameters.contains("all")) { - associationParameters.addAll( - Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges, SavingsApiConstants.linkedAccount)); - } - - if (associationParameters.contains(SavingsApiConstants.transactions)) { - mandatoryResponseParameters.add(SavingsApiConstants.transactions); - final Collection currentTransactions = this.depositAccountReadPlatformService - .retrieveAllTransactions(DepositAccountType.FIXED_DEPOSIT, accountId); - if (!CollectionUtils.isEmpty(currentTransactions)) { - transactions = currentTransactions; - } - } - - if (associationParameters.contains(SavingsApiConstants.charges)) { - mandatoryResponseParameters.add(SavingsApiConstants.charges); - final Collection currentCharges = this.savingsAccountChargeReadPlatformService - .retrieveSavingsAccountCharges(accountId, chargeStatus); - if (!CollectionUtils.isEmpty(currentCharges)) { - charges = currentCharges; - } - } - - if (associationParameters.contains(SavingsApiConstants.linkedAccount)) { - mandatoryResponseParameters.add(SavingsApiConstants.linkedAccount); - linkedAccount = this.accountAssociationsReadPlatformService.retriveSavingsLinkedAssociation(accountId); - } - } - - if (savingsAccount.getTransferToSavingsId() != null) { - transferToSavingsAccount = this.accountAssociationsReadPlatformService - .retriveSavingsAccount(savingsAccount.getTransferToSavingsId()); - } - - FixedDepositAccountData templateData = null; - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - - if (settings.isTemplate()) { - templateData = (FixedDepositAccountData) this.depositAccountReadPlatformService.retrieveTemplate( - DepositAccountType.FIXED_DEPOSIT, savingsAccount.getClientId(), savingsAccount.getGroupId(), - savingsAccount.getDepositProductId(), staffInSelectedOfficeOnly); - } - - return FixedDepositAccountData.associationsAndTemplate(savingsAccount, templateData, transactions, charges, linkedAccount, - transferToSavingsAccount); - } - - @PUT - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Modify a fixed deposit application", description = "Fixed deposit application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PutFixedDepositAccountsAccountIdRequest.class))) - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PutFixedDepositAccountsAccountIdResponse.class))) - public String update(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, - @Parameter(hidden = true) final String apiRequestBodyAsJson) { - - final CommandWrapper commandRequest = new CommandWrapperBuilder().updateFixedDepositAccount(accountId) - .withJson(apiRequestBodyAsJson).build(); - - final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return this.toApiJsonSerializer.serialize(result); - } - - @POST - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Approve fixed deposit application | Undo approval fixed deposit application | Reject fixed deposit application | Withdraw fixed deposit application | Activate a fixed deposit account | Close a fixed deposit account | Premature Close a fixed deposit account | Calculate Premature amount on Fixed deposit account | Calculate Interest on Fixed Deposit Account | Post Interest on Fixed Deposit Account", description = "Approve fixed deposit application:\n\n" - + "Approves fixed deposit application so long as its in 'Submitted and pending approval' state.\n\n" - + "Undo approval fixed deposit application:\n\n" - + "Will move 'approved' fixed deposit application back to 'Submitted and pending approval' state.\n\n" - + "Reject fixed deposit application:\n\n" - + "Rejects fixed deposit application so long as its in 'Submitted and pending approval' state.\n\n" - + "Withdraw fixed deposit application:\n\n" - + "Used when an applicant withdraws from the fixed deposit application. It must be in 'Submitted and pending approval' state.\n\n" - + "Close a fixed deposit account:\n\n" - + "Results in a Matured fixed deposit account being converted into a 'closed' fixed deposit account.\n\n" - + "Premature Close a fixed deposit account:\n\n" - + "Results in an Active fixed deposit account being converted into a 'Premature Closed' fixed deposit account with options to withdraw prematured amount. (premature amount is calculated using interest rate chart applicable along with penal interest if any.)\n\n" - + "Calculate Premature amount on Fixed deposit account:\n\n" - + "Calculate premature amount on fixed deposit account till premature close date. Premature amount is calculated based on interest chart and penal interest applicable.\n\n" - + "Calculate Interest on Fixed Deposit Account:\n\n" - + "Calculates interest earned on a fixed deposit account based on todays date. It does not attempt to post or credit the interest on the account. That is responsibility of the Post Interest API that will likely be called by overnight process.\n\n" - + "Post Interest on Fixed Deposit Account:\n\n" - + "Calculates and Posts interest earned on a fixed deposit account based on today's date and whether an interest posting or crediting event is due.\n\n" - + "Showing request/response for Calculate Interest on Fixed Deposit Account") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PostFixedDepositAccountsAccountIdRequest.class))) - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PostFixedDepositAccountsAccountIdResponse.class))) - public String handleCommands(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, - @QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo, - @Parameter(hidden = true) final String apiRequestBodyAsJson) { - - String jsonApiRequest = apiRequestBodyAsJson; - if (StringUtils.isBlank(jsonApiRequest)) { - jsonApiRequest = "{}"; - } - - final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); - - CommandProcessingResult result = null; - if (is(commandParam, "reject")) { - final CommandWrapper commandRequest = builder.rejectFixedDepositAccountApplication(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "withdrawnByApplicant")) { - final CommandWrapper commandRequest = builder.withdrawFixedDepositAccountApplication(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "approve")) { - final CommandWrapper commandRequest = builder.approveFixedDepositAccountApplication(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "undoapproval")) { - final CommandWrapper commandRequest = builder.undoFixedDepositAccountApplication(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "activate")) { - final CommandWrapper commandRequest = builder.fixedDepositAccountActivation(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "calculateInterest")) { - final CommandWrapper commandRequest = builder.withNoJsonBody().fixedDepositAccountInterestCalculation(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "postInterest")) { - final CommandWrapper commandRequest = builder.fixedDepositAccountInterestPosting(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "close")) { - final CommandWrapper commandRequest = builder.closeFixedDepositAccount(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "prematureClose")) { - final CommandWrapper commandRequest = builder.prematureCloseFixedDepositAccount(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "calculatePrematureAmount")) { - final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson); - final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper); - final DepositAccountData account = this.accountPreMatureCalculationPlatformService.calculatePreMatureAmount(accountId, query, - DepositAccountType.FIXED_DEPOSIT); - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - return this.toApiJsonSerializer.serialize(settings, account, - DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - if (result == null) { - throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", - "postInterest", "close", "prematureClose", "calculatePrematureAmount" }); - } - - return this.toApiJsonSerializer.serialize(result); - } - - private boolean is(final String commandParam, final String commandValue) { - return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); - } - - @DELETE - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a fixed deposit application", description = "At present we support hard delete of fixed deposit application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the fixed deposit account.") - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.DeleteFixedDepositAccountsAccountIdResponse.class))) - public String delete(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId) { - - final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteFixedDepositAccount(accountId).build(); - - final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return this.toApiJsonSerializer.serialize(result); - } - - @GET - @Path("{accountId}/template") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - public String accountClosureTemplate(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, - @QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo) { - - this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); - DepositAccountData account = null; - if (is(commandParam, "close")) { - account = this.depositAccountReadPlatformService.retrieveOneWithClosureTemplate(DepositAccountType.FIXED_DEPOSIT, accountId); - } - - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - return this.toApiJsonSerializer.serialize(settings, account, DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - @GET - @Path("downloadtemplate") - @Produces("application/vnd.ms-excel") - public Response getFixedDepositTemplate(@QueryParam("officeId") final Long officeId, @QueryParam("staffId") final Long staffId, - @QueryParam("dateFormat") final String dateFormat) { - return bulkImportWorkbookPopulatorService.getTemplate(GlobalEntityType.FIXED_DEPOSIT_ACCOUNTS.toString(), officeId, staffId, - dateFormat); - } - - @POST - @Path("uploadtemplate") - @Consumes(MediaType.MULTIPART_FORM_DATA) - @RequestBody(description = "Upload fixed deposit template", content = { - @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) }) - public String postFixedDepositTemplate(@FormDataParam("file") InputStream uploadedInputStream, - @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("locale") final String locale, - @FormDataParam("dateFormat") final String dateFormat) { - Long importDocumentId = bulkImportWorkbookService.importWorkbook(GlobalEntityType.FIXED_DEPOSIT_ACCOUNTS.toString(), - uploadedInputStream, fileDetail, locale, dateFormat); - return this.toApiJsonSerializer.serialize(importDocumentId); - } - - @GET - @Path("transaction/downloadtemplate") - @Produces("application/vnd.ms-excel") - public Response getFixedDepositTransactionTemplate(@QueryParam("officeId") final Long officeId, - @QueryParam("dateFormat") final String dateFormat) { - return bulkImportWorkbookPopulatorService.getTemplate(GlobalEntityType.FIXED_DEPOSIT_TRANSACTIONS.toString(), officeId, null, - dateFormat); - } - - @POST - @Path("transaction/uploadtemplate") - @Consumes(MediaType.MULTIPART_FORM_DATA) - @RequestBody(description = "Upload fixed deposit transaction template", content = { - @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) }) - public String postFixedDepositTransactionTemplate(@FormDataParam("file") InputStream uploadedInputStream, - @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("locale") final String locale, - @FormDataParam("dateFormat") final String dateFormat) { - final Long importDocumentId = this.bulkImportWorkbookService.importWorkbook(GlobalEntityType.FIXED_DEPOSIT_TRANSACTIONS.toString(), - uploadedInputStream, fileDetail, locale, dateFormat); - return this.toApiJsonSerializer.serialize(importDocumentId); - } -} +/** + * 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.api; + +import com.google.gson.JsonElement; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +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.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.DefaultValue; +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.Response; +import jakarta.ws.rs.core.UriInfo; +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +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.bulkimport.data.GlobalEntityType; +import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookPopulatorService; +import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookService; +import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; +import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.api.JsonQuery; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.PaginationParameters; +import org.apache.fineract.infrastructure.core.data.UploadRequest; +import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; +import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.service.SqlValidator; +import org.apache.fineract.portfolio.account.data.PortfolioAccountData; +import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService; +import org.apache.fineract.portfolio.savings.DepositAccountType; +import org.apache.fineract.portfolio.savings.DepositsApiConstants; +import org.apache.fineract.portfolio.savings.SavingsApiConstants; +import org.apache.fineract.portfolio.savings.data.DepositAccountData; +import org.apache.fineract.portfolio.savings.data.FixedDepositAccountData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; +import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalculationPlatformService; +import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService; +import org.apache.fineract.portfolio.savings.service.FixedDepositAccountInterestCalculationService; +import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +@Path("/v1/fixeddepositaccounts") +@Component +@Tag(name = "Fixed Deposit Account", description = "Fixed Deposit accounts are instances of a praticular fixed deposit product created. An application process around the creation of accounts is also supported.") +@RequiredArgsConstructor +public class FixedDepositAccountsApiResource { + + private final DepositAccountReadPlatformService depositAccountReadPlatformService; + private final PlatformSecurityContext context; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService; + private final FromJsonHelper fromJsonHelper; + private final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService; + private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService; + private final BulkImportWorkbookService bulkImportWorkbookService; + private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; + private final FixedDepositAccountInterestCalculationService fixedDepositAccountInterestCalculationService; + private final SqlValidator sqlValidator; + + @GET + @Path("template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve Fixed Deposit Account Template", description = """ + This is a convenience resource. It can be useful when building maintenance user interface screens for fixed deposit applications. The template data returned consists of any or all of: + + + + Field Defaults + + Allowed Value ListsExample Requests: + + + + fixeddepositaccounts/template?clientId=1""") + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.GetFixedDepositAccountsTemplateResponse.class))) + public String template(@QueryParam("clientId") @Parameter(description = "clientId") final Long clientId, + @QueryParam("groupId") @Parameter(description = "groupId") final Long groupId, + @QueryParam("productId") @Parameter(description = "productId") final Long productId, + @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, + @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); + + final DepositAccountData account = this.depositAccountReadPlatformService.retrieveTemplate(DepositAccountType.FIXED_DEPOSIT, + clientId, groupId, productId, staffInSelectedOfficeOnly); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, account, DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "List Fixed deposit applications/accounts", description = """ + Lists Fixed Deposit Accounts + + Example Requests: + + + + fixeddepositaccounts + + + + fixeddepositaccounts?fields=name""") + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.GetFixedDepositAccountsResponse.class)))) + public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("paged") @Parameter(description = "paged") final Boolean paged, + @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("accountNo") @Parameter(description = "accountNo") final String accountNo, + @QueryParam("clientName") @Parameter(description = "clientName") final String clientName) { + + this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); + sqlValidator.validate(orderBy); + sqlValidator.validate(sortOrder); + final PaginationParameters paginationParameters = PaginationParameters.builder().paged(Boolean.TRUE.equals(paged)).limit(limit) + .offset(offset).orderBy(orderBy).sortOrder(sortOrder).build(); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + if (paginationParameters.isPaged()) { + final Page account = this.depositAccountReadPlatformService + .retrieveAllPaged(DepositAccountType.FIXED_DEPOSIT, paginationParameters, accountNo, clientName); + return this.toApiJsonSerializer.serialize(settings, account, + DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + final Collection account = this.depositAccountReadPlatformService.retrieveAll(DepositAccountType.FIXED_DEPOSIT, + paginationParameters); + + return this.toApiJsonSerializer.serialize(settings, account, DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Submit new fixed deposit application", description = """ + Submits a new fixed deposit application + Mandatory Fields: clientId or groupId, productId, submittedOnDate, depositAmount, depositPeriod, depositPeriodFrequencyId + + Optional Fields: accountNo, externalId, fieldOfficerId,linkAccountId(if provided initial deposit amount will be collected from this account),transferInterestToSavings(By enabling this flag all interest postings will be transferred to linked saving account )""") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PostFixedDepositAccountsRequest.class))) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PostFixedDepositAccountsResponse.class))) + public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().createFixedDepositAccount().withJson(apiRequestBodyAsJson) + .build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + @GET + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve a fixed deposit application/account", description = """ + Retrieves a fixed deposit application/account + + Example Requests : + + + + fixeddepositaccounts/1 + + fixeddepositaccounts/1?associations=all""") + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.GetFixedDepositAccountsAccountIdResponse.class))) + public String retrieveOne(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, + @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, + @DefaultValue("all") @QueryParam("chargeStatus") @Parameter(description = "chargeStatus") final String chargeStatus, + @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); + + if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { + throw new UnrecognizedQueryParamException("status", chargeStatus, new Object[] { "all", "active", "inactive" }); + } + + final FixedDepositAccountData account = (FixedDepositAccountData) this.depositAccountReadPlatformService + .retrieveOneWithChartSlabs(DepositAccountType.FIXED_DEPOSIT, accountId); + + final Set mandatoryResponseParameters = new HashSet<>(); + final FixedDepositAccountData accountTemplate = populateTemplateAndAssociations(accountId, account, staffInSelectedOfficeOnly, + chargeStatus, uriInfo, mandatoryResponseParameters); + accountTemplate.setActivationCharge(getActivationCharge(accountId)); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), + mandatoryResponseParameters); + return this.toApiJsonSerializer.serialize(settings, accountTemplate, + DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + @GET + @Path("calculate-fd-interest") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.CalculateFixedDepositInterestResponse.class))) + public String calculateFixedDepositInterest(@Context final UriInfo uriInfo, + @QueryParam("principalAmount") @Parameter(description = "BigDecimal principalAmount") final BigDecimal principalAmount, + @QueryParam("annualInterestRate") @Parameter(description = "annualInterestRate") final BigDecimal annualInterestRate, + @QueryParam("tenureInMonths") @Parameter(description = "tenureInMonths") final Long tenureInMonths, + @QueryParam("interestCompoundingPeriodInMonths") @Parameter(description = "interestCompoundingPeriodInMonths") final Long interestCompoundingPeriodInMonths, + @QueryParam("interestPostingPeriodInMonths") @Parameter(description = "interestPostingPeriodInMonths") final Long interestPostingPeriodInMonths) { + HashMap request = new HashMap<>(); + request.put("annualInterestRate", annualInterestRate); + request.put("tenureInMonths", tenureInMonths); + request.put("interestCompoundingPeriodInMonths", interestCompoundingPeriodInMonths); + request.put("interestPostingPeriodInMonths", interestPostingPeriodInMonths); + request.put("principalAmount", principalAmount); + String apiRequestBodyAsJson = toApiJsonSerializer.serialize(request); + JsonElement jsonElement = fromJsonHelper.parse(apiRequestBodyAsJson); + HashMap result = fixedDepositAccountInterestCalculationService + .calculateInterest(new JsonQuery(apiRequestBodyAsJson, jsonElement, fromJsonHelper)); + + return toApiJsonSerializer.serializeResult(result); + + } + + private BigDecimal getActivationCharge(Long accountId) { + BigDecimal activationCharge = BigDecimal.ZERO; + Collection savingCharges = this.savingsAccountChargeReadPlatformService + .retrieveSavingsAccountCharges(accountId, "active"); + for (SavingsAccountChargeData savingsAccountChargeData : savingCharges) { + if (savingsAccountChargeData.isSavingsActivation()) { + activationCharge = activationCharge.add(savingsAccountChargeData.getAmountOutstanding()); + } + } + return activationCharge; + } + + private FixedDepositAccountData populateTemplateAndAssociations(final Long accountId, final FixedDepositAccountData savingsAccount, + final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo, + final Set mandatoryResponseParameters) { + + Collection transactions = null; + Collection charges = null; + PortfolioAccountData linkedAccount = null; + PortfolioAccountData transferToSavingsAccount = null; + + final Set associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); + if (!associationParameters.isEmpty()) { + + if (associationParameters.contains("all")) { + associationParameters.addAll( + Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges, SavingsApiConstants.linkedAccount)); + } + + if (associationParameters.contains(SavingsApiConstants.transactions)) { + mandatoryResponseParameters.add(SavingsApiConstants.transactions); + final Collection currentTransactions = this.depositAccountReadPlatformService + .retrieveAllTransactions(DepositAccountType.FIXED_DEPOSIT, accountId); + if (!CollectionUtils.isEmpty(currentTransactions)) { + transactions = currentTransactions; + } + } + + if (associationParameters.contains(SavingsApiConstants.charges)) { + mandatoryResponseParameters.add(SavingsApiConstants.charges); + final Collection currentCharges = this.savingsAccountChargeReadPlatformService + .retrieveSavingsAccountCharges(accountId, chargeStatus); + if (!CollectionUtils.isEmpty(currentCharges)) { + charges = currentCharges; + } + } + + if (associationParameters.contains(SavingsApiConstants.linkedAccount)) { + mandatoryResponseParameters.add(SavingsApiConstants.linkedAccount); + linkedAccount = this.accountAssociationsReadPlatformService.retriveSavingsLinkedAssociation(accountId); + } + } + + if (savingsAccount.getTransferToSavingsId() != null) { + transferToSavingsAccount = this.accountAssociationsReadPlatformService + .retriveSavingsAccount(savingsAccount.getTransferToSavingsId()); + } + + FixedDepositAccountData templateData = null; + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + if (settings.isTemplate()) { + templateData = (FixedDepositAccountData) this.depositAccountReadPlatformService.retrieveTemplate( + DepositAccountType.FIXED_DEPOSIT, savingsAccount.getClientId(), savingsAccount.getGroupId(), + savingsAccount.getDepositProductId(), staffInSelectedOfficeOnly); + } + + return FixedDepositAccountData.associationsAndTemplate(savingsAccount, templateData, transactions, charges, linkedAccount, + transferToSavingsAccount); + } + + @PUT + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Modify a fixed deposit application", description = "Fixed deposit application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PutFixedDepositAccountsAccountIdRequest.class))) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PutFixedDepositAccountsAccountIdResponse.class))) + public String update(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, + @Parameter(hidden = true) final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateFixedDepositAccount(accountId) + .withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + @POST + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Approve fixed deposit application | Undo approval fixed deposit application | Reject fixed deposit application | Withdraw fixed deposit application | Activate a fixed deposit account | Close a fixed deposit account | Premature Close a fixed deposit account | Calculate Premature amount on Fixed deposit account | Calculate Interest on Fixed Deposit Account | Post Interest on Fixed Deposit Account", description = "Approve fixed deposit application:\n\n" + + "Approves fixed deposit application so long as its in 'Submitted and pending approval' state.\n\n" + + "Undo approval fixed deposit application:\n\n" + + "Will move 'approved' fixed deposit application back to 'Submitted and pending approval' state.\n\n" + + "Reject fixed deposit application:\n\n" + + "Rejects fixed deposit application so long as its in 'Submitted and pending approval' state.\n\n" + + "Withdraw fixed deposit application:\n\n" + + "Used when an applicant withdraws from the fixed deposit application. It must be in 'Submitted and pending approval' state.\n\n" + + "Close a fixed deposit account:\n\n" + + "Results in a Matured fixed deposit account being converted into a 'closed' fixed deposit account.\n\n" + + "Premature Close a fixed deposit account:\n\n" + + "Results in an Active fixed deposit account being converted into a 'Premature Closed' fixed deposit account with options to withdraw prematured amount. (premature amount is calculated using interest rate chart applicable along with penal interest if any.)\n\n" + + "Calculate Premature amount on Fixed deposit account:\n\n" + + "Calculate premature amount on fixed deposit account till premature close date. Premature amount is calculated based on interest chart and penal interest applicable.\n\n" + + "Calculate Interest on Fixed Deposit Account:\n\n" + + "Calculates interest earned on a fixed deposit account based on todays date. It does not attempt to post or credit the interest on the account. That is responsibility of the Post Interest API that will likely be called by overnight process.\n\n" + + "Post Interest on Fixed Deposit Account:\n\n" + + "Calculates and Posts interest earned on a fixed deposit account based on today's date and whether an interest posting or crediting event is due.\n\n" + + "Showing request/response for Calculate Interest on Fixed Deposit Account") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PostFixedDepositAccountsAccountIdRequest.class))) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PostFixedDepositAccountsAccountIdResponse.class))) + public String handleCommands(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, + @QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo, + @Parameter(hidden = true) final String apiRequestBodyAsJson) { + + String jsonApiRequest = apiRequestBodyAsJson; + if (StringUtils.isBlank(jsonApiRequest)) { + jsonApiRequest = "{}"; + } + + final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); + + CommandProcessingResult result = null; + if (is(commandParam, "reject")) { + final CommandWrapper commandRequest = builder.rejectFixedDepositAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "withdrawnByApplicant")) { + final CommandWrapper commandRequest = builder.withdrawFixedDepositAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "approve")) { + final CommandWrapper commandRequest = builder.approveFixedDepositAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "undoapproval")) { + final CommandWrapper commandRequest = builder.undoFixedDepositAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "activate")) { + final CommandWrapper commandRequest = builder.fixedDepositAccountActivation(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "calculateInterest")) { + final CommandWrapper commandRequest = builder.withNoJsonBody().fixedDepositAccountInterestCalculation(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "postInterest")) { + final CommandWrapper commandRequest = builder.fixedDepositAccountInterestPosting(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "close")) { + final CommandWrapper commandRequest = builder.closeFixedDepositAccount(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "prematureClose")) { + final CommandWrapper commandRequest = builder.prematureCloseFixedDepositAccount(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "calculatePrematureAmount")) { + final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson); + final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper); + final DepositAccountData account = this.accountPreMatureCalculationPlatformService.calculatePreMatureAmount(accountId, query, + DepositAccountType.FIXED_DEPOSIT); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, account, + DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + if (result == null) { + throw new UnrecognizedQueryParamException("command", commandParam, + new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", + "postInterest", "close", "prematureClose", "calculatePrematureAmount" }); + } + + return this.toApiJsonSerializer.serialize(result); + } + + private boolean is(final String commandParam, final String commandValue) { + return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); + } + + @DELETE + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Delete a fixed deposit application", description = "At present we support hard delete of fixed deposit application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the fixed deposit account.") + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.DeleteFixedDepositAccountsAccountIdResponse.class))) + public String delete(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteFixedDepositAccount(accountId).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + @GET + @Path("{accountId}/template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String accountClosureTemplate(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, + @QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); + DepositAccountData account = null; + if (is(commandParam, "close")) { + account = this.depositAccountReadPlatformService.retrieveOneWithClosureTemplate(DepositAccountType.FIXED_DEPOSIT, accountId); + } + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, account, DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + @GET + @Path("downloadtemplate") + @Produces("application/vnd.ms-excel") + public Response getFixedDepositTemplate(@QueryParam("officeId") final Long officeId, @QueryParam("staffId") final Long staffId, + @QueryParam("dateFormat") final String dateFormat) { + return bulkImportWorkbookPopulatorService.getTemplate(GlobalEntityType.FIXED_DEPOSIT_ACCOUNTS.toString(), officeId, staffId, + dateFormat); + } + + @POST + @Path("uploadtemplate") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @RequestBody(description = "Upload fixed deposit template", content = { + @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) }) + public String postFixedDepositTemplate(@FormDataParam("file") InputStream uploadedInputStream, + @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("locale") final String locale, + @FormDataParam("dateFormat") final String dateFormat) { + Long importDocumentId = bulkImportWorkbookService.importWorkbook(GlobalEntityType.FIXED_DEPOSIT_ACCOUNTS.toString(), + uploadedInputStream, fileDetail, locale, dateFormat); + return this.toApiJsonSerializer.serialize(importDocumentId); + } + + @GET + @Path("transaction/downloadtemplate") + @Produces("application/vnd.ms-excel") + public Response getFixedDepositTransactionTemplate(@QueryParam("officeId") final Long officeId, + @QueryParam("dateFormat") final String dateFormat) { + return bulkImportWorkbookPopulatorService.getTemplate(GlobalEntityType.FIXED_DEPOSIT_TRANSACTIONS.toString(), officeId, null, + dateFormat); + } + + @POST + @Path("transaction/uploadtemplate") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @RequestBody(description = "Upload fixed deposit transaction template", content = { + @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) }) + public String postFixedDepositTransactionTemplate(@FormDataParam("file") InputStream uploadedInputStream, + @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("locale") final String locale, + @FormDataParam("dateFormat") final String dateFormat) { + final Long importDocumentId = this.bulkImportWorkbookService.importWorkbook(GlobalEntityType.FIXED_DEPOSIT_TRANSACTIONS.toString(), + uploadedInputStream, fileDetail, locale, dateFormat); + return this.toApiJsonSerializer.serialize(importDocumentId); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java index 5027d4b3b..0ed66a8ff 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java @@ -1,470 +1,472 @@ -/** - * 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.api; - -import com.google.gson.JsonElement; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.DefaultValue; -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.Response; -import jakarta.ws.rs.core.UriInfo; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; -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.bulkimport.data.GlobalEntityType; -import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookPopulatorService; -import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookService; -import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; -import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; -import org.apache.fineract.infrastructure.core.api.JsonQuery; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.data.PaginationParameters; -import org.apache.fineract.infrastructure.core.data.UploadRequest; -import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; -import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; -import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; -import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; -import org.apache.fineract.infrastructure.core.service.Page; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.infrastructure.security.service.SqlValidator; -import org.apache.fineract.portfolio.savings.DepositAccountType; -import org.apache.fineract.portfolio.savings.DepositsApiConstants; -import org.apache.fineract.portfolio.savings.SavingsApiConstants; -import org.apache.fineract.portfolio.savings.data.DepositAccountData; -import org.apache.fineract.portfolio.savings.data.RecurringDepositAccountData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; -import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalculationPlatformService; -import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService; -import org.apache.fineract.portfolio.savings.service.RecurringDepositAccountInterestCalculationService; -import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService; -import org.glassfish.jersey.media.multipart.FormDataContentDisposition; -import org.glassfish.jersey.media.multipart.FormDataParam; -import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; - -@Path("/v1/recurringdepositaccounts") -@Component -@Tag(name = "Recurring Deposit Account", description = "Recurring Deposit accounts are instances of a praticular recurring deposit product created. An application process around the creation of accounts is also supported.") -@RequiredArgsConstructor -public class RecurringDepositAccountsApiResource { - - private final DepositAccountReadPlatformService depositAccountReadPlatformService; - private final PlatformSecurityContext context; - private final DefaultToApiJsonSerializer toApiJsonSerializer; - private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; - private final ApiRequestParameterHelper apiRequestParameterHelper; - private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService; - private final FromJsonHelper fromJsonHelper; - private final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService; - private final BulkImportWorkbookService bulkImportWorkbookService; - private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; - private final RecurringDepositAccountInterestCalculationService recurringDepositAccountInterestCalculationService; - private final SqlValidator sqlValidator; - - @GET - @Path("template") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve recurring Deposit Account Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for recurring deposit applications. The template data returned consists of any or all of:\n" - + "\n" + "Field Defaults\n" + "Allowed Value Lists\n\n" + "Example Requests:\n" + "\n" - + "recurringdepositaccounts/template?clientId=1\n" + "\n" + "\n" + "recurringdepositaccounts/template?clientId=1&productId=1") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.GetRecurringDepositAccountsTemplateResponse.class))) }) - public String template(@QueryParam("clientId") @Parameter(description = "clientId") final Long clientId, - @QueryParam("groupId") @Parameter(description = "groupId") final Long groupId, - @QueryParam("productId") @Parameter(description = "productId") final Long productId, - @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @Context final UriInfo uriInfo) { - - this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - - final DepositAccountData account = this.depositAccountReadPlatformService.retrieveTemplate(DepositAccountType.RECURRING_DEPOSIT, - clientId, groupId, productId, staffInSelectedOfficeOnly); - - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - return this.toApiJsonSerializer.serialize(settings, account, - DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - @GET - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Recurring deposit applications/accounts", description = "Lists Recurring deposit applications/accounts\n\n" - + "Example Requests:\n" + "\n" + "recurringdepositaccounts\n" + "\n" + "\n" + "recurringdepositaccounts?fields=name") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.GetRecurringDepositAccountsResponse.class)))) }) - public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("paged") @Parameter(description = "paged") final Boolean paged, - @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) { - - this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - sqlValidator.validate(orderBy); - sqlValidator.validate(sortOrder); - final PaginationParameters paginationParameters = PaginationParameters.builder().paged(Boolean.TRUE.equals(paged)).limit(limit) - .offset(offset).orderBy(orderBy).sortOrder(sortOrder).build(); - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - - if (paginationParameters.isPaged()) { - final Page account = this.depositAccountReadPlatformService - .retrieveAllPaged(DepositAccountType.RECURRING_DEPOSIT, paginationParameters); - return this.toApiJsonSerializer.serialize(settings, account, - DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - final Collection account = this.depositAccountReadPlatformService - .retrieveAll(DepositAccountType.RECURRING_DEPOSIT, paginationParameters); - - return this.toApiJsonSerializer.serialize(settings, account, - DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - @POST - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Submit new recurring deposit application", description = "Submits new recurring deposit application\n\n" - + "Mandatory Fields: clientId or groupId, productId, submittedOnDate, depositAmount, depositPeriod, depositPeriodFrequencyId\n\n" - + "Optional Fields: accountNo, externalId, fieldOfficerId,linkAccountId(if provided initial deposit amount will be collected from this account),transferInterestToSavings(By enabling this flag all interest postings will be transferred to linked saving account )\n\n" - + "Inherited from Product (if not provided): interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, lockinPeriodFrequencyType, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnTypeId, charts, withHoldTax") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PostRecurringDepositAccountsRequest.class))) - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PostRecurringDepositAccountsResponse.class))) }) - public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) { - - final CommandWrapper commandRequest = new CommandWrapperBuilder().createRecurringDepositAccount().withJson(apiRequestBodyAsJson) - .build(); - - final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return this.toApiJsonSerializer.serialize(result); - } - - @GET - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a recurring deposit application/account", description = "Retrieves a recurring deposit application/account\n\n" - + "Example Requests :\n" + "\n" + "recurringdepositaccounts/1\n" + "\n" + "\n" + "recurringdepositaccounts/1?associations=all") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.GetRecurringDepositAccountsAccountIdResponse.class))) }) - public String retrieveOne(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, - @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @DefaultValue("all") @QueryParam("chargeStatus") @Parameter(description = "chargeStatus") final String chargeStatus, - @Context final UriInfo uriInfo) { - - this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - - if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { - throw new UnrecognizedQueryParamException("status", chargeStatus, new Object[] { "all", "active", "inactive" }); - } - - final RecurringDepositAccountData account = (RecurringDepositAccountData) this.depositAccountReadPlatformService - .retrieveOneWithChartSlabs(DepositAccountType.RECURRING_DEPOSIT, accountId); - - final Set mandatoryResponseParameters = new HashSet<>(); - final RecurringDepositAccountData accountTemplate = populateTemplateAndAssociations(accountId, account, staffInSelectedOfficeOnly, - chargeStatus, uriInfo, mandatoryResponseParameters); - - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), - mandatoryResponseParameters); - return this.toApiJsonSerializer.serialize(settings, accountTemplate, - DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - private RecurringDepositAccountData populateTemplateAndAssociations(final Long accountId, - final RecurringDepositAccountData savingsAccount, final boolean staffInSelectedOfficeOnly, final String chargeStatus, - final UriInfo uriInfo, final Set mandatoryResponseParameters) { - - Collection transactions = null; - Collection charges = null; - - final Set associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); - if (!associationParameters.isEmpty()) { - - if (associationParameters.contains("all")) { - associationParameters.addAll(Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges)); - } - - if (associationParameters.contains(SavingsApiConstants.transactions)) { - mandatoryResponseParameters.add(SavingsApiConstants.transactions); - final Collection currentTransactions = this.depositAccountReadPlatformService - .retrieveAllTransactions(DepositAccountType.RECURRING_DEPOSIT, accountId); - if (!CollectionUtils.isEmpty(currentTransactions)) { - transactions = currentTransactions; - } - } - - if (associationParameters.contains(SavingsApiConstants.charges)) { - mandatoryResponseParameters.add(SavingsApiConstants.charges); - final Collection currentCharges = this.savingsAccountChargeReadPlatformService - .retrieveSavingsAccountCharges(accountId, chargeStatus); - if (!CollectionUtils.isEmpty(currentCharges)) { - charges = currentCharges; - } - } - } - - RecurringDepositAccountData templateData = null; - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - - if (settings.isTemplate()) { - templateData = (RecurringDepositAccountData) this.depositAccountReadPlatformService.retrieveTemplate( - DepositAccountType.RECURRING_DEPOSIT, savingsAccount.getClientId(), savingsAccount.getGroupId(), - savingsAccount.getDepositProductId(), staffInSelectedOfficeOnly); - } - - return RecurringDepositAccountData.withTemplateOptions(savingsAccount, templateData, transactions, charges); - } - - @PUT - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Modify a recurring deposit application", description = "Recurring deposit application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PutRecurringDepositAccountsAccountIdRequest.class))) - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PutRecurringDepositAccountsAccountIdResponse.class))) }) - public String update(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, - @Parameter(hidden = true) final String apiRequestBodyAsJson) { - - final CommandWrapper commandRequest = new CommandWrapperBuilder().updateRecurringDepositAccount(accountId) - .withJson(apiRequestBodyAsJson).build(); - - final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return this.toApiJsonSerializer.serialize(result); - } - - @POST - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Approve recurring deposit application | Undo approval recurring deposit application | Reject recurring deposit application | Withdraw recurring deposit application | Activate a recurring deposit account | Update the recommended deposit amount for a recurring deposit account | Close a recurring deposit account | Premature Close a recurring deposit account | Calculate Premature amount on Recurring deposit account | Calculate Interest on recurring Deposit Account | Post Interest on recurring Deposit Account", description = "Approve recurring deposit application:\n\n" - + "Approves recurring deposit application so long as its in 'Submitted and pending approval' state.\n\n" - + "Undo approval recurring deposit application:\n\n" - + "Will move 'approved' recurring deposit application back to 'Submitted and pending approval' state.\n\n" - + "Reject recurring deposit application\n\n" - + "Rejects recurring deposit application so long as its in 'Submitted and pending approval' state.\n\n" - + "Withdraw recurring deposit application:\n\n" - + "Used when an applicant withdraws from the recurring deposit application. It must be in 'Submitted and pending approval' state.\n\n" - + "Activate a recurring deposit account:\n\n" - + "Results in an approved recurring deposit application being converted into an 'active' recurring deposit account.\n\n" - + "Update the recommended deposit amount for a recurring deposit account:\n\n" - + "Updates the recommended deposit amount for a RD account as on the effective date.\n\n" - + "Close a recurring deposit account\n\n" - + "Results in a Matured recurring deposit account being converted into a 'closed' recurring deposit account.\n" + "\n" - + "On account close allowed actions are." + "Premature Close a recurring deposit account:\n\n" - + "Results in an Active recurring deposit account being converted into a 'Premature Closed' recurring deposit account with options to withdraw prematured amount. (premature amount is calculated using interest rate chart applicable along with penal interest if any.)\n" - + "\n" + "On account premature closure allowed actions are.\n\n" - + "Calculate Premature amount on Recurring deposit account:\n\n" - + "Calculate premature amount on recurring deposit till premature close date. Premature amount is calculated based on interest chart and penal interest applicable if any.\n\n" - + "Calculate Interest on recurring Deposit Account:\n\n" - + "Calculates interest earned on a recurring deposit account based on todays date. It does not attempt to post or credit the interest on the account. That is responsibility of the Post Interest API that will likely be called by overnight process.\n\n" - + "Post Interest on recurring Deposit Account:\n\n" - + "Calculates and Posts interest earned on a recurring deposit account based on todays date and whether an interest posting or crediting event is due.\n\n" - + "Showing request/response for 'Post Interest on recurring Deposit Account'") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PostRecurringDepositAccountsAccountIdRequest.class))) - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PostRecurringDepositAccountsAccountIdResponse.class))) }) - public String handleCommands(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, - @QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo, - @Parameter(hidden = true) final String apiRequestBodyAsJson) { - - String jsonApiRequest = apiRequestBodyAsJson; - if (StringUtils.isBlank(jsonApiRequest)) { - jsonApiRequest = "{}"; - } - - final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); - - CommandProcessingResult result = null; - if (is(commandParam, "reject")) { - final CommandWrapper commandRequest = builder.rejectRecurringDepositAccountApplication(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "withdrawnByApplicant")) { - final CommandWrapper commandRequest = builder.withdrawRecurringDepositAccountApplication(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "approve")) { - final CommandWrapper commandRequest = builder.approveRecurringDepositAccountApplication(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "undoapproval")) { - final CommandWrapper commandRequest = builder.undoRecurringDepositAccountApplication(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "activate")) { - final CommandWrapper commandRequest = builder.recurringDepositAccountActivation(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "calculateInterest")) { - final CommandWrapper commandRequest = builder.withNoJsonBody().recurringDepositAccountInterestCalculation(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, DepositsApiConstants.UPDATE_DEPOSIT_AMOUNT)) { - final CommandWrapper commandRequest = builder.updateDepositAmountForRecurringDepositAccount(accountId) - .withJson(apiRequestBodyAsJson).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "postInterest")) { - final CommandWrapper commandRequest = builder.recurringDepositAccountInterestPosting(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "close")) { - final CommandWrapper commandRequest = builder.closeRecurringDepositAccount(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "prematureClose")) { - final CommandWrapper commandRequest = builder.prematureCloseRecurringDepositAccount(accountId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "calculatePrematureAmount")) { - final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson); - final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper); - final DepositAccountData account = this.accountPreMatureCalculationPlatformService.calculatePreMatureAmount(accountId, query, - DepositAccountType.RECURRING_DEPOSIT); - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - return this.toApiJsonSerializer.serialize(settings, account, - DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - if (result == null) { - throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", - "postInterest", "close", "prematureClose", "calculatePrematureAmount" }); - } - - return this.toApiJsonSerializer.serialize(result); - } - - @POST - @Path("calculate-rd-interest") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - public String calculateRecurringDepositInterest(@Context final UriInfo uriInfo, final String apiRequestBodyAsJson) { - JsonElement jsonElement = fromJsonHelper.parse(apiRequestBodyAsJson); - HashMap result = recurringDepositAccountInterestCalculationService - .calculateInterest(new JsonQuery(apiRequestBodyAsJson, jsonElement, fromJsonHelper)); - - return toApiJsonSerializer.serializeResult(result); - } - - private boolean is(final String commandParam, final String commandValue) { - return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); - } - - @DELETE - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a recurring deposit application", description = "At present we support hard delete of recurring deposit application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the recurring deposit account.") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.DeleteRecurringDepositAccountsResponse.class))) }) - public String delete(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId) { - - final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteRecurringDepositAccount(accountId).build(); - - final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return this.toApiJsonSerializer.serialize(result); - } - - @GET - @Path("{accountId}/template") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - public String accountClosureTemplate(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, - @QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo) { - - this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - DepositAccountData account = null; - if (is(commandParam, "close")) { - account = this.depositAccountReadPlatformService.retrieveOneWithClosureTemplate(DepositAccountType.RECURRING_DEPOSIT, - accountId); - - } - - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - return this.toApiJsonSerializer.serialize(settings, account, - DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - @GET - @Path("downloadtemplate") - @Produces("application/vnd.ms-excel") - public Response getRecurringDepositTemplate(@QueryParam("officeId") final Long officeId, @QueryParam("staffId") final Long staffId, - @QueryParam("dateFormat") final String dateFormat) { - return bulkImportWorkbookPopulatorService.getTemplate(GlobalEntityType.RECURRING_DEPOSIT_ACCOUNTS.toString(), officeId, staffId, - dateFormat); - } - - @POST - @Path("uploadtemplate") - @Consumes(MediaType.MULTIPART_FORM_DATA) - @RequestBody(description = "Upload recurring deposit template", content = { - @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) }) - public String postRecurringDepositTemplate(@FormDataParam("file") InputStream uploadedInputStream, - @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("locale") final String locale, - @FormDataParam("dateFormat") final String dateFormat) { - final Long importDocumentId = this.bulkImportWorkbookService.importWorkbook(GlobalEntityType.RECURRING_DEPOSIT_ACCOUNTS.toString(), - uploadedInputStream, fileDetail, locale, dateFormat); - return this.toApiJsonSerializer.serialize(importDocumentId); - } - - @GET - @Path("transactions/downloadtemplate") - @Produces("application/vnd.ms-excel") - public Response getRecurringDepositTransactionTemplate(@QueryParam("officeId") final Long officeId, - @QueryParam("dateFormat") final String dateFormat) { - return bulkImportWorkbookPopulatorService.getTemplate(GlobalEntityType.RECURRING_DEPOSIT_ACCOUNTS_TRANSACTIONS.toString(), officeId, - null, dateFormat); - } - - @POST - @Path("transactions/uploadtemplate") - @Consumes(MediaType.MULTIPART_FORM_DATA) - @RequestBody(description = "Upload recurring deposit transactions template", content = { - @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) }) - public String postRecurringDepositTransactionsTemplate(@FormDataParam("file") InputStream uploadedInputStream, - @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("locale") final String locale, - @FormDataParam("dateFormat") final String dateFormat) { - final Long importDocumentId = this.bulkImportWorkbookService.importWorkbook( - GlobalEntityType.RECURRING_DEPOSIT_ACCOUNTS_TRANSACTIONS.toString(), uploadedInputStream, fileDetail, locale, dateFormat); - return this.toApiJsonSerializer.serialize(importDocumentId); - } -} +/** + * 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.api; + +import com.google.gson.JsonElement; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.DefaultValue; +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.Response; +import jakarta.ws.rs.core.UriInfo; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +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.bulkimport.data.GlobalEntityType; +import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookPopulatorService; +import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookService; +import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; +import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.api.JsonQuery; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.PaginationParameters; +import org.apache.fineract.infrastructure.core.data.UploadRequest; +import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; +import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.service.SqlValidator; +import org.apache.fineract.portfolio.savings.DepositAccountType; +import org.apache.fineract.portfolio.savings.DepositsApiConstants; +import org.apache.fineract.portfolio.savings.SavingsApiConstants; +import org.apache.fineract.portfolio.savings.data.DepositAccountData; +import org.apache.fineract.portfolio.savings.data.RecurringDepositAccountData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; +import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalculationPlatformService; +import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService; +import org.apache.fineract.portfolio.savings.service.RecurringDepositAccountInterestCalculationService; +import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +@Path("/v1/recurringdepositaccounts") +@Component +@Tag(name = "Recurring Deposit Account", description = "Recurring Deposit accounts are instances of a praticular recurring deposit product created. An application process around the creation of accounts is also supported.") +@RequiredArgsConstructor +public class RecurringDepositAccountsApiResource { + + private final DepositAccountReadPlatformService depositAccountReadPlatformService; + private final PlatformSecurityContext context; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService; + private final FromJsonHelper fromJsonHelper; + private final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService; + private final BulkImportWorkbookService bulkImportWorkbookService; + private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; + private final RecurringDepositAccountInterestCalculationService recurringDepositAccountInterestCalculationService; + private final SqlValidator sqlValidator; + + @GET + @Path("template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve recurring Deposit Account Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for recurring deposit applications. The template data returned consists of any or all of:\n" + + "\n" + "Field Defaults\n" + "Allowed Value Lists\n\n" + "Example Requests:\n" + "\n" + + "recurringdepositaccounts/template?clientId=1\n" + "\n" + "\n" + "recurringdepositaccounts/template?clientId=1&productId=1") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.GetRecurringDepositAccountsTemplateResponse.class))) }) + public String template(@QueryParam("clientId") @Parameter(description = "clientId") final Long clientId, + @QueryParam("groupId") @Parameter(description = "groupId") final Long groupId, + @QueryParam("productId") @Parameter(description = "productId") final Long productId, + @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, + @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + + final DepositAccountData account = this.depositAccountReadPlatformService.retrieveTemplate(DepositAccountType.RECURRING_DEPOSIT, + clientId, groupId, productId, staffInSelectedOfficeOnly); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, account, + DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "List Recurring deposit applications/accounts", description = "Lists Recurring deposit applications/accounts\n\n" + + "Example Requests:\n" + "\n" + "recurringdepositaccounts\n" + "\n" + "\n" + "recurringdepositaccounts?fields=name") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.GetRecurringDepositAccountsResponse.class)))) }) + public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("paged") @Parameter(description = "paged") final Boolean paged, + @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("accountNo") @Parameter(description = "accountNo") final String accountNo, + @QueryParam("clientName") @Parameter(description = "clientName") final String clientName) { + + this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + sqlValidator.validate(orderBy); + sqlValidator.validate(sortOrder); + final PaginationParameters paginationParameters = PaginationParameters.builder().paged(Boolean.TRUE.equals(paged)).limit(limit) + .offset(offset).orderBy(orderBy).sortOrder(sortOrder).build(); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + if (paginationParameters.isPaged()) { + final Page account = this.depositAccountReadPlatformService + .retrieveAllPaged(DepositAccountType.RECURRING_DEPOSIT, paginationParameters, accountNo, clientName); + return this.toApiJsonSerializer.serialize(settings, account, + DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + final Collection account = this.depositAccountReadPlatformService + .retrieveAll(DepositAccountType.RECURRING_DEPOSIT, paginationParameters); + + return this.toApiJsonSerializer.serialize(settings, account, + DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Submit new recurring deposit application", description = "Submits new recurring deposit application\n\n" + + "Mandatory Fields: clientId or groupId, productId, submittedOnDate, depositAmount, depositPeriod, depositPeriodFrequencyId\n\n" + + "Optional Fields: accountNo, externalId, fieldOfficerId,linkAccountId(if provided initial deposit amount will be collected from this account),transferInterestToSavings(By enabling this flag all interest postings will be transferred to linked saving account )\n\n" + + "Inherited from Product (if not provided): interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, lockinPeriodFrequencyType, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnTypeId, charts, withHoldTax") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PostRecurringDepositAccountsRequest.class))) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PostRecurringDepositAccountsResponse.class))) }) + public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().createRecurringDepositAccount().withJson(apiRequestBodyAsJson) + .build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + @GET + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve a recurring deposit application/account", description = "Retrieves a recurring deposit application/account\n\n" + + "Example Requests :\n" + "\n" + "recurringdepositaccounts/1\n" + "\n" + "\n" + "recurringdepositaccounts/1?associations=all") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.GetRecurringDepositAccountsAccountIdResponse.class))) }) + public String retrieveOne(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, + @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, + @DefaultValue("all") @QueryParam("chargeStatus") @Parameter(description = "chargeStatus") final String chargeStatus, + @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + + if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { + throw new UnrecognizedQueryParamException("status", chargeStatus, new Object[] { "all", "active", "inactive" }); + } + + final RecurringDepositAccountData account = (RecurringDepositAccountData) this.depositAccountReadPlatformService + .retrieveOneWithChartSlabs(DepositAccountType.RECURRING_DEPOSIT, accountId); + + final Set mandatoryResponseParameters = new HashSet<>(); + final RecurringDepositAccountData accountTemplate = populateTemplateAndAssociations(accountId, account, staffInSelectedOfficeOnly, + chargeStatus, uriInfo, mandatoryResponseParameters); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), + mandatoryResponseParameters); + return this.toApiJsonSerializer.serialize(settings, accountTemplate, + DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + private RecurringDepositAccountData populateTemplateAndAssociations(final Long accountId, + final RecurringDepositAccountData savingsAccount, final boolean staffInSelectedOfficeOnly, final String chargeStatus, + final UriInfo uriInfo, final Set mandatoryResponseParameters) { + + Collection transactions = null; + Collection charges = null; + + final Set associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); + if (!associationParameters.isEmpty()) { + + if (associationParameters.contains("all")) { + associationParameters.addAll(Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges)); + } + + if (associationParameters.contains(SavingsApiConstants.transactions)) { + mandatoryResponseParameters.add(SavingsApiConstants.transactions); + final Collection currentTransactions = this.depositAccountReadPlatformService + .retrieveAllTransactions(DepositAccountType.RECURRING_DEPOSIT, accountId); + if (!CollectionUtils.isEmpty(currentTransactions)) { + transactions = currentTransactions; + } + } + + if (associationParameters.contains(SavingsApiConstants.charges)) { + mandatoryResponseParameters.add(SavingsApiConstants.charges); + final Collection currentCharges = this.savingsAccountChargeReadPlatformService + .retrieveSavingsAccountCharges(accountId, chargeStatus); + if (!CollectionUtils.isEmpty(currentCharges)) { + charges = currentCharges; + } + } + } + + RecurringDepositAccountData templateData = null; + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + if (settings.isTemplate()) { + templateData = (RecurringDepositAccountData) this.depositAccountReadPlatformService.retrieveTemplate( + DepositAccountType.RECURRING_DEPOSIT, savingsAccount.getClientId(), savingsAccount.getGroupId(), + savingsAccount.getDepositProductId(), staffInSelectedOfficeOnly); + } + + return RecurringDepositAccountData.withTemplateOptions(savingsAccount, templateData, transactions, charges); + } + + @PUT + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Modify a recurring deposit application", description = "Recurring deposit application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PutRecurringDepositAccountsAccountIdRequest.class))) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PutRecurringDepositAccountsAccountIdResponse.class))) }) + public String update(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, + @Parameter(hidden = true) final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateRecurringDepositAccount(accountId) + .withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + @POST + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Approve recurring deposit application | Undo approval recurring deposit application | Reject recurring deposit application | Withdraw recurring deposit application | Activate a recurring deposit account | Update the recommended deposit amount for a recurring deposit account | Close a recurring deposit account | Premature Close a recurring deposit account | Calculate Premature amount on Recurring deposit account | Calculate Interest on recurring Deposit Account | Post Interest on recurring Deposit Account", description = "Approve recurring deposit application:\n\n" + + "Approves recurring deposit application so long as its in 'Submitted and pending approval' state.\n\n" + + "Undo approval recurring deposit application:\n\n" + + "Will move 'approved' recurring deposit application back to 'Submitted and pending approval' state.\n\n" + + "Reject recurring deposit application\n\n" + + "Rejects recurring deposit application so long as its in 'Submitted and pending approval' state.\n\n" + + "Withdraw recurring deposit application:\n\n" + + "Used when an applicant withdraws from the recurring deposit application. It must be in 'Submitted and pending approval' state.\n\n" + + "Activate a recurring deposit account:\n\n" + + "Results in an approved recurring deposit application being converted into an 'active' recurring deposit account.\n\n" + + "Update the recommended deposit amount for a recurring deposit account:\n\n" + + "Updates the recommended deposit amount for a RD account as on the effective date.\n\n" + + "Close a recurring deposit account\n\n" + + "Results in a Matured recurring deposit account being converted into a 'closed' recurring deposit account.\n" + "\n" + + "On account close allowed actions are." + "Premature Close a recurring deposit account:\n\n" + + "Results in an Active recurring deposit account being converted into a 'Premature Closed' recurring deposit account with options to withdraw prematured amount. (premature amount is calculated using interest rate chart applicable along with penal interest if any.)\n" + + "\n" + "On account premature closure allowed actions are.\n\n" + + "Calculate Premature amount on Recurring deposit account:\n\n" + + "Calculate premature amount on recurring deposit till premature close date. Premature amount is calculated based on interest chart and penal interest applicable if any.\n\n" + + "Calculate Interest on recurring Deposit Account:\n\n" + + "Calculates interest earned on a recurring deposit account based on todays date. It does not attempt to post or credit the interest on the account. That is responsibility of the Post Interest API that will likely be called by overnight process.\n\n" + + "Post Interest on recurring Deposit Account:\n\n" + + "Calculates and Posts interest earned on a recurring deposit account based on todays date and whether an interest posting or crediting event is due.\n\n" + + "Showing request/response for 'Post Interest on recurring Deposit Account'") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PostRecurringDepositAccountsAccountIdRequest.class))) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PostRecurringDepositAccountsAccountIdResponse.class))) }) + public String handleCommands(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, + @QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo, + @Parameter(hidden = true) final String apiRequestBodyAsJson) { + + String jsonApiRequest = apiRequestBodyAsJson; + if (StringUtils.isBlank(jsonApiRequest)) { + jsonApiRequest = "{}"; + } + + final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); + + CommandProcessingResult result = null; + if (is(commandParam, "reject")) { + final CommandWrapper commandRequest = builder.rejectRecurringDepositAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "withdrawnByApplicant")) { + final CommandWrapper commandRequest = builder.withdrawRecurringDepositAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "approve")) { + final CommandWrapper commandRequest = builder.approveRecurringDepositAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "undoapproval")) { + final CommandWrapper commandRequest = builder.undoRecurringDepositAccountApplication(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "activate")) { + final CommandWrapper commandRequest = builder.recurringDepositAccountActivation(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "calculateInterest")) { + final CommandWrapper commandRequest = builder.withNoJsonBody().recurringDepositAccountInterestCalculation(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, DepositsApiConstants.UPDATE_DEPOSIT_AMOUNT)) { + final CommandWrapper commandRequest = builder.updateDepositAmountForRecurringDepositAccount(accountId) + .withJson(apiRequestBodyAsJson).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "postInterest")) { + final CommandWrapper commandRequest = builder.recurringDepositAccountInterestPosting(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "close")) { + final CommandWrapper commandRequest = builder.closeRecurringDepositAccount(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "prematureClose")) { + final CommandWrapper commandRequest = builder.prematureCloseRecurringDepositAccount(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "calculatePrematureAmount")) { + final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson); + final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper); + final DepositAccountData account = this.accountPreMatureCalculationPlatformService.calculatePreMatureAmount(accountId, query, + DepositAccountType.RECURRING_DEPOSIT); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, account, + DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + if (result == null) { + throw new UnrecognizedQueryParamException("command", commandParam, + new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", + "postInterest", "close", "prematureClose", "calculatePrematureAmount" }); + } + + return this.toApiJsonSerializer.serialize(result); + } + + @POST + @Path("calculate-rd-interest") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String calculateRecurringDepositInterest(@Context final UriInfo uriInfo, final String apiRequestBodyAsJson) { + JsonElement jsonElement = fromJsonHelper.parse(apiRequestBodyAsJson); + HashMap result = recurringDepositAccountInterestCalculationService + .calculateInterest(new JsonQuery(apiRequestBodyAsJson, jsonElement, fromJsonHelper)); + + return toApiJsonSerializer.serializeResult(result); + } + + private boolean is(final String commandParam, final String commandValue) { + return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); + } + + @DELETE + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Delete a recurring deposit application", description = "At present we support hard delete of recurring deposit application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the recurring deposit account.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.DeleteRecurringDepositAccountsResponse.class))) }) + public String delete(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteRecurringDepositAccount(accountId).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + @GET + @Path("{accountId}/template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String accountClosureTemplate(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, + @QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + DepositAccountData account = null; + if (is(commandParam, "close")) { + account = this.depositAccountReadPlatformService.retrieveOneWithClosureTemplate(DepositAccountType.RECURRING_DEPOSIT, + accountId); + + } + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, account, + DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + @GET + @Path("downloadtemplate") + @Produces("application/vnd.ms-excel") + public Response getRecurringDepositTemplate(@QueryParam("officeId") final Long officeId, @QueryParam("staffId") final Long staffId, + @QueryParam("dateFormat") final String dateFormat) { + return bulkImportWorkbookPopulatorService.getTemplate(GlobalEntityType.RECURRING_DEPOSIT_ACCOUNTS.toString(), officeId, staffId, + dateFormat); + } + + @POST + @Path("uploadtemplate") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @RequestBody(description = "Upload recurring deposit template", content = { + @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) }) + public String postRecurringDepositTemplate(@FormDataParam("file") InputStream uploadedInputStream, + @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("locale") final String locale, + @FormDataParam("dateFormat") final String dateFormat) { + final Long importDocumentId = this.bulkImportWorkbookService.importWorkbook(GlobalEntityType.RECURRING_DEPOSIT_ACCOUNTS.toString(), + uploadedInputStream, fileDetail, locale, dateFormat); + return this.toApiJsonSerializer.serialize(importDocumentId); + } + + @GET + @Path("transactions/downloadtemplate") + @Produces("application/vnd.ms-excel") + public Response getRecurringDepositTransactionTemplate(@QueryParam("officeId") final Long officeId, + @QueryParam("dateFormat") final String dateFormat) { + return bulkImportWorkbookPopulatorService.getTemplate(GlobalEntityType.RECURRING_DEPOSIT_ACCOUNTS_TRANSACTIONS.toString(), officeId, + null, dateFormat); + } + + @POST + @Path("transactions/uploadtemplate") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @RequestBody(description = "Upload recurring deposit transactions template", content = { + @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) }) + public String postRecurringDepositTransactionsTemplate(@FormDataParam("file") InputStream uploadedInputStream, + @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("locale") final String locale, + @FormDataParam("dateFormat") final String dateFormat) { + final Long importDocumentId = this.bulkImportWorkbookService.importWorkbook( + GlobalEntityType.RECURRING_DEPOSIT_ACCOUNTS_TRANSACTIONS.toString(), uploadedInputStream, fileDetail, locale, dateFormat); + return this.toApiJsonSerializer.serialize(importDocumentId); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java index 2630c5351..2107cba03 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java @@ -1,643 +1,646 @@ -/** - * 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.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.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.DefaultValue; -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.Response; -import jakarta.ws.rs.core.UriInfo; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Collection; -import java.util.Set; -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; -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.bulkimport.data.GlobalEntityType; -import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookPopulatorService; -import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookService; -import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; -import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.data.UploadRequest; -import org.apache.fineract.infrastructure.core.domain.ExternalId; -import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; -import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; -import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; -import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; -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.savings.DepositAccountType; -import org.apache.fineract.portfolio.savings.SavingsApiConstants; -import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; -import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException; -import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService; -import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService; -import org.apache.fineract.portfolio.savings.service.SavingsAccountTemplateReadPlatformService; -import org.glassfish.jersey.media.multipart.FormDataContentDisposition; -import org.glassfish.jersey.media.multipart.FormDataParam; -import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; - -@Path("/v1/savingsaccounts") -@Component -@Tag(name = "Savings Account", description = "Savings accounts are instances of a particular savings product created for an individual or group. An application process around the creation of accounts is also supported.") -@RequiredArgsConstructor -public class SavingsAccountsApiResource { - - private final SavingsAccountReadPlatformService savingsAccountReadPlatformService; - private final SavingsAccountTemplateReadPlatformService savingsAccountTemplateReadPlatformService; - private final PlatformSecurityContext context; - private final DefaultToApiJsonSerializer toApiJsonSerializer; - private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; - private final ApiRequestParameterHelper apiRequestParameterHelper; - private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService; - private final BulkImportWorkbookService bulkImportWorkbookService; - private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; - private final SqlValidator sqlValidator; - - @GET - @Path("template") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Savings Account 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\n" + "Example Requests:\n" + "\n" + "savingsaccounts/template?clientId=1\n" - + "\n" + "\n" + "savingsaccounts/template?clientId=1&productId=1") - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.GetSavingsAccountsTemplateResponse.class))) - public String template(@QueryParam("clientId") @Parameter(description = "clientId") final Long clientId, - @QueryParam("groupId") @Parameter(description = "groupId") final Long groupId, - @QueryParam("productId") @Parameter(description = "productId") final Long productId, - @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @Context final UriInfo uriInfo) { - - context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); - - final SavingsAccountData savingsAccount = savingsAccountTemplateReadPlatformService.retrieveTemplate(clientId, groupId, productId, - staffInSelectedOfficeOnly); - - final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - return toApiJsonSerializer.serialize(settings, savingsAccount, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - @GET - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List savings applications/accounts", description = "Lists savings applications/accounts\n\n" - + "Example Requests:\n" + "\n" + "savingsaccounts\n" + "\n" + "\n" + "savingsaccounts?fields=name") - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.GetSavingsAccountsResponse.class))) - public String retrieveAll(@Context final UriInfo uriInfo, - @QueryParam("externalId") @Parameter(description = "externalId") final String externalId, - // @QueryParam("underHierarchy") final String hierarchy, - @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(SavingsApiConstants.SAVINGS_ACCOUNT_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 Page products = savingsAccountReadPlatformService.retrieveAll(searchParameters); - - final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - return toApiJsonSerializer.serialize(settings, products, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS); - } - - @POST - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Submit new savings application", description = "Submits new savings application\n\n" - + "Mandatory Fields: clientId or groupId, productId, submittedOnDate\n\n" - + "Optional Fields: accountNo, externalId, fieldOfficerId\n\n" - + "Inherited from Product (if not provided): nominalAnnualInterestRate, interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, allowOverdraft, overdraftLimit, withHoldTax\n\n" - + "Additional Mandatory Field if Entity-Datatable Check is enabled for the entity of type Savings: datatables") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsRequest.class))) - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsResponse.class))) - public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) { - - final CommandWrapper commandRequest = new CommandWrapperBuilder().createSavingsAccount().withJson(apiRequestBodyAsJson).build(); - - final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return toApiJsonSerializer.serialize(result); - } - - @POST - @Path("/gsim") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - public String submitGSIMApplication(final String apiRequestBodyAsJson) { - - final CommandWrapper commandRequest = new CommandWrapperBuilder().createGSIMAccount().withJson(apiRequestBodyAsJson).build(); - - final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return toApiJsonSerializer.serialize(result); - } - - @GET - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - public SavingsAccountData retrieveOne(@PathParam("accountId") final Long accountId, - @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, - @QueryParam("associations") final String associations, @Context final UriInfo uriInfo) { - - return retrieveSavingAccount(accountId, null, staffInSelectedOfficeOnly, chargeStatus, uriInfo); - } - - @GET - @Path("/external-id/{externalId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - public SavingsAccountData retrieveOne(@PathParam("externalId") final String externalId, - @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, - @QueryParam("associations") final String associations, @Context final UriInfo uriInfo) { - - return retrieveSavingAccount(null, externalId, staffInSelectedOfficeOnly, chargeStatus, uriInfo); - } - - @PUT - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Modify a savings application | Modify savings account withhold tax applicability", description = "Modify a savings application:\n\n" - + "Savings application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc\n\n" - + "Modify savings account withhold tax applicability:\n\n" - + "Savings application's withhold tax can be modified when in 'Active' state. Once the application is activated, can modify the account withhold tax to post tax or vice-versa" - + "Showing request/response for 'Modify a savings application'") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PutSavingsAccountsAccountIdRequest.class))) - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PutSavingsAccountsAccountIdResponse.class))) - public String update(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, - @Parameter(hidden = true) final String apiRequestBodyAsJson, - @QueryParam("command") @Parameter(description = "command") final String commandParam) { - - return updateSavingAccount(accountId, null, apiRequestBodyAsJson, commandParam); - } - - @PUT - @Path("/external-id/{externalId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Modify a savings application | Modify savings account withhold tax applicability", description = "Modify a savings application:\n\n" - + "Savings application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc\n\n" - + "Modify savings account withhold tax applicability:\n\n" - + "Savings application's withhold tax can be modified when in 'Active' state. Once the application is activated, can modify the account withhold tax to post tax or vice-versa" - + "Showing request/response for 'Modify a savings application'") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PutSavingsAccountsAccountIdRequest.class))) - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PutSavingsAccountsAccountIdResponse.class))) - public String update(@PathParam("externalId") @Parameter(description = "externalId") final String externalId, - @Parameter(hidden = true) final String apiRequestBodyAsJson, - @QueryParam("command") @Parameter(description = "command") final String commandParam) { - - return updateSavingAccount(null, externalId, apiRequestBodyAsJson, commandParam); - } - - @PUT - @Path("/gsim/{parentAccountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - public String updateGsim(@PathParam("parentAccountId") final Long parentAccountId, final String apiRequestBodyAsJson) { - final CommandWrapper commandRequest = new CommandWrapperBuilder().updateGSIMAccount(parentAccountId).withJson(apiRequestBodyAsJson) - .build(); - - final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return toApiJsonSerializer.serialize(result); - } - - @POST - @Path("/gsimcommands/{parentAccountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - public String handleGSIMCommands(@PathParam("parentAccountId") final Long parentAccountId, - @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) { - - String jsonApiRequest = apiRequestBodyAsJson; - if (StringUtils.isBlank(jsonApiRequest)) { - jsonApiRequest = "{}"; - } - - final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); - - CommandProcessingResult result = null; - if (is(commandParam, "reject")) { - final CommandWrapper commandRequest = builder.rejectGSIMAccountApplication(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "withdrawnByApplicant")) { - final CommandWrapper commandRequest = builder.withdrawSavingsAccountApplication(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "approve")) { - final CommandWrapper commandRequest = builder.approveGSIMAccountApplication(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "undoapproval")) { - final CommandWrapper commandRequest = builder.undoGSIMApplicationApproval(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "activate")) { - final CommandWrapper commandRequest = builder.gsimAccountActivation(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "calculateInterest")) { - final CommandWrapper commandRequest = builder.withNoJsonBody().savingsAccountInterestCalculation(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "postInterest")) { - final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "applyAnnualFees")) { - final CommandWrapper commandRequest = builder.savingsAccountApplyAnnualFees(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "close")) { - final CommandWrapper commandRequest = builder.closeGSIMApplication(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } - if (result == null) { - throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", - "postInterest", "close", "assignSavingsOfficer", "unassignSavingsOfficer", - SavingsApiConstants.COMMAND_BLOCK_DEBIT, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT, - SavingsApiConstants.COMMAND_BLOCK_CREDIT, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT, - SavingsApiConstants.COMMAND_BLOCK_ACCOUNT, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT }); - } - - return toApiJsonSerializer.serialize(result); - } - - @POST - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Approve savings application | Undo approval savings application | Assign Savings Officer | Unassign Savings Officer | Reject savings application | Withdraw savings application | Activate a savings account | Close a savings account | Calculate Interest on Savings Account | Post Interest on Savings Account | Block Savings Account | Unblock Savings Account | Block Savings Account Credit transactions | Unblock Savings Account Credit transactions | Block Savings Account Debit transactions | Unblock Savings Account debit transactions", description = "Approve savings application:\n\n" - + "Approves savings application so long as its in 'Submitted and pending approval' state.\n\n" - + "Undo approval savings application:\n\n" - + "Will move 'approved' savings application back to 'Submitted and pending approval' state.\n\n" + "Assign Savings Officer:\n\n" - + "Allows you to assign Savings Officer for existing Savings Account.\n\n" + "Unassign Savings Officer:\n\n" - + "Allows you to unassign the Savings Officer.\n\n" + "Reject savings application:\n\n" - + "Rejects savings application so long as its in 'Submitted and pending approval' state.\n\n" - + "Withdraw savings application:\n\n" - + "Used when an applicant withdraws from the savings application. It must be in 'Submitted and pending approval' state.\n\n" - + "Activate a savings account:\n\n" - + "Results in an approved savings application being converted into an 'active' savings account.\n\n" - + "Close a savings account:\n\n" - + "Results in an Activated savings application being converted into an 'closed' savings account.\n" + "\n" - + "closedOnDate is closure date of savings account\n" + "\n" - + "withdrawBalance is a boolean description, true value of this field performs a withdrawal transaction with account's running balance.\n\n" - + "Mandatory Fields: dateFormat,locale,closedOnDate\n\n" - + "Optional Fields: note, withdrawBalance, paymentTypeId, accountNumber, checkNumber, routingCode, receiptNumber, bankNumber\n\n" - + "Calculate Interest on Savings Account:\n\n" - + "Calculates interest earned on a savings account based on todays date. It does not attempt to post or credit the interest on the account. That is responsibility of the Post Interest API that will likely be called by overnight process.\n\n" - + "Post Interest on Savings Account:\n\n" - + "Calculates and Posts interest earned on a savings account based on today's date and whether an interest posting or crediting event is due.\n\n" - + "Block Savings Account:\n\n" + "Blocks Savings account from all types of credit and debit transactions\n\n" - + "Unblock Savings Account:\n\n" - + "Unblock a blocked account. On unblocking account, user can perform debit and credit transactions\n\n" - + "Block Savings Account Credit transactions:\n\n" - + "Savings account will be blocked from all types of credit transactions.\n\n" - + "Unblock Savings Account Credit transactions:\n\n" - + "It unblocks the Saving account's credit operations. Now all types of credits can be transacted to Savings account\n\n" - + "Block Savings Account Debit transactions:\n\n" + "All types of debit operations from Savings account wil be blocked\n\n" - + "Unblock Savings Account debit transactions:\n\n" - + "It unblocks the Saving account's debit operations. Now all types of debits can be transacted from Savings account\n\n" - + "Showing request/response for 'Unassign Savings Officer'") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsAccountIdRequest.class))) - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsAccountIdResponse.class))) - public String handleCommands(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, - @QueryParam("command") @Parameter(description = "command") final String commandParam, - @Parameter(hidden = true) final String apiRequestBodyAsJson) { - - return handleCommands(accountId, null, commandParam, apiRequestBodyAsJson); - } - - @POST - @Path("/external-id/{externalId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Approve savings application | Undo approval savings application | Assign Savings Officer | Unassign Savings Officer | Reject savings application | Withdraw savings application | Activate a savings account | Close a savings account | Calculate Interest on Savings Account | Post Interest on Savings Account | Block Savings Account | Unblock Savings Account | Block Savings Account Credit transactions | Unblock Savings Account Credit transactions | Block Savings Account Debit transactions | Unblock Savings Account debit transactions", description = "Approve savings application:\n\n" - + "Approves savings application so long as its in 'Submitted and pending approval' state.\n\n" - + "Undo approval savings application:\n\n" - + "Will move 'approved' savings application back to 'Submitted and pending approval' state.\n\n" + "Assign Savings Officer:\n\n" - + "Allows you to assign Savings Officer for existing Savings Account.\n\n" + "Unassign Savings Officer:\n\n" - + "Allows you to unassign the Savings Officer.\n\n" + "Reject savings application:\n\n" - + "Rejects savings application so long as its in 'Submitted and pending approval' state.\n\n" - + "Withdraw savings application:\n\n" - + "Used when an applicant withdraws from the savings application. It must be in 'Submitted and pending approval' state.\n\n" - + "Activate a savings account:\n\n" - + "Results in an approved savings application being converted into an 'active' savings account.\n\n" - + "Close a savings account:\n\n" - + "Results in an Activated savings application being converted into an 'closed' savings account.\n" + "\n" - + "closedOnDate is closure date of savings account\n" + "\n" - + "withdrawBalance is a boolean description, true value of this field performs a withdrawal transaction with account's running balance.\n\n" - + "Mandatory Fields: dateFormat,locale,closedOnDate\n\n" - + "Optional Fields: note, withdrawBalance, paymentTypeId, accountNumber, checkNumber, routingCode, receiptNumber, bankNumber\n\n" - + "Calculate Interest on Savings Account:\n\n" - + "Calculates interest earned on a savings account based on todays date. It does not attempt to post or credit the interest on the account. That is responsibility of the Post Interest API that will likely be called by overnight process.\n\n" - + "Post Interest on Savings Account:\n\n" - + "Calculates and Posts interest earned on a savings account based on today's date and whether an interest posting or crediting event is due.\n\n" - + "Block Savings Account:\n\n" + "Blocks Savings account from all types of credit and debit transactions\n\n" - + "Unblock Savings Account:\n\n" - + "Unblock a blocked account. On unblocking account, user can perform debit and credit transactions\n\n" - + "Block Savings Account Credit transactions:\n\n" - + "Savings account will be blocked from all types of credit transactions.\n\n" - + "Unblock Savings Account Credit transactions:\n\n" - + "It unblocks the Saving account's credit operations. Now all types of credits can be transacted to Savings account\n\n" - + "Block Savings Account Debit transactions:\n\n" + "All types of debit operations from Savings account wil be blocked\n\n" - + "Unblock Savings Account debit transactions:\n\n" - + "It unblocks the Saving account's debit operations. Now all types of debits can be transacted from Savings account\n\n" - + "Showing request/response for 'Unassign Savings Officer'") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsAccountIdRequest.class))) - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsAccountIdResponse.class))) - public String handleCommands(@PathParam("externalId") @Parameter(description = "externalId") final String externalId, - @QueryParam("command") @Parameter(description = "command") final String commandParam, - @Parameter(hidden = true) final String apiRequestBodyAsJson) { - - return handleCommands(null, externalId, commandParam, apiRequestBodyAsJson); - } - - private boolean is(final String commandParam, final String commandValue) { - return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); - } - - @DELETE - @Path("{accountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a savings application", description = "At present we support hard delete of savings application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the savings account.") - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.DeleteSavingsAccountsAccountIdResponse.class))) - public String delete(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId) { - - return deleteSavingAccount(accountId, null); - } - - @DELETE - @Path("/external-id/{externalId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a savings application", description = "At present we support hard delete of savings application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the savings account.") - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.DeleteSavingsAccountsAccountIdResponse.class))) - public String delete(@PathParam("externalId") @Parameter(description = "externalId") final String externalId) { - - return deleteSavingAccount(null, externalId); - } - - @GET - @Path("downloadtemplate") - @Produces("application/vnd.ms-excel") - public Response getSavingsTemplate(@QueryParam("officeId") final Long officeId, @QueryParam("staffId") final Long staffId, - @QueryParam("dateFormat") final String dateFormat) { - return bulkImportWorkbookPopulatorService.getTemplate(GlobalEntityType.SAVINGS_ACCOUNT.toString(), officeId, staffId, dateFormat); - } - - @POST - @Path("uploadtemplate") - @Consumes(MediaType.MULTIPART_FORM_DATA) - @RequestBody(description = "Upload savings template", content = { - @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) }) - public String postSavingsTemplate(@FormDataParam("file") InputStream uploadedInputStream, - @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("locale") final String locale, - @FormDataParam("dateFormat") final String dateFormat) { - final Long importDocumentId = bulkImportWorkbookService.importWorkbook(GlobalEntityType.SAVINGS_ACCOUNT.toString(), - uploadedInputStream, fileDetail, locale, dateFormat); - return toApiJsonSerializer.serialize(importDocumentId); - } - - @GET - @Path("transactions/downloadtemplate") - @Produces("application/vnd.ms-excel") - public Response getSavingsTransactionTemplate(@QueryParam("officeId") final Long officeId, - @QueryParam("dateFormat") final String dateFormat) { - return bulkImportWorkbookPopulatorService.getTemplate(GlobalEntityType.SAVINGS_TRANSACTIONS.toString(), officeId, null, dateFormat); - } - - @POST - @Path("transactions/uploadtemplate") - @Consumes(MediaType.MULTIPART_FORM_DATA) - @RequestBody(description = "Upload savings transaction template", content = { - @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) }) - public String postSavingsTransactionTemplate(@FormDataParam("file") InputStream uploadedInputStream, - @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("locale") final String locale, - @FormDataParam("dateFormat") final String dateFormat) { - final Long importDocumentId = bulkImportWorkbookService.importWorkbook(GlobalEntityType.SAVINGS_TRANSACTIONS.toString(), - uploadedInputStream, fileDetail, locale, dateFormat); - return toApiJsonSerializer.serialize(importDocumentId); - } - - private SavingsAccountData retrieveSavingAccount(Long accountId, String externalId, boolean staffInSelectedOfficeOnly, - String chargeStatus, UriInfo uriInfo) { - context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); - - if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { - throw new UnrecognizedQueryParamException("status", chargeStatus, new Object[] { "all", "active", "inactive" }); - } - - ExternalId accountExternalId = ExternalIdFactory.produce(externalId); - accountId = getResolvedAccountId(accountId, accountExternalId); - final SavingsAccountData savingsAccount = savingsAccountReadPlatformService.retrieveOne(accountId); - - return populateTemplateAndAssociations(accountId, savingsAccount, staffInSelectedOfficeOnly, chargeStatus, uriInfo); - } - - private String updateSavingAccount(Long accountId, String externalId, String apiRequestBodyAsJson, String commandParam) { - ExternalId accountExternalId = ExternalIdFactory.produce(externalId); - accountId = getResolvedAccountId(accountId, accountExternalId); - - if (is(commandParam, "updateWithHoldTax")) { - final CommandWrapper commandRequest = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson).updateWithHoldTax(accountId) - .build(); - final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - return toApiJsonSerializer.serialize(result); - } - - final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSavingsAccount(accountId).withJson(apiRequestBodyAsJson) - .build(); - - final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return toApiJsonSerializer.serialize(result); - } - - private String handleCommands(Long accountId, String externalId, String commandParam, String apiRequestBodyAsJson) { - ExternalId accountExternalId = ExternalIdFactory.produce(externalId); - accountId = getResolvedAccountId(accountId, accountExternalId); - - String jsonApiRequest = apiRequestBodyAsJson; - if (StringUtils.isBlank(jsonApiRequest)) { - jsonApiRequest = "{}"; - } - - final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); - - CommandProcessingResult result = null; - if (is(commandParam, "reject")) { - final CommandWrapper commandRequest = builder.rejectSavingsAccountApplication(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "withdrawnByApplicant")) { - final CommandWrapper commandRequest = builder.withdrawSavingsAccountApplication(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "approve")) { - final CommandWrapper commandRequest = builder.approveSavingsAccountApplication(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "undoapproval")) { - final CommandWrapper commandRequest = builder.undoSavingsAccountApplication(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "activate")) { - final CommandWrapper commandRequest = builder.savingsAccountActivation(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "calculateInterest")) { - final CommandWrapper commandRequest = builder.withNoJsonBody().savingsAccountInterestCalculation(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "postInterest")) { - final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "applyAnnualFees")) { - final CommandWrapper commandRequest = builder.savingsAccountApplyAnnualFees(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "close")) { - final CommandWrapper commandRequest = builder.closeSavingsAccountApplication(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "assignSavingsOfficer")) { - final CommandWrapper commandRequest = builder.assignSavingsOfficer(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - return toApiJsonSerializer.serialize(result); - } else if (is(commandParam, "unassignSavingsOfficer")) { - final CommandWrapper commandRequest = builder.unassignSavingsOfficer(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - return toApiJsonSerializer.serialize(result); - } else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_DEBIT)) { - final CommandWrapper commandRequest = builder.blockDebitsFromSavingsAccount(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT)) { - final CommandWrapper commandRequest = builder.unblockDebitsFromSavingsAccount(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_CREDIT)) { - final CommandWrapper commandRequest = builder.blockCreditsToSavingsAccount(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT)) { - final CommandWrapper commandRequest = builder.unblockCreditsToSavingsAccount(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_ACCOUNT)) { - final CommandWrapper commandRequest = builder.blockSavingsAccount(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT)) { - final CommandWrapper commandRequest = builder.unblockSavingsAccount(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } - - if (result == null) { - // - throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", - "postInterest", "close", "assignSavingsOfficer", "unassignSavingsOfficer", - SavingsApiConstants.COMMAND_BLOCK_DEBIT, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT, - SavingsApiConstants.COMMAND_BLOCK_CREDIT, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT, - SavingsApiConstants.COMMAND_BLOCK_ACCOUNT, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT }); - } - - return toApiJsonSerializer.serialize(result); - } - - private String deleteSavingAccount(Long accountId, String externalId) { - ExternalId accountExternalId = ExternalIdFactory.produce(externalId); - accountId = getResolvedAccountId(accountId, accountExternalId); - - final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSavingsAccount(accountId).build(); - - final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return toApiJsonSerializer.serialize(result); - } - - private Long getResolvedAccountId(Long accountId, ExternalId accountExternalId) { - Long resolvedAccountId = accountId; - if (resolvedAccountId == null) { - accountExternalId.throwExceptionIfEmpty(); - resolvedAccountId = savingsAccountReadPlatformService.retrieveAccountIdByExternalId(accountExternalId); - if (resolvedAccountId == null) { - throw new SavingsAccountNotFoundException(resolvedAccountId); - } - } - return resolvedAccountId; - } - - private SavingsAccountData populateTemplateAndAssociations(final Long accountId, final SavingsAccountData savingsAccount, - final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo) { - - Collection transactions = null; - Collection charges = null; - - final Set associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); - if (!associationParameters.isEmpty()) { - - if (associationParameters.contains("all")) { - associationParameters.addAll(Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges)); - } - - if (associationParameters.contains(SavingsApiConstants.transactions)) { - final Collection currentTransactions = savingsAccountReadPlatformService - .retrieveAllTransactions(accountId, DepositAccountType.SAVINGS_DEPOSIT); - if (!CollectionUtils.isEmpty(currentTransactions)) { - transactions = currentTransactions; - } - } - - if (associationParameters.contains(SavingsApiConstants.charges)) { - final Collection currentCharges = savingsAccountChargeReadPlatformService - .retrieveSavingsAccountCharges(accountId, chargeStatus); - if (!CollectionUtils.isEmpty(currentCharges)) { - charges = currentCharges; - } - } - } - - SavingsAccountData templateData = null; - final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - if (settings.isTemplate()) { - templateData = savingsAccountTemplateReadPlatformService.retrieveTemplate(savingsAccount.getClientId(), - savingsAccount.getGroupId(), savingsAccount.getSavingsProductId(), staffInSelectedOfficeOnly); - } - - return SavingsAccountData.withTemplateOptions(savingsAccount, templateData, transactions, charges); - } -} +/** + * 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.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.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.DefaultValue; +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.Response; +import jakarta.ws.rs.core.UriInfo; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +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.bulkimport.data.GlobalEntityType; +import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookPopulatorService; +import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookService; +import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; +import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.UploadRequest; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; +import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +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.savings.DepositAccountType; +import org.apache.fineract.portfolio.savings.SavingsApiConstants; +import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; +import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException; +import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService; +import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService; +import org.apache.fineract.portfolio.savings.service.SavingsAccountTemplateReadPlatformService; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +@Path("/v1/savingsaccounts") +@Component +@Tag(name = "Savings Account", description = "Savings accounts are instances of a particular savings product created for an individual or group. An application process around the creation of accounts is also supported.") +@RequiredArgsConstructor +public class SavingsAccountsApiResource { + + private final SavingsAccountReadPlatformService savingsAccountReadPlatformService; + private final SavingsAccountTemplateReadPlatformService savingsAccountTemplateReadPlatformService; + private final PlatformSecurityContext context; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService; + private final BulkImportWorkbookService bulkImportWorkbookService; + private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; + private final SqlValidator sqlValidator; + + @GET + @Path("template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve Savings Account 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\n" + "Example Requests:\n" + "\n" + "savingsaccounts/template?clientId=1\n" + + "\n" + "\n" + "savingsaccounts/template?clientId=1&productId=1") + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.GetSavingsAccountsTemplateResponse.class))) + public String template(@QueryParam("clientId") @Parameter(description = "clientId") final Long clientId, + @QueryParam("groupId") @Parameter(description = "groupId") final Long groupId, + @QueryParam("productId") @Parameter(description = "productId") final Long productId, + @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, + @Context final UriInfo uriInfo) { + + context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); + + final SavingsAccountData savingsAccount = savingsAccountTemplateReadPlatformService.retrieveTemplate(clientId, groupId, productId, + staffInSelectedOfficeOnly); + + final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return toApiJsonSerializer.serialize(settings, savingsAccount, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "List savings applications/accounts", description = "Lists savings applications/accounts\n\n" + + "Example Requests:\n" + "\n" + "savingsaccounts\n" + "\n" + "\n" + "savingsaccounts?fields=name") + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.GetSavingsAccountsResponse.class))) + public String retrieveAll(@Context final UriInfo uriInfo, + @QueryParam("externalId") @Parameter(description = "externalId") final String externalId, + // @QueryParam("underHierarchy") final String hierarchy, + @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("accountNo") @Parameter(description = "accountNo") final String accountNo, + @QueryParam("clientName") @Parameter(description = "clientName") final String clientName, + @QueryParam("depositType") @Parameter(description = "depositType") final Integer depositType) { + + context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_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).accountNo(accountNo).name(clientName).depositType(depositType).build(); + + final Page products = savingsAccountReadPlatformService.retrieveAll(searchParameters); + + final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return toApiJsonSerializer.serialize(settings, products, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS); + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Submit new savings application", description = "Submits new savings application\n\n" + + "Mandatory Fields: clientId or groupId, productId, submittedOnDate\n\n" + + "Optional Fields: accountNo, externalId, fieldOfficerId\n\n" + + "Inherited from Product (if not provided): nominalAnnualInterestRate, interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, allowOverdraft, overdraftLimit, withHoldTax\n\n" + + "Additional Mandatory Field if Entity-Datatable Check is enabled for the entity of type Savings: datatables") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsRequest.class))) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsResponse.class))) + public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().createSavingsAccount().withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return toApiJsonSerializer.serialize(result); + } + + @POST + @Path("/gsim") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String submitGSIMApplication(final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().createGSIMAccount().withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return toApiJsonSerializer.serialize(result); + } + + @GET + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public SavingsAccountData retrieveOne(@PathParam("accountId") final Long accountId, + @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, + @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, + @QueryParam("associations") final String associations, @Context final UriInfo uriInfo) { + + return retrieveSavingAccount(accountId, null, staffInSelectedOfficeOnly, chargeStatus, uriInfo); + } + + @GET + @Path("/external-id/{externalId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public SavingsAccountData retrieveOne(@PathParam("externalId") final String externalId, + @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, + @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, + @QueryParam("associations") final String associations, @Context final UriInfo uriInfo) { + + return retrieveSavingAccount(null, externalId, staffInSelectedOfficeOnly, chargeStatus, uriInfo); + } + + @PUT + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Modify a savings application | Modify savings account withhold tax applicability", description = "Modify a savings application:\n\n" + + "Savings application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc\n\n" + + "Modify savings account withhold tax applicability:\n\n" + + "Savings application's withhold tax can be modified when in 'Active' state. Once the application is activated, can modify the account withhold tax to post tax or vice-versa" + + "Showing request/response for 'Modify a savings application'") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PutSavingsAccountsAccountIdRequest.class))) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PutSavingsAccountsAccountIdResponse.class))) + public String update(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, + @Parameter(hidden = true) final String apiRequestBodyAsJson, + @QueryParam("command") @Parameter(description = "command") final String commandParam) { + + return updateSavingAccount(accountId, null, apiRequestBodyAsJson, commandParam); + } + + @PUT + @Path("/external-id/{externalId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Modify a savings application | Modify savings account withhold tax applicability", description = "Modify a savings application:\n\n" + + "Savings application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc\n\n" + + "Modify savings account withhold tax applicability:\n\n" + + "Savings application's withhold tax can be modified when in 'Active' state. Once the application is activated, can modify the account withhold tax to post tax or vice-versa" + + "Showing request/response for 'Modify a savings application'") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PutSavingsAccountsAccountIdRequest.class))) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PutSavingsAccountsAccountIdResponse.class))) + public String update(@PathParam("externalId") @Parameter(description = "externalId") final String externalId, + @Parameter(hidden = true) final String apiRequestBodyAsJson, + @QueryParam("command") @Parameter(description = "command") final String commandParam) { + + return updateSavingAccount(null, externalId, apiRequestBodyAsJson, commandParam); + } + + @PUT + @Path("/gsim/{parentAccountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String updateGsim(@PathParam("parentAccountId") final Long parentAccountId, final String apiRequestBodyAsJson) { + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateGSIMAccount(parentAccountId).withJson(apiRequestBodyAsJson) + .build(); + + final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return toApiJsonSerializer.serialize(result); + } + + @POST + @Path("/gsimcommands/{parentAccountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String handleGSIMCommands(@PathParam("parentAccountId") final Long parentAccountId, + @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) { + + String jsonApiRequest = apiRequestBodyAsJson; + if (StringUtils.isBlank(jsonApiRequest)) { + jsonApiRequest = "{}"; + } + + final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); + + CommandProcessingResult result = null; + if (is(commandParam, "reject")) { + final CommandWrapper commandRequest = builder.rejectGSIMAccountApplication(parentAccountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "withdrawnByApplicant")) { + final CommandWrapper commandRequest = builder.withdrawSavingsAccountApplication(parentAccountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "approve")) { + final CommandWrapper commandRequest = builder.approveGSIMAccountApplication(parentAccountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "undoapproval")) { + final CommandWrapper commandRequest = builder.undoGSIMApplicationApproval(parentAccountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "activate")) { + final CommandWrapper commandRequest = builder.gsimAccountActivation(parentAccountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "calculateInterest")) { + final CommandWrapper commandRequest = builder.withNoJsonBody().savingsAccountInterestCalculation(parentAccountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "postInterest")) { + final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(parentAccountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "applyAnnualFees")) { + final CommandWrapper commandRequest = builder.savingsAccountApplyAnnualFees(parentAccountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "close")) { + final CommandWrapper commandRequest = builder.closeGSIMApplication(parentAccountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } + if (result == null) { + throw new UnrecognizedQueryParamException("command", commandParam, + new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", + "postInterest", "close", "assignSavingsOfficer", "unassignSavingsOfficer", + SavingsApiConstants.COMMAND_BLOCK_DEBIT, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT, + SavingsApiConstants.COMMAND_BLOCK_CREDIT, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT, + SavingsApiConstants.COMMAND_BLOCK_ACCOUNT, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT }); + } + + return toApiJsonSerializer.serialize(result); + } + + @POST + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Approve savings application | Undo approval savings application | Assign Savings Officer | Unassign Savings Officer | Reject savings application | Withdraw savings application | Activate a savings account | Close a savings account | Calculate Interest on Savings Account | Post Interest on Savings Account | Block Savings Account | Unblock Savings Account | Block Savings Account Credit transactions | Unblock Savings Account Credit transactions | Block Savings Account Debit transactions | Unblock Savings Account debit transactions", description = "Approve savings application:\n\n" + + "Approves savings application so long as its in 'Submitted and pending approval' state.\n\n" + + "Undo approval savings application:\n\n" + + "Will move 'approved' savings application back to 'Submitted and pending approval' state.\n\n" + "Assign Savings Officer:\n\n" + + "Allows you to assign Savings Officer for existing Savings Account.\n\n" + "Unassign Savings Officer:\n\n" + + "Allows you to unassign the Savings Officer.\n\n" + "Reject savings application:\n\n" + + "Rejects savings application so long as its in 'Submitted and pending approval' state.\n\n" + + "Withdraw savings application:\n\n" + + "Used when an applicant withdraws from the savings application. It must be in 'Submitted and pending approval' state.\n\n" + + "Activate a savings account:\n\n" + + "Results in an approved savings application being converted into an 'active' savings account.\n\n" + + "Close a savings account:\n\n" + + "Results in an Activated savings application being converted into an 'closed' savings account.\n" + "\n" + + "closedOnDate is closure date of savings account\n" + "\n" + + "withdrawBalance is a boolean description, true value of this field performs a withdrawal transaction with account's running balance.\n\n" + + "Mandatory Fields: dateFormat,locale,closedOnDate\n\n" + + "Optional Fields: note, withdrawBalance, paymentTypeId, accountNumber, checkNumber, routingCode, receiptNumber, bankNumber\n\n" + + "Calculate Interest on Savings Account:\n\n" + + "Calculates interest earned on a savings account based on todays date. It does not attempt to post or credit the interest on the account. That is responsibility of the Post Interest API that will likely be called by overnight process.\n\n" + + "Post Interest on Savings Account:\n\n" + + "Calculates and Posts interest earned on a savings account based on today's date and whether an interest posting or crediting event is due.\n\n" + + "Block Savings Account:\n\n" + "Blocks Savings account from all types of credit and debit transactions\n\n" + + "Unblock Savings Account:\n\n" + + "Unblock a blocked account. On unblocking account, user can perform debit and credit transactions\n\n" + + "Block Savings Account Credit transactions:\n\n" + + "Savings account will be blocked from all types of credit transactions.\n\n" + + "Unblock Savings Account Credit transactions:\n\n" + + "It unblocks the Saving account's credit operations. Now all types of credits can be transacted to Savings account\n\n" + + "Block Savings Account Debit transactions:\n\n" + "All types of debit operations from Savings account wil be blocked\n\n" + + "Unblock Savings Account debit transactions:\n\n" + + "It unblocks the Saving account's debit operations. Now all types of debits can be transacted from Savings account\n\n" + + "Showing request/response for 'Unassign Savings Officer'") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsAccountIdRequest.class))) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsAccountIdResponse.class))) + public String handleCommands(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, + @QueryParam("command") @Parameter(description = "command") final String commandParam, + @Parameter(hidden = true) final String apiRequestBodyAsJson) { + + return handleCommands(accountId, null, commandParam, apiRequestBodyAsJson); + } + + @POST + @Path("/external-id/{externalId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Approve savings application | Undo approval savings application | Assign Savings Officer | Unassign Savings Officer | Reject savings application | Withdraw savings application | Activate a savings account | Close a savings account | Calculate Interest on Savings Account | Post Interest on Savings Account | Block Savings Account | Unblock Savings Account | Block Savings Account Credit transactions | Unblock Savings Account Credit transactions | Block Savings Account Debit transactions | Unblock Savings Account debit transactions", description = "Approve savings application:\n\n" + + "Approves savings application so long as its in 'Submitted and pending approval' state.\n\n" + + "Undo approval savings application:\n\n" + + "Will move 'approved' savings application back to 'Submitted and pending approval' state.\n\n" + "Assign Savings Officer:\n\n" + + "Allows you to assign Savings Officer for existing Savings Account.\n\n" + "Unassign Savings Officer:\n\n" + + "Allows you to unassign the Savings Officer.\n\n" + "Reject savings application:\n\n" + + "Rejects savings application so long as its in 'Submitted and pending approval' state.\n\n" + + "Withdraw savings application:\n\n" + + "Used when an applicant withdraws from the savings application. It must be in 'Submitted and pending approval' state.\n\n" + + "Activate a savings account:\n\n" + + "Results in an approved savings application being converted into an 'active' savings account.\n\n" + + "Close a savings account:\n\n" + + "Results in an Activated savings application being converted into an 'closed' savings account.\n" + "\n" + + "closedOnDate is closure date of savings account\n" + "\n" + + "withdrawBalance is a boolean description, true value of this field performs a withdrawal transaction with account's running balance.\n\n" + + "Mandatory Fields: dateFormat,locale,closedOnDate\n\n" + + "Optional Fields: note, withdrawBalance, paymentTypeId, accountNumber, checkNumber, routingCode, receiptNumber, bankNumber\n\n" + + "Calculate Interest on Savings Account:\n\n" + + "Calculates interest earned on a savings account based on todays date. It does not attempt to post or credit the interest on the account. That is responsibility of the Post Interest API that will likely be called by overnight process.\n\n" + + "Post Interest on Savings Account:\n\n" + + "Calculates and Posts interest earned on a savings account based on today's date and whether an interest posting or crediting event is due.\n\n" + + "Block Savings Account:\n\n" + "Blocks Savings account from all types of credit and debit transactions\n\n" + + "Unblock Savings Account:\n\n" + + "Unblock a blocked account. On unblocking account, user can perform debit and credit transactions\n\n" + + "Block Savings Account Credit transactions:\n\n" + + "Savings account will be blocked from all types of credit transactions.\n\n" + + "Unblock Savings Account Credit transactions:\n\n" + + "It unblocks the Saving account's credit operations. Now all types of credits can be transacted to Savings account\n\n" + + "Block Savings Account Debit transactions:\n\n" + "All types of debit operations from Savings account wil be blocked\n\n" + + "Unblock Savings Account debit transactions:\n\n" + + "It unblocks the Saving account's debit operations. Now all types of debits can be transacted from Savings account\n\n" + + "Showing request/response for 'Unassign Savings Officer'") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsAccountIdRequest.class))) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsAccountIdResponse.class))) + public String handleCommands(@PathParam("externalId") @Parameter(description = "externalId") final String externalId, + @QueryParam("command") @Parameter(description = "command") final String commandParam, + @Parameter(hidden = true) final String apiRequestBodyAsJson) { + + return handleCommands(null, externalId, commandParam, apiRequestBodyAsJson); + } + + private boolean is(final String commandParam, final String commandValue) { + return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); + } + + @DELETE + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Delete a savings application", description = "At present we support hard delete of savings application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the savings account.") + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.DeleteSavingsAccountsAccountIdResponse.class))) + public String delete(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId) { + + return deleteSavingAccount(accountId, null); + } + + @DELETE + @Path("/external-id/{externalId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Delete a savings application", description = "At present we support hard delete of savings application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the savings account.") + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.DeleteSavingsAccountsAccountIdResponse.class))) + public String delete(@PathParam("externalId") @Parameter(description = "externalId") final String externalId) { + + return deleteSavingAccount(null, externalId); + } + + @GET + @Path("downloadtemplate") + @Produces("application/vnd.ms-excel") + public Response getSavingsTemplate(@QueryParam("officeId") final Long officeId, @QueryParam("staffId") final Long staffId, + @QueryParam("dateFormat") final String dateFormat) { + return bulkImportWorkbookPopulatorService.getTemplate(GlobalEntityType.SAVINGS_ACCOUNT.toString(), officeId, staffId, dateFormat); + } + + @POST + @Path("uploadtemplate") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @RequestBody(description = "Upload savings template", content = { + @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) }) + public String postSavingsTemplate(@FormDataParam("file") InputStream uploadedInputStream, + @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("locale") final String locale, + @FormDataParam("dateFormat") final String dateFormat) { + final Long importDocumentId = bulkImportWorkbookService.importWorkbook(GlobalEntityType.SAVINGS_ACCOUNT.toString(), + uploadedInputStream, fileDetail, locale, dateFormat); + return toApiJsonSerializer.serialize(importDocumentId); + } + + @GET + @Path("transactions/downloadtemplate") + @Produces("application/vnd.ms-excel") + public Response getSavingsTransactionTemplate(@QueryParam("officeId") final Long officeId, + @QueryParam("dateFormat") final String dateFormat) { + return bulkImportWorkbookPopulatorService.getTemplate(GlobalEntityType.SAVINGS_TRANSACTIONS.toString(), officeId, null, dateFormat); + } + + @POST + @Path("transactions/uploadtemplate") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @RequestBody(description = "Upload savings transaction template", content = { + @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = UploadRequest.class)) }) + public String postSavingsTransactionTemplate(@FormDataParam("file") InputStream uploadedInputStream, + @FormDataParam("file") FormDataContentDisposition fileDetail, @FormDataParam("locale") final String locale, + @FormDataParam("dateFormat") final String dateFormat) { + final Long importDocumentId = bulkImportWorkbookService.importWorkbook(GlobalEntityType.SAVINGS_TRANSACTIONS.toString(), + uploadedInputStream, fileDetail, locale, dateFormat); + return toApiJsonSerializer.serialize(importDocumentId); + } + + private SavingsAccountData retrieveSavingAccount(Long accountId, String externalId, boolean staffInSelectedOfficeOnly, + String chargeStatus, UriInfo uriInfo) { + context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); + + if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { + throw new UnrecognizedQueryParamException("status", chargeStatus, new Object[] { "all", "active", "inactive" }); + } + + ExternalId accountExternalId = ExternalIdFactory.produce(externalId); + accountId = getResolvedAccountId(accountId, accountExternalId); + final SavingsAccountData savingsAccount = savingsAccountReadPlatformService.retrieveOne(accountId); + + return populateTemplateAndAssociations(accountId, savingsAccount, staffInSelectedOfficeOnly, chargeStatus, uriInfo); + } + + private String updateSavingAccount(Long accountId, String externalId, String apiRequestBodyAsJson, String commandParam) { + ExternalId accountExternalId = ExternalIdFactory.produce(externalId); + accountId = getResolvedAccountId(accountId, accountExternalId); + + if (is(commandParam, "updateWithHoldTax")) { + final CommandWrapper commandRequest = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson).updateWithHoldTax(accountId) + .build(); + final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + return toApiJsonSerializer.serialize(result); + } + + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSavingsAccount(accountId).withJson(apiRequestBodyAsJson) + .build(); + + final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return toApiJsonSerializer.serialize(result); + } + + private String handleCommands(Long accountId, String externalId, String commandParam, String apiRequestBodyAsJson) { + ExternalId accountExternalId = ExternalIdFactory.produce(externalId); + accountId = getResolvedAccountId(accountId, accountExternalId); + + String jsonApiRequest = apiRequestBodyAsJson; + if (StringUtils.isBlank(jsonApiRequest)) { + jsonApiRequest = "{}"; + } + + final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); + + CommandProcessingResult result = null; + if (is(commandParam, "reject")) { + final CommandWrapper commandRequest = builder.rejectSavingsAccountApplication(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "withdrawnByApplicant")) { + final CommandWrapper commandRequest = builder.withdrawSavingsAccountApplication(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "approve")) { + final CommandWrapper commandRequest = builder.approveSavingsAccountApplication(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "undoapproval")) { + final CommandWrapper commandRequest = builder.undoSavingsAccountApplication(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "activate")) { + final CommandWrapper commandRequest = builder.savingsAccountActivation(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "calculateInterest")) { + final CommandWrapper commandRequest = builder.withNoJsonBody().savingsAccountInterestCalculation(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "postInterest")) { + final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "applyAnnualFees")) { + final CommandWrapper commandRequest = builder.savingsAccountApplyAnnualFees(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "close")) { + final CommandWrapper commandRequest = builder.closeSavingsAccountApplication(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "assignSavingsOfficer")) { + final CommandWrapper commandRequest = builder.assignSavingsOfficer(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + return toApiJsonSerializer.serialize(result); + } else if (is(commandParam, "unassignSavingsOfficer")) { + final CommandWrapper commandRequest = builder.unassignSavingsOfficer(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + return toApiJsonSerializer.serialize(result); + } else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_DEBIT)) { + final CommandWrapper commandRequest = builder.blockDebitsFromSavingsAccount(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT)) { + final CommandWrapper commandRequest = builder.unblockDebitsFromSavingsAccount(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_CREDIT)) { + final CommandWrapper commandRequest = builder.blockCreditsToSavingsAccount(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT)) { + final CommandWrapper commandRequest = builder.unblockCreditsToSavingsAccount(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_ACCOUNT)) { + final CommandWrapper commandRequest = builder.blockSavingsAccount(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT)) { + final CommandWrapper commandRequest = builder.unblockSavingsAccount(accountId).build(); + result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + } + + if (result == null) { + // + throw new UnrecognizedQueryParamException("command", commandParam, + new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", + "postInterest", "close", "assignSavingsOfficer", "unassignSavingsOfficer", + SavingsApiConstants.COMMAND_BLOCK_DEBIT, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT, + SavingsApiConstants.COMMAND_BLOCK_CREDIT, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT, + SavingsApiConstants.COMMAND_BLOCK_ACCOUNT, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT }); + } + + return toApiJsonSerializer.serialize(result); + } + + private String deleteSavingAccount(Long accountId, String externalId) { + ExternalId accountExternalId = ExternalIdFactory.produce(externalId); + accountId = getResolvedAccountId(accountId, accountExternalId); + + final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSavingsAccount(accountId).build(); + + final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return toApiJsonSerializer.serialize(result); + } + + private Long getResolvedAccountId(Long accountId, ExternalId accountExternalId) { + Long resolvedAccountId = accountId; + if (resolvedAccountId == null) { + accountExternalId.throwExceptionIfEmpty(); + resolvedAccountId = savingsAccountReadPlatformService.retrieveAccountIdByExternalId(accountExternalId); + if (resolvedAccountId == null) { + throw new SavingsAccountNotFoundException(resolvedAccountId); + } + } + return resolvedAccountId; + } + + private SavingsAccountData populateTemplateAndAssociations(final Long accountId, final SavingsAccountData savingsAccount, + final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo) { + + Collection transactions = null; + Collection charges = null; + + final Set associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); + if (!associationParameters.isEmpty()) { + + if (associationParameters.contains("all")) { + associationParameters.addAll(Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges)); + } + + if (associationParameters.contains(SavingsApiConstants.transactions)) { + final Collection currentTransactions = savingsAccountReadPlatformService + .retrieveAllTransactions(accountId, DepositAccountType.SAVINGS_DEPOSIT); + if (!CollectionUtils.isEmpty(currentTransactions)) { + transactions = currentTransactions; + } + } + + if (associationParameters.contains(SavingsApiConstants.charges)) { + final Collection currentCharges = savingsAccountChargeReadPlatformService + .retrieveSavingsAccountCharges(accountId, chargeStatus); + if (!CollectionUtils.isEmpty(currentCharges)) { + charges = currentCharges; + } + } + } + + SavingsAccountData templateData = null; + final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + if (settings.isTemplate()) { + templateData = savingsAccountTemplateReadPlatformService.retrieveTemplate(savingsAccount.getClientId(), + savingsAccount.getGroupId(), savingsAccount.getSavingsProductId(), staffInSelectedOfficeOnly); + } + + return SavingsAccountData.withTemplateOptions(savingsAccount, templateData, transactions, charges); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/CurrentAccountTransactionAdjustmentCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/CurrentAccountTransactionAdjustmentCommandHandler.java index d1af50d6e..8cf592984 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/CurrentAccountTransactionAdjustmentCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/CurrentAccountTransactionAdjustmentCommandHandler.java @@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional; @CommandType(entity = "CURRENTACCOUNT", action = "ADJUSTTRANSACTION") public class CurrentAccountTransactionAdjustmentCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService writePlatformService; + private final CurrentAccountTransactionWritePlatformService writePlatformService; @Autowired - public CurrentAccountTransactionAdjustmentCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + public CurrentAccountTransactionAdjustmentCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) { this.writePlatformService = writePlatformService; } @@ -42,6 +42,6 @@ public class CurrentAccountTransactionAdjustmentCommandHandler implements NewCom @Override public CommandProcessingResult processCommand(final JsonCommand command) { final Long transactionId = Long.valueOf(command.getTransactionId()); - return this.writePlatformService.adjustSavingsTransaction(command.getSavingsId(), transactionId, command); + return this.writePlatformService.adjustTransaction(command.getSavingsId(), transactionId, command); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/DepositCurrentAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/DepositCurrentAccountCommandHandler.java index c331667ac..c87cefbcc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/DepositCurrentAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/DepositCurrentAccountCommandHandler.java @@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional; @CommandType(entity = "CURRENTACCOUNT", action = "DEPOSIT") public class DepositCurrentAccountCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService writePlatformService; + private final CurrentAccountTransactionWritePlatformService writePlatformService; @Autowired - public DepositCurrentAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + public DepositCurrentAccountCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) { this.writePlatformService = writePlatformService; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/HoldAmountCurrentAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/HoldAmountCurrentAccountCommandHandler.java index ee3dc3fbd..fe5ff9630 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/HoldAmountCurrentAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/HoldAmountCurrentAccountCommandHandler.java @@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional; @CommandType(entity = "CURRENTACCOUNT", action = "HOLDAMOUNT") public class HoldAmountCurrentAccountCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService writePlatformService; + private final CurrentAccountTransactionWritePlatformService writePlatformService; @Autowired - public HoldAmountCurrentAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + public HoldAmountCurrentAccountCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) { this.writePlatformService = writePlatformService; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/ReleaseAmountCurrentAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/ReleaseAmountCurrentAccountCommandHandler.java index aa42e6b5e..bee360745 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/ReleaseAmountCurrentAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/ReleaseAmountCurrentAccountCommandHandler.java @@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional; @CommandType(entity = "CURRENTACCOUNT", action = "RELEASEAMOUNT") public class ReleaseAmountCurrentAccountCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService writePlatformService; + private final CurrentAccountTransactionWritePlatformService writePlatformService; @Autowired - public ReleaseAmountCurrentAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + public ReleaseAmountCurrentAccountCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) { this.writePlatformService = writePlatformService; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/ReverseTransactionCurrentAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/ReverseTransactionCurrentAccountCommandHandler.java index 12561459d..dcad329dd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/ReverseTransactionCurrentAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/ReverseTransactionCurrentAccountCommandHandler.java @@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional; @CommandType(entity = "CURRENTACCOUNT", action = "REVERSETRANSACTION") public class ReverseTransactionCurrentAccountCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService writePlatformService; + private final CurrentAccountTransactionWritePlatformService writePlatformService; @Autowired - public ReverseTransactionCurrentAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + public ReverseTransactionCurrentAccountCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) { this.writePlatformService = writePlatformService; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/UndoTransactionCurrentAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/UndoTransactionCurrentAccountCommandHandler.java index 80cd9d3f8..25b5e49bd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/UndoTransactionCurrentAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/UndoTransactionCurrentAccountCommandHandler.java @@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional; @CommandType(entity = "CURRENTACCOUNT", action = "UNDOTRANSACTION") public class UndoTransactionCurrentAccountCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService writePlatformService; + private final CurrentAccountTransactionWritePlatformService writePlatformService; @Autowired - public UndoTransactionCurrentAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + public UndoTransactionCurrentAccountCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) { this.writePlatformService = writePlatformService; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/WithdrawCurrentAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/WithdrawCurrentAccountCommandHandler.java index 5609c58f1..6245561d6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/WithdrawCurrentAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/current/WithdrawCurrentAccountCommandHandler.java @@ -22,7 +22,7 @@ import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountTransactionWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,10 +31,10 @@ import org.springframework.transaction.annotation.Transactional; @CommandType(entity = "CURRENTACCOUNT", action = "WITHDRAWAL") public class WithdrawCurrentAccountCommandHandler implements NewCommandSourceHandler { - private final SavingsAccountWritePlatformService writePlatformService; + private final CurrentAccountTransactionWritePlatformService writePlatformService; @Autowired - public WithdrawCurrentAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + public WithdrawCurrentAccountCommandHandler(final CurrentAccountTransactionWritePlatformService writePlatformService) { this.writePlatformService = writePlatformService; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/CurrentAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/CurrentAccountReadPlatformServiceImpl.java index 18b530551..318875b1d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/CurrentAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/CurrentAccountReadPlatformServiceImpl.java @@ -1,533 +1,573 @@ -/** - * 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.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotFoundException; -import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountReadPlatformService; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountReadPlatformService { - - private static final String BASE_SQL = - "SELECT ca.*, " - + "c.display_name AS client_name, c.account_no AS client_account_no, " - + "o.name AS office_name, " - + "s.display_name AS staff_name, " - + "cp.name AS product_name, cp.short_name AS product_short_name, " - + "curr.name AS currency_name, curr.display_symbol AS currency_display_symbol, " - + "curr.internationalized_name_code AS currency_name_code " - + "FROM m_current_account ca " - + "LEFT JOIN m_client c ON c.id = ca.client_id " - + "LEFT JOIN m_office o ON o.id = ca.office_id " - + "LEFT JOIN m_staff s ON s.id = ca.staff_id " - + "LEFT JOIN m_current_product cp ON cp.id = ca.product_id " - + "LEFT JOIN m_currency curr ON curr.code = ca.currency_code "; - - private final JdbcTemplate jdbcTemplate; - - @Override - public Map retrieveOne(Long accountId) { - List> rows = jdbcTemplate.query( - BASE_SQL + "WHERE ca.id = ?", - new FlatRowMapper(), - accountId); - if (rows.isEmpty()) { - throw new CurrentAccountNotFoundException(accountId); - } - final Map response = buildStructuredResponse(rows.get(0)); - response.put("charges", retrieveCharges(accountId)); - response.put("transactions", retrieveTransactions(accountId)); - return response; - } - - private List> retrieveTransactions(Long accountId) { - final String sql = - "SELECT t.id, t.transaction_type_enum, t.transaction_date, t.amount, " - + "t.running_balance_derived, t.is_reversed, t.is_reversal, t.ref_no, t.remarks, " - + "t.submitted_on_date " - + "FROM m_current_account_transaction t " - + "WHERE t.current_account_id = ? " - + "ORDER BY t.transaction_date ASC, t.id ASC"; - - return jdbcTemplate.query(sql, (rs, rowNum) -> { - final Map t = new LinkedHashMap<>(); - t.put("id", rs.getLong("id")); - - final int txnTypeEnum = rs.getInt("transaction_type_enum"); - t.put("transactionType", buildTransactionType(txnTypeEnum)); - - final java.time.LocalDate txnDate = rs.getObject("transaction_date", java.time.LocalDate.class); - if (txnDate != null) { - t.put("date", new int[]{txnDate.getYear(), txnDate.getMonthValue(), txnDate.getDayOfMonth()}); - } else { - t.put("date", null); - } - - t.put("amount", rs.getBigDecimal("amount")); - t.put("runningBalance", rs.getBigDecimal("running_balance_derived")); - t.put("reversed", rs.getBoolean("is_reversed")); - t.put("isReversal", rs.getBoolean("is_reversal")); - t.put("refNo", rs.getString("ref_no")); - t.put("remarks", rs.getString("remarks")); - - final java.time.LocalDate submittedOn = rs.getObject("submitted_on_date", java.time.LocalDate.class); - if (submittedOn != null) { - t.put("submittedOnDate", new int[]{submittedOn.getYear(), submittedOn.getMonthValue(), submittedOn.getDayOfMonth()}); - } else { - t.put("submittedOnDate", null); - } - return t; - }, accountId); - } - - private Map buildTransactionType(int typeEnum) { - final Map tt = new LinkedHashMap<>(); - tt.put("id", typeEnum); - tt.put("deposit", typeEnum == 1); - tt.put("withdrawal", typeEnum == 2); - tt.put("interestPosting", typeEnum == 3); - tt.put("feeDeduction", typeEnum == 4 || typeEnum == 7); - tt.put("overdraftInterest", false); - tt.put("withholdTax", false); - tt.put("initiateTransfer", false); - tt.put("approveTransfer", false); - tt.put("withdrawTransfer", false); - tt.put("rejectTransfer", false); - tt.put("writtenoff", false); - tt.put("overdraftFee", false); - tt.put("escheat", false); - tt.put("amountHold", false); - tt.put("amountRelease", false); - tt.put("accrual", false); - switch (typeEnum) { - case 1 -> { tt.put("code", "savingsTransactionType.deposit"); tt.put("value", "Deposit"); } - case 2 -> { tt.put("code", "savingsTransactionType.withdrawal"); tt.put("value", "Withdrawal"); } - case 3 -> { tt.put("code", "savingsTransactionType.interestPosting"); tt.put("value", "Interest Posting"); } - case 4 -> { tt.put("code", "savingsTransactionType.feeDeduction"); tt.put("value", "Fee Deduction"); } - case 7 -> { tt.put("code", "savingsTransactionType.payCharge"); tt.put("value", "Charge Deduction"); } - default -> { tt.put("code", "savingsTransactionType.deposit"); tt.put("value", "Deposit"); } - } - return tt; - } - - private List> retrieveCharges(Long accountId) { - final String sql = - "SELECT cac.id, cac.charge_id, ch.name AS charge_name, " - + "cac.charge_time_enum, cac.charge_due_date, cac.fee_on_month, cac.fee_on_day, " - + "cac.fee_interval, cac.charge_calculation_enum, " - + "cac.amount, cac.amount_paid_derived, cac.amount_waived_derived, " - + "cac.amount_writtenoff_derived, cac.amount_outstanding_derived, " - + "cac.is_paid_derived, cac.waived, cac.is_penalty, cac.is_active " - + "FROM m_current_account_charge cac " - + "JOIN m_charge ch ON ch.id = cac.charge_id " - + "WHERE cac.current_account_id = ? AND cac.is_active = true " - + "ORDER BY cac.id"; - - return jdbcTemplate.query(sql, (rs, rowNum) -> { - final Map c = new LinkedHashMap<>(); - c.put("id", rs.getLong("id")); - c.put("chargeId", rs.getLong("charge_id")); - c.put("name", rs.getString("charge_name")); - - final int chargeTimeEnum = rs.getInt("charge_time_enum"); - c.put("chargeTimeType", buildEnumOption(chargeTimeEnum, chargeTimeValue(chargeTimeEnum))); - - c.put("chargeDueDate", rs.getDate("charge_due_date") != null ? rs.getDate("charge_due_date").toLocalDate().toString() : null); - c.put("feeOnMonthDay", buildFeeOnMonthDay(rs.getObject("fee_on_month"), rs.getObject("fee_on_day"))); - c.put("feeInterval", rs.getObject("fee_interval")); - - final int chargeCalcEnum = rs.getInt("charge_calculation_enum"); - c.put("chargeCalculationType", buildEnumOption(chargeCalcEnum, chargeCalcValue(chargeCalcEnum))); - - c.put("amount", rs.getBigDecimal("amount")); - c.put("amountPaid", rs.getBigDecimal("amount_paid_derived")); - c.put("amountWaived", rs.getBigDecimal("amount_waived_derived")); - c.put("amountWrittenOff", rs.getBigDecimal("amount_writtenoff_derived")); - c.put("amountOutstanding", rs.getBigDecimal("amount_outstanding_derived")); - c.put("paid", rs.getBoolean("is_paid_derived")); - c.put("waived", rs.getBoolean("waived")); - c.put("penalty", rs.getBoolean("is_penalty")); - c.put("isActive", rs.getBoolean("is_active")); - return c; - }, accountId); - } - - private Map buildFeeOnMonthDay(Object month, Object day) { - if (month == null || day == null) return null; - final Map m = new LinkedHashMap<>(); - m.put("month", month); - m.put("day", day); - return m; - } - - private String chargeTimeValue(int id) { - return switch (id) { - case 1 -> "Disbursement"; - case 2 -> "Specified due date"; - case 3 -> "Savings Activation"; - case 4 -> "Withdrawal Fee"; - case 5 -> "Annual Fee"; - case 6 -> "Annual Fee"; - case 7 -> "Monthly Fee"; - case 8 -> "Overdue Fee"; - default -> "Specified due date"; - }; - } - - private String chargeCalcValue(int id) { - return switch (id) { - case 1 -> "Flat"; - case 2 -> "% Amount"; - case 3 -> "% Loan Amount + Interest"; - case 4 -> "% Interest"; - default -> "Flat"; - }; - } - - @Override - public List> retrieveAll() { - List> rows = jdbcTemplate.query(BASE_SQL, new FlatRowMapper()); - List> result = new ArrayList<>(); - for (Map row : rows) { - final Map response = buildStructuredResponse(row); - final Long id = ((Number) row.get("id")).longValue(); - response.put("charges", retrieveCharges(id)); - result.add(response); - } - return result; - } - - private Map buildStructuredResponse(Map row) { - Map out = new LinkedHashMap<>(); - - out.put("id", row.get("id")); - out.put("accountNo", row.get("account_no")); - out.put("externalId", row.get("external_id")); - out.put("clientId", row.get("client_id")); - out.put("clientName", row.get("client_name")); - out.put("clientAccountNo", row.get("client_account_no")); - out.put("groupId", row.get("group_id")); - out.put("productId", row.get("product_id")); - out.put("productName", row.get("product_name")); - out.put("currentAccountProductName", row.get("product_name")); - out.put("shortProductName", row.get("product_short_name")); - out.put("officeId", row.get("office_id")); - out.put("officeName", row.get("office_name")); - out.put("staffId", row.get("staff_id")); - out.put("fieldOfficerId", row.get("staff_id")); - out.put("fieldOfficerName", row.get("staff_name")); - - // status - int statusEnum = toInt(row.get("status_enum"), 100); - out.put("status", buildStatus(statusEnum)); - - // subStatus - int subStatusEnum = toInt(row.get("sub_status_enum"), 0); - out.put("subStatus", buildSubStatus(subStatusEnum)); - - // accountType - int accountTypeEnum = toInt(row.get("account_type_enum"), 1); - out.put("accountType", buildAccountType(accountTypeEnum)); - - // timeline - out.put("timeline", buildTimeline(row)); - - // currency - out.put("currency", buildCurrency(row)); - - // interest settings - out.put("nominalAnnualInterestRate", row.get("nominal_annual_interest_rate")); - - int compPeriod = toInt(row.get("interest_compounding_period_enum"), 4); - out.put("interestCompoundingPeriodType", buildEnumOption(compPeriod, interestCompoundingPeriodValue(compPeriod))); - - int postingPeriod = toInt(row.get("interest_posting_period_enum"), 4); - out.put("interestPostingPeriodType", buildEnumOption(postingPeriod, interestPostingPeriodValue(postingPeriod))); - - int calcType = toInt(row.get("interest_calculation_type_enum"), 1); - out.put("interestCalculationType", buildEnumOption(calcType, interestCalcTypeValue(calcType))); - - int daysInYear = toInt(row.get("interest_calculation_days_in_year_type_enum"), 365); - out.put("interestCalculationDaysInYearType", buildEnumOption(daysInYear, daysInYearValue(daysInYear))); - - out.put("minBalanceForInterestCalculation", row.get("min_balance_for_interest_calculation")); - - // balance/overdraft - out.put("allowOverdraft", row.get("allow_overdraft")); - out.put("overdraftLimit", row.get("overdraft_limit")); - out.put("minOverdraftForInterestCalculation", row.get("min_overdraft_for_interest_calculation")); - out.put("minRequiredBalance", row.get("min_required_balance")); - out.put("enforceMinRequiredBalance", row.get("enforce_min_required_balance")); - out.put("onHoldFunds", row.get("on_hold_funds_derived")); - out.put("withHoldTax", row.get("withhold_tax")); - out.put("taxGroup", null); - - // summary - out.put("summary", buildSummary(row)); - - // CBS-specific fields - out.put("cbsAccountType", row.get("cbs_account_type")); - out.put("customerSegment", row.get("customer_segment")); - out.put("accountStatus", row.get("account_status")); - out.put("modeOfOperation", row.get("mode_of_operation")); - out.put("specialCategory", row.get("special_category")); - out.put("msme", row.get("msme")); - out.put("autoDebitFacility", row.get("auto_debit_facility")); - out.put("balanceSwapOption", row.get("balance_swap_option")); - out.put("establishmentDate", row.get("establishment_date")); - out.put("startOperationDate", row.get("start_operation_date")); - out.put("nonIndividualClient", row.get("is_non_individual_client")); - out.put("signatoryRights", row.get("signatory_rights")); - out.put("introducerClientId", row.get("introducer_client_id")); - out.put("introducerName", row.get("introducer_name")); - out.put("introducerCifNumber", row.get("introducer_cif_number")); - out.put("nomineeData", row.get("nominee_data")); - - return out; - } - - private Map buildStatus(int statusEnum) { - Map s = new LinkedHashMap<>(); - s.put("id", statusEnum); - switch (statusEnum) { - case 100 -> { - s.put("code", "savingsAccountStatusType.submittedAndPendingApproval"); - s.put("value", "Submitted and Pending Approval"); - s.put("submittedAndPendingApproval", true); - s.put("approved", false); s.put("active", false); - s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", false); - } - case 200 -> { - s.put("code", "savingsAccountStatusType.approved"); - s.put("value", "Approved"); - s.put("submittedAndPendingApproval", false); - s.put("approved", true); s.put("active", false); - s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", false); - } - case 300 -> { - s.put("code", "savingsAccountStatusType.active"); - s.put("value", "Active"); - s.put("submittedAndPendingApproval", false); - s.put("approved", false); s.put("active", true); - s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", false); - } - case 400 -> { - s.put("code", "savingsAccountStatusType.withdrawn"); - s.put("value", "Withdrawn by Applicant"); - s.put("submittedAndPendingApproval", false); - s.put("approved", false); s.put("active", false); - s.put("rejected", false); s.put("withdrawnByApplicant", true); s.put("closed", false); - } - case 500 -> { - s.put("code", "savingsAccountStatusType.rejected"); - s.put("value", "Rejected"); - s.put("submittedAndPendingApproval", false); - s.put("approved", false); s.put("active", false); - s.put("rejected", true); s.put("withdrawnByApplicant", false); s.put("closed", false); - } - case 600 -> { - s.put("code", "savingsAccountStatusType.closed"); - s.put("value", "Closed"); - s.put("submittedAndPendingApproval", false); - s.put("approved", false); s.put("active", false); - s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", true); - } - default -> { - s.put("code", "savingsAccountStatusType.invalid"); - s.put("value", "Invalid"); - s.put("submittedAndPendingApproval", false); - s.put("approved", false); s.put("active", false); - s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", false); - } - } - return s; - } - - private Map buildSubStatus(int subStatusEnum) { - Map s = new LinkedHashMap<>(); - s.put("id", subStatusEnum); - String value = switch (subStatusEnum) { - case 0 -> "None"; - case 100 -> "Inactive"; - case 200 -> "Dormant"; - case 300 -> "Block"; - case 400 -> "Block Debit"; - case 500 -> "Block Credit"; - default -> "None"; - }; - s.put("code", "SavingsAccountSubStatusEnum." + value.replace(" ", "")); - s.put("value", value); - s.put("none", subStatusEnum == 0); - s.put("inactive", subStatusEnum == 100); - s.put("dormant", subStatusEnum == 200); - s.put("block", subStatusEnum == 300); - s.put("blockDebit", subStatusEnum == 400); - s.put("blockCredit", subStatusEnum == 500); - return s; - } - - private Map buildAccountType(int accountTypeEnum) { - Map s = new LinkedHashMap<>(); - s.put("id", accountTypeEnum); - String value = switch (accountTypeEnum) { - case 1 -> "Individual"; - case 2 -> "Group"; - case 3 -> "JLG"; - default -> "Individual"; - }; - s.put("code", "loanType." + value.toLowerCase()); - s.put("value", value); - return s; - } - - private Map buildTimeline(Map row) { - Map t = new LinkedHashMap<>(); - t.put("submittedOnDate", row.get("submittedon_date")); - t.put("approvedOnDate", row.get("approvedon_date")); - t.put("activatedOnDate", row.get("activatedon_date")); - t.put("rejectedOnDate", row.get("rejectedon_date")); - t.put("withdrawnOnDate", row.get("withdrawnon_date")); - t.put("closedOnDate", row.get("closedon_date")); - return t; - } - - private Map buildCurrency(Map row) { - Map c = new LinkedHashMap<>(); - c.put("code", row.get("currency_code")); - c.put("name", row.get("currency_name")); - c.put("decimalPlaces", row.get("currency_digits")); - c.put("inMultiplesOf", row.get("currency_multiplesof")); - c.put("displaySymbol", row.get("currency_display_symbol")); - c.put("nameCode", row.get("currency_name_code")); - c.put("displayLabel", row.get("currency_name") + " [" + row.get("currency_code") + "]"); - return c; - } - - private Map buildSummary(Map row) { - Map s = new LinkedHashMap<>(); - s.put("accountBalance", row.get("account_balance_derived")); - s.put("availableBalance", row.get("account_balance_derived")); - s.put("totalDeposits", row.get("total_deposits_derived")); - s.put("totalWithdrawals", row.get("total_withdrawals_derived")); - s.put("totalInterestEarned", row.get("total_interest_earned_derived")); - s.put("totalInterestPosted", row.get("total_interest_posted_derived")); - s.put("interestNotPosted", computeInterestNotPosted(row)); - s.put("totalOverdraftInterestDerived", row.get("total_overdraft_interest_derived")); - s.put("totalWithholdTax", row.get("total_withhold_tax_derived")); - s.put("totalWithdrawalFees", null); - s.put("totalAnnualFees", null); - s.put("lastInterestCalculationDate", row.get("last_interest_calculation_date")); - s.put("onHoldFunds", row.get("on_hold_funds_derived")); - return s; - } - - private BigDecimal computeInterestNotPosted(Map row) { - BigDecimal earned = toBigDecimal(row.get("total_interest_earned_derived")); - BigDecimal posted = toBigDecimal(row.get("total_interest_posted_derived")); - if (earned == null) return BigDecimal.ZERO; - if (posted == null) return earned; - return earned.subtract(posted); - } - - private Map buildEnumOption(int id, String value) { - Map e = new LinkedHashMap<>(); - e.put("id", id); - e.put("code", value.replace(" ", "")); - e.put("value", value); - return e; - } - - private String interestCompoundingPeriodValue(int id) { - return switch (id) { - case 1 -> "Daily"; - case 2 -> "Weekly"; - case 3 -> "Bi-Weekly"; - case 4 -> "Monthly"; - case 5 -> "Quarterly"; - case 6 -> "Semi-Annual"; - case 7 -> "Annually"; - default -> "Monthly"; - }; - } - - private String interestPostingPeriodValue(int id) { - return switch (id) { - case 1 -> "Monthly"; - case 2 -> "Quarterly"; - case 3 -> "Biannual"; - case 4 -> "Annually"; - default -> "Annually"; - }; - } - - private String interestCalcTypeValue(int id) { - return switch (id) { - case 1 -> "Daily Balance"; - case 2 -> "Average Daily Balance"; - default -> "Daily Balance"; - }; - } - - private String daysInYearValue(int id) { - return switch (id) { - case 360 -> "360 Days"; - case 365 -> "365 Days"; - case 366 -> "Actual Days"; - default -> "365 Days"; - }; - } - - private int toInt(Object val, int defaultVal) { - if (val == null) return defaultVal; - if (val instanceof Number) return ((Number) val).intValue(); - try { return Integer.parseInt(val.toString().trim()); } catch (Exception e) { return defaultVal; } - } - - private BigDecimal toBigDecimal(Object val) { - if (val == null) return null; - if (val instanceof BigDecimal) return (BigDecimal) val; - try { return new BigDecimal(val.toString().trim()); } catch (Exception e) { return null; } - } - - private static class FlatRowMapper implements RowMapper> { - @Override - public Map mapRow(ResultSet rs, int rowNum) throws SQLException { - ResultSetMetaData meta = rs.getMetaData(); - int cols = meta.getColumnCount(); - Map row = new LinkedHashMap<>(); - for (int i = 1; i <= cols; i++) { - row.put(meta.getColumnLabel(i), rs.getObject(i)); - } - return row; - } - } -} +/** + * 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.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotFoundException; +import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountReadPlatformService; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountReadPlatformService { + + private static final String BASE_SQL = + "SELECT ca.*, " + + "c.display_name AS client_name, c.account_no AS client_account_no, " + + "o.name AS office_name, " + + "s.display_name AS staff_name, " + + "cp.name AS product_name, cp.short_name AS product_short_name, " + + "curr.name AS currency_name, curr.display_symbol AS currency_display_symbol, " + + "curr.internationalized_name_code AS currency_name_code " + + "FROM m_current_account ca " + + "LEFT JOIN m_client c ON c.id = ca.client_id " + + "LEFT JOIN m_office o ON o.id = ca.office_id " + + "LEFT JOIN m_staff s ON s.id = ca.staff_id " + + "LEFT JOIN m_current_product cp ON cp.id = ca.product_id " + + "LEFT JOIN m_currency curr ON curr.code = ca.currency_code "; + + private final JdbcTemplate jdbcTemplate; + + @Override + public Map retrieveOne(Long accountId) { + List> rows = jdbcTemplate.query( + BASE_SQL + "WHERE ca.id = ?", + new FlatRowMapper(), + accountId); + if (rows.isEmpty()) { + throw new CurrentAccountNotFoundException(accountId); + } + final Map response = buildStructuredResponse(rows.get(0)); + response.put("charges", retrieveCharges(accountId)); + response.put("transactions", retrieveTransactions(accountId)); + return response; + } + + private List> retrieveTransactions(Long accountId) { + final String sql = + "SELECT t.id, t.transaction_type_enum, t.transaction_date, t.amount, " + + "t.running_balance_derived, t.is_reversed, t.is_reversal, t.ref_no, t.remarks, " + + "t.submitted_on_date " + + "FROM m_current_account_transaction t " + + "WHERE t.current_account_id = ? " + + "ORDER BY t.transaction_date ASC, t.id ASC"; + + return jdbcTemplate.query(sql, (rs, rowNum) -> { + final Map t = new LinkedHashMap<>(); + t.put("id", rs.getLong("id")); + + final int txnTypeEnum = rs.getInt("transaction_type_enum"); + t.put("transactionType", buildTransactionType(txnTypeEnum)); + + final java.time.LocalDate txnDate = rs.getObject("transaction_date", java.time.LocalDate.class); + if (txnDate != null) { + t.put("date", new int[]{txnDate.getYear(), txnDate.getMonthValue(), txnDate.getDayOfMonth()}); + } else { + t.put("date", null); + } + + t.put("amount", rs.getBigDecimal("amount")); + t.put("runningBalance", rs.getBigDecimal("running_balance_derived")); + t.put("reversed", rs.getBoolean("is_reversed")); + t.put("isReversal", rs.getBoolean("is_reversal")); + t.put("refNo", rs.getString("ref_no")); + t.put("remarks", rs.getString("remarks")); + + final java.time.LocalDate submittedOn = rs.getObject("submitted_on_date", java.time.LocalDate.class); + if (submittedOn != null) { + t.put("submittedOnDate", new int[]{submittedOn.getYear(), submittedOn.getMonthValue(), submittedOn.getDayOfMonth()}); + } else { + t.put("submittedOnDate", null); + } + return t; + }, accountId); + } + + private Map buildTransactionType(int typeEnum) { + final Map tt = new LinkedHashMap<>(); + tt.put("id", typeEnum); + tt.put("deposit", typeEnum == 1); + tt.put("withdrawal", typeEnum == 2); + tt.put("interestPosting", typeEnum == 3); + tt.put("feeDeduction", typeEnum == 4 || typeEnum == 7); + tt.put("overdraftInterest", false); + tt.put("withholdTax", false); + tt.put("initiateTransfer", false); + tt.put("approveTransfer", false); + tt.put("withdrawTransfer", false); + tt.put("rejectTransfer", false); + tt.put("writtenoff", false); + tt.put("overdraftFee", false); + tt.put("escheat", false); + tt.put("amountHold", false); + tt.put("amountRelease", false); + tt.put("accrual", false); + switch (typeEnum) { + case 1 -> { tt.put("code", "savingsTransactionType.deposit"); tt.put("value", "Deposit"); } + case 2 -> { tt.put("code", "savingsTransactionType.withdrawal"); tt.put("value", "Withdrawal"); } + case 3 -> { tt.put("code", "savingsTransactionType.interestPosting"); tt.put("value", "Interest Posting"); } + case 4 -> { tt.put("code", "savingsTransactionType.feeDeduction"); tt.put("value", "Fee Deduction"); } + case 7 -> { tt.put("code", "savingsTransactionType.payCharge"); tt.put("value", "Charge Deduction"); } + default -> { tt.put("code", "savingsTransactionType.deposit"); tt.put("value", "Deposit"); } + } + return tt; + } + + private List> retrieveCharges(Long accountId) { + final String sql = + "SELECT cac.id, cac.charge_id, ch.name AS charge_name, " + + "cac.charge_time_enum, cac.charge_due_date, cac.fee_on_month, cac.fee_on_day, " + + "cac.fee_interval, cac.charge_calculation_enum, " + + "cac.amount, cac.amount_paid_derived, cac.amount_waived_derived, " + + "cac.amount_writtenoff_derived, cac.amount_outstanding_derived, " + + "cac.is_paid_derived, cac.waived, cac.is_penalty, cac.is_active " + + "FROM m_current_account_charge cac " + + "JOIN m_charge ch ON ch.id = cac.charge_id " + + "WHERE cac.current_account_id = ? AND cac.is_active = true " + + "ORDER BY cac.id"; + + return jdbcTemplate.query(sql, (rs, rowNum) -> { + final Map c = new LinkedHashMap<>(); + c.put("id", rs.getLong("id")); + c.put("chargeId", rs.getLong("charge_id")); + c.put("name", rs.getString("charge_name")); + + final int chargeTimeEnum = rs.getInt("charge_time_enum"); + c.put("chargeTimeType", buildEnumOption(chargeTimeEnum, chargeTimeValue(chargeTimeEnum))); + + c.put("chargeDueDate", rs.getDate("charge_due_date") != null ? rs.getDate("charge_due_date").toLocalDate().toString() : null); + c.put("feeOnMonthDay", buildFeeOnMonthDay(rs.getObject("fee_on_month"), rs.getObject("fee_on_day"))); + c.put("feeInterval", rs.getObject("fee_interval")); + + final int chargeCalcEnum = rs.getInt("charge_calculation_enum"); + c.put("chargeCalculationType", buildEnumOption(chargeCalcEnum, chargeCalcValue(chargeCalcEnum))); + + c.put("amount", rs.getBigDecimal("amount")); + c.put("amountPaid", rs.getBigDecimal("amount_paid_derived")); + c.put("amountWaived", rs.getBigDecimal("amount_waived_derived")); + c.put("amountWrittenOff", rs.getBigDecimal("amount_writtenoff_derived")); + c.put("amountOutstanding", rs.getBigDecimal("amount_outstanding_derived")); + c.put("paid", rs.getBoolean("is_paid_derived")); + c.put("waived", rs.getBoolean("waived")); + c.put("penalty", rs.getBoolean("is_penalty")); + c.put("isActive", rs.getBoolean("is_active")); + return c; + }, accountId); + } + + private Map buildFeeOnMonthDay(Object month, Object day) { + if (month == null || day == null) return null; + final Map m = new LinkedHashMap<>(); + m.put("month", month); + m.put("day", day); + return m; + } + + private String chargeTimeValue(int id) { + return switch (id) { + case 1 -> "Disbursement"; + case 2 -> "Specified due date"; + case 3 -> "Savings Activation"; + case 4 -> "Withdrawal Fee"; + case 5 -> "Annual Fee"; + case 6 -> "Annual Fee"; + case 7 -> "Monthly Fee"; + case 8 -> "Overdue Fee"; + default -> "Specified due date"; + }; + } + + private String chargeCalcValue(int id) { + return switch (id) { + case 1 -> "Flat"; + case 2 -> "% Amount"; + case 3 -> "% Loan Amount + Interest"; + case 4 -> "% Interest"; + default -> "Flat"; + }; + } + + @Override + public List> retrieveAll() { + List> rows = jdbcTemplate.query(BASE_SQL, new FlatRowMapper()); + List> result = new ArrayList<>(); + for (Map row : rows) { + final Map response = buildStructuredResponse(row); + final Long id = ((Number) row.get("id")).longValue(); + response.put("charges", retrieveCharges(id)); + result.add(response); + } + return result; + } + + @Override + public List> retrieveAll(final Integer offset, final Integer limit, final String accountNo, + final String clientName) { + final StringBuilder sqlBuilder = new StringBuilder(BASE_SQL); + final List params = new ArrayList<>(); + boolean hasWhere = false; + + if (StringUtils.isNotBlank(accountNo)) { + sqlBuilder.append(hasWhere ? " AND " : " WHERE "); + sqlBuilder.append("ca.account_no LIKE ?"); + params.add("%" + accountNo + "%"); + hasWhere = true; + } + if (StringUtils.isNotBlank(clientName)) { + sqlBuilder.append(hasWhere ? " AND " : " WHERE "); + sqlBuilder.append("c.display_name LIKE ?"); + params.add("%" + clientName + "%"); + hasWhere = true; + } + if (limit != null && limit > 0) { + sqlBuilder.append(" LIMIT ?"); + params.add(limit); + if (offset != null && offset >= 0) { + sqlBuilder.append(" OFFSET ?"); + params.add(offset); + } + } + + List> rows = jdbcTemplate.query(sqlBuilder.toString(), new FlatRowMapper(), params.toArray()); + List> result = new ArrayList<>(); + for (Map row : rows) { + final Map response = buildStructuredResponse(row); + final Long id = ((Number) row.get("id")).longValue(); + response.put("charges", retrieveCharges(id)); + result.add(response); + } + return result; + } + + private Map buildStructuredResponse(Map row) { + Map out = new LinkedHashMap<>(); + + out.put("id", row.get("id")); + out.put("accountNo", row.get("account_no")); + out.put("externalId", row.get("external_id")); + out.put("clientId", row.get("client_id")); + out.put("clientName", row.get("client_name")); + out.put("clientAccountNo", row.get("client_account_no")); + out.put("groupId", row.get("group_id")); + out.put("productId", row.get("product_id")); + out.put("productName", row.get("product_name")); + out.put("currentAccountProductName", row.get("product_name")); + out.put("shortProductName", row.get("product_short_name")); + out.put("officeId", row.get("office_id")); + out.put("officeName", row.get("office_name")); + out.put("staffId", row.get("staff_id")); + out.put("fieldOfficerId", row.get("staff_id")); + out.put("fieldOfficerName", row.get("staff_name")); + + // status + int statusEnum = toInt(row.get("status_enum"), 100); + out.put("status", buildStatus(statusEnum)); + + // subStatus + int subStatusEnum = toInt(row.get("sub_status_enum"), 0); + out.put("subStatus", buildSubStatus(subStatusEnum)); + + // accountType + int accountTypeEnum = toInt(row.get("account_type_enum"), 1); + out.put("accountType", buildAccountType(accountTypeEnum)); + + // timeline + out.put("timeline", buildTimeline(row)); + + // currency + out.put("currency", buildCurrency(row)); + + // interest settings + out.put("nominalAnnualInterestRate", row.get("nominal_annual_interest_rate")); + + int compPeriod = toInt(row.get("interest_compounding_period_enum"), 4); + out.put("interestCompoundingPeriodType", buildEnumOption(compPeriod, interestCompoundingPeriodValue(compPeriod))); + + int postingPeriod = toInt(row.get("interest_posting_period_enum"), 4); + out.put("interestPostingPeriodType", buildEnumOption(postingPeriod, interestPostingPeriodValue(postingPeriod))); + + int calcType = toInt(row.get("interest_calculation_type_enum"), 1); + out.put("interestCalculationType", buildEnumOption(calcType, interestCalcTypeValue(calcType))); + + int daysInYear = toInt(row.get("interest_calculation_days_in_year_type_enum"), 365); + out.put("interestCalculationDaysInYearType", buildEnumOption(daysInYear, daysInYearValue(daysInYear))); + + out.put("minBalanceForInterestCalculation", row.get("min_balance_for_interest_calculation")); + + // balance/overdraft + out.put("allowOverdraft", row.get("allow_overdraft")); + out.put("overdraftLimit", row.get("overdraft_limit")); + out.put("minOverdraftForInterestCalculation", row.get("min_overdraft_for_interest_calculation")); + out.put("minRequiredBalance", row.get("min_required_balance")); + out.put("enforceMinRequiredBalance", row.get("enforce_min_required_balance")); + out.put("onHoldFunds", row.get("on_hold_funds_derived")); + out.put("withHoldTax", row.get("withhold_tax")); + out.put("taxGroup", null); + + // summary + out.put("summary", buildSummary(row)); + + // CBS-specific fields + out.put("cbsAccountType", row.get("cbs_account_type")); + out.put("customerSegment", row.get("customer_segment")); + out.put("accountStatus", row.get("account_status")); + out.put("modeOfOperation", row.get("mode_of_operation")); + out.put("specialCategory", row.get("special_category")); + out.put("msme", row.get("msme")); + out.put("autoDebitFacility", row.get("auto_debit_facility")); + out.put("balanceSwapOption", row.get("balance_swap_option")); + out.put("establishmentDate", row.get("establishment_date")); + out.put("startOperationDate", row.get("start_operation_date")); + out.put("nonIndividualClient", row.get("is_non_individual_client")); + out.put("signatoryRights", row.get("signatory_rights")); + out.put("introducerClientId", row.get("introducer_client_id")); + out.put("introducerName", row.get("introducer_name")); + out.put("introducerCifNumber", row.get("introducer_cif_number")); + out.put("nomineeData", row.get("nominee_data")); + + return out; + } + + private Map buildStatus(int statusEnum) { + Map s = new LinkedHashMap<>(); + s.put("id", statusEnum); + switch (statusEnum) { + case 100 -> { + s.put("code", "savingsAccountStatusType.submittedAndPendingApproval"); + s.put("value", "Submitted and Pending Approval"); + s.put("submittedAndPendingApproval", true); + s.put("approved", false); s.put("active", false); + s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", false); + } + case 200 -> { + s.put("code", "savingsAccountStatusType.approved"); + s.put("value", "Approved"); + s.put("submittedAndPendingApproval", false); + s.put("approved", true); s.put("active", false); + s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", false); + } + case 300 -> { + s.put("code", "savingsAccountStatusType.active"); + s.put("value", "Active"); + s.put("submittedAndPendingApproval", false); + s.put("approved", false); s.put("active", true); + s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", false); + } + case 400 -> { + s.put("code", "savingsAccountStatusType.withdrawn"); + s.put("value", "Withdrawn by Applicant"); + s.put("submittedAndPendingApproval", false); + s.put("approved", false); s.put("active", false); + s.put("rejected", false); s.put("withdrawnByApplicant", true); s.put("closed", false); + } + case 500 -> { + s.put("code", "savingsAccountStatusType.rejected"); + s.put("value", "Rejected"); + s.put("submittedAndPendingApproval", false); + s.put("approved", false); s.put("active", false); + s.put("rejected", true); s.put("withdrawnByApplicant", false); s.put("closed", false); + } + case 600 -> { + s.put("code", "savingsAccountStatusType.closed"); + s.put("value", "Closed"); + s.put("submittedAndPendingApproval", false); + s.put("approved", false); s.put("active", false); + s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", true); + } + default -> { + s.put("code", "savingsAccountStatusType.invalid"); + s.put("value", "Invalid"); + s.put("submittedAndPendingApproval", false); + s.put("approved", false); s.put("active", false); + s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", false); + } + } + return s; + } + + private Map buildSubStatus(int subStatusEnum) { + Map s = new LinkedHashMap<>(); + s.put("id", subStatusEnum); + String value = switch (subStatusEnum) { + case 0 -> "None"; + case 100 -> "Inactive"; + case 200 -> "Dormant"; + case 300 -> "Block"; + case 400 -> "Block Debit"; + case 500 -> "Block Credit"; + default -> "None"; + }; + s.put("code", "SavingsAccountSubStatusEnum." + value.replace(" ", "")); + s.put("value", value); + s.put("none", subStatusEnum == 0); + s.put("inactive", subStatusEnum == 100); + s.put("dormant", subStatusEnum == 200); + s.put("block", subStatusEnum == 300); + s.put("blockDebit", subStatusEnum == 400); + s.put("blockCredit", subStatusEnum == 500); + return s; + } + + private Map buildAccountType(int accountTypeEnum) { + Map s = new LinkedHashMap<>(); + s.put("id", accountTypeEnum); + String value = switch (accountTypeEnum) { + case 1 -> "Individual"; + case 2 -> "Group"; + case 3 -> "JLG"; + default -> "Individual"; + }; + s.put("code", "loanType." + value.toLowerCase()); + s.put("value", value); + return s; + } + + private Map buildTimeline(Map row) { + Map t = new LinkedHashMap<>(); + t.put("submittedOnDate", row.get("submittedon_date")); + t.put("approvedOnDate", row.get("approvedon_date")); + t.put("activatedOnDate", row.get("activatedon_date")); + t.put("rejectedOnDate", row.get("rejectedon_date")); + t.put("withdrawnOnDate", row.get("withdrawnon_date")); + t.put("closedOnDate", row.get("closedon_date")); + return t; + } + + private Map buildCurrency(Map row) { + Map c = new LinkedHashMap<>(); + c.put("code", row.get("currency_code")); + c.put("name", row.get("currency_name")); + c.put("decimalPlaces", row.get("currency_digits")); + c.put("inMultiplesOf", row.get("currency_multiplesof")); + c.put("displaySymbol", row.get("currency_display_symbol")); + c.put("nameCode", row.get("currency_name_code")); + c.put("displayLabel", row.get("currency_name") + " [" + row.get("currency_code") + "]"); + return c; + } + + private Map buildSummary(Map row) { + Map s = new LinkedHashMap<>(); + s.put("accountBalance", row.get("account_balance_derived")); + s.put("availableBalance", row.get("account_balance_derived")); + s.put("totalDeposits", row.get("total_deposits_derived")); + s.put("totalWithdrawals", row.get("total_withdrawals_derived")); + s.put("totalInterestEarned", row.get("total_interest_earned_derived")); + s.put("totalInterestPosted", row.get("total_interest_posted_derived")); + s.put("interestNotPosted", computeInterestNotPosted(row)); + s.put("totalOverdraftInterestDerived", row.get("total_overdraft_interest_derived")); + s.put("totalWithholdTax", row.get("total_withhold_tax_derived")); + s.put("totalWithdrawalFees", null); + s.put("totalAnnualFees", null); + s.put("lastInterestCalculationDate", row.get("last_interest_calculation_date")); + s.put("onHoldFunds", row.get("on_hold_funds_derived")); + return s; + } + + private BigDecimal computeInterestNotPosted(Map row) { + BigDecimal earned = toBigDecimal(row.get("total_interest_earned_derived")); + BigDecimal posted = toBigDecimal(row.get("total_interest_posted_derived")); + if (earned == null) return BigDecimal.ZERO; + if (posted == null) return earned; + return earned.subtract(posted); + } + + private Map buildEnumOption(int id, String value) { + Map e = new LinkedHashMap<>(); + e.put("id", id); + e.put("code", value.replace(" ", "")); + e.put("value", value); + return e; + } + + private String interestCompoundingPeriodValue(int id) { + return switch (id) { + case 1 -> "Daily"; + case 2 -> "Weekly"; + case 3 -> "Bi-Weekly"; + case 4 -> "Monthly"; + case 5 -> "Quarterly"; + case 6 -> "Semi-Annual"; + case 7 -> "Annually"; + default -> "Monthly"; + }; + } + + private String interestPostingPeriodValue(int id) { + return switch (id) { + case 1 -> "Monthly"; + case 2 -> "Quarterly"; + case 3 -> "Biannual"; + case 4 -> "Annually"; + default -> "Annually"; + }; + } + + private String interestCalcTypeValue(int id) { + return switch (id) { + case 1 -> "Daily Balance"; + case 2 -> "Average Daily Balance"; + default -> "Daily Balance"; + }; + } + + private String daysInYearValue(int id) { + return switch (id) { + case 360 -> "360 Days"; + case 365 -> "365 Days"; + case 366 -> "Actual Days"; + default -> "365 Days"; + }; + } + + private int toInt(Object val, int defaultVal) { + if (val == null) return defaultVal; + if (val instanceof Number) return ((Number) val).intValue(); + try { return Integer.parseInt(val.toString().trim()); } catch (Exception e) { return defaultVal; } + } + + private BigDecimal toBigDecimal(Object val) { + if (val == null) return null; + if (val instanceof BigDecimal) return (BigDecimal) val; + try { return new BigDecimal(val.toString().trim()); } catch (Exception e) { return null; } + } + + private static class FlatRowMapper implements RowMapper> { + @Override + public Map mapRow(ResultSet rs, int rowNum) throws SQLException { + ResultSetMetaData meta = rs.getMetaData(); + int cols = meta.getColumnCount(); + Map row = new LinkedHashMap<>(); + for (int i = 1; i <= cols; i++) { + row.put(meta.getColumnLabel(i), rs.getObject(i)); + } + return row; + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/CurrentAccountTransactionWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/CurrentAccountTransactionWritePlatformServiceImpl.java new file mode 100644 index 000000000..cde77dd4d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/CurrentAccountTransactionWritePlatformServiceImpl.java @@ -0,0 +1,468 @@ +/** + * 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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings; +import org.apache.fineract.accounting.glaccount.domain.GLAccount; +import org.apache.fineract.accounting.journalentry.domain.JournalEntry; +import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository; +import org.apache.fineract.accounting.journalentry.domain.JournalEntryType; +import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; +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.GeneralPlatformDomainRuleException; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.organisation.office.domain.Office; +import org.apache.fineract.portfolio.PortfolioProductType; +import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount; +import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountRepository; +import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction; +import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransactionRepository; +import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotFoundException; +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.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CurrentAccountTransactionWritePlatformServiceImpl implements CurrentAccountTransactionWritePlatformService { + + // Savings entity type used for GL journal entries (EntityType.SAVINGS = 2) + private static final Integer ENTITY_TYPE_SAVINGS = 2; + + private final PlatformSecurityContext context; + private final CurrentAccountRepository currentAccountRepository; + private final CurrentAccountTransactionRepository currentAccountTransactionRepository; + private final CurrentProductRepository currentProductRepository; + private final ProductToGLAccountMappingRepository productToGLAccountMappingRepository; + private final JournalEntryRepository journalEntryRepository; + + @Transactional + @Override + public CommandProcessingResult deposit(final Long accountId, final JsonCommand command) { + context.authenticatedUser(); + + final CurrentAccount account = findActiveAccount(accountId); + final Office office = account.getOffice(); + + LocalDate txnDate = command.localDateValueOfParameterNamed("transactionDate"); + if (txnDate == null) txnDate = LocalDate.now(); + + final BigDecimal amount = command.bigDecimalValueOfParameterNamed("transactionAmount"); + validatePositiveAmount(amount, accountId, "deposit"); + + final String refNo = command.stringValueOfParameterNamed("accountNumber"); + + final CurrentAccountTransaction txn = CurrentAccountTransaction.deposit(account, office, amount, txnDate, refNo); + + final BigDecimal oldBalance = safeBalance(account.getAccountBalanceDerived()); + final BigDecimal newBalance = oldBalance.add(amount); + txn.setRunningBalanceDerived(newBalance); + + currentAccountTransactionRepository.saveAndFlush(txn); + + account.setAccountBalanceDerived(newBalance); + account.setTotalDepositsDerived(safeBalance(account.getTotalDepositsDerived()).add(amount)); + currentAccountRepository.save(account); + + postTransactionGl(account, txn, JournalEntryType.DEBIT, JournalEntryType.CREDIT, "Current Account Deposit"); + + return new CommandProcessingResultBuilder() + .withEntityId(txn.getId()) + .withSavingsId(accountId) + .withClientId(account.getClient() != null ? account.getClient().getId() : null) + .withOfficeId(office != null ? office.getId() : null) + .build(); + } + + @Transactional + @Override + public CommandProcessingResult withdrawal(final Long accountId, final JsonCommand command) { + context.authenticatedUser(); + + final CurrentAccount account = findActiveAccount(accountId); + final Office office = account.getOffice(); + + LocalDate txnDate = command.localDateValueOfParameterNamed("transactionDate"); + if (txnDate == null) txnDate = LocalDate.now(); + + final BigDecimal amount = command.bigDecimalValueOfParameterNamed("transactionAmount"); + validatePositiveAmount(amount, accountId, "withdrawal"); + + final BigDecimal currentBalance = safeBalance(account.getAccountBalanceDerived()); + final BigDecimal onHold = safeBalance(account.getOnHoldFundsDerived()); + final BigDecimal available = currentBalance.subtract(onHold); + + if (!account.isAllowOverdraft() && amount.compareTo(available) > 0) { + throw new GeneralPlatformDomainRuleException("error.msg.current.account.insufficient.balance", + "Insufficient available balance. Available: " + available + ", Requested: " + amount, accountId); + } + + final String refNo = command.stringValueOfParameterNamed("accountNumber"); + + final CurrentAccountTransaction txn = CurrentAccountTransaction.withdrawal(account, office, amount, txnDate, refNo); + + final BigDecimal newBalance = currentBalance.subtract(amount); + txn.setRunningBalanceDerived(newBalance); + + currentAccountTransactionRepository.saveAndFlush(txn); + + account.setAccountBalanceDerived(newBalance); + account.setTotalWithdrawalsDerived(safeBalance(account.getTotalWithdrawalsDerived()).add(amount)); + currentAccountRepository.save(account); + + // Withdrawal GL: DR SAVINGS_CONTROL (liability down), CR SAVINGS_REFERENCE (cash out) + postTransactionGl(account, txn, JournalEntryType.CREDIT, JournalEntryType.DEBIT, "Current Account Withdrawal"); + + return new CommandProcessingResultBuilder() + .withEntityId(txn.getId()) + .withSavingsId(accountId) + .withClientId(account.getClient() != null ? account.getClient().getId() : null) + .withOfficeId(office != null ? office.getId() : null) + .build(); + } + + @Transactional + @Override + public CommandProcessingResult holdAmount(final Long accountId, final JsonCommand command) { + context.authenticatedUser(); + + final CurrentAccount account = findActiveAccount(accountId); + final Office office = account.getOffice(); + + LocalDate txnDate = command.localDateValueOfParameterNamed("transactionDate"); + if (txnDate == null) txnDate = LocalDate.now(); + + final BigDecimal amount = command.bigDecimalValueOfParameterNamed("transactionAmount"); + validatePositiveAmount(amount, accountId, "hold amount"); + + final BigDecimal currentBalance = safeBalance(account.getAccountBalanceDerived()); + final BigDecimal onHold = safeBalance(account.getOnHoldFundsDerived()); + final BigDecimal available = currentBalance.subtract(onHold); + + if (amount.compareTo(available) > 0) { + throw new GeneralPlatformDomainRuleException("error.msg.current.account.insufficient.balance.for.hold", + "Insufficient available balance to place hold. Available: " + available + ", Requested: " + amount, accountId); + } + + final String reason = command.stringValueOfParameterNamed("reasonForBlock"); + + final CurrentAccountTransaction txn = CurrentAccountTransaction.holdAmount(account, office, amount, txnDate, reason); + txn.setRunningBalanceDerived(currentBalance); + + currentAccountTransactionRepository.saveAndFlush(txn); + + account.setOnHoldFundsDerived(onHold.add(amount)); + currentAccountRepository.save(account); + + return new CommandProcessingResultBuilder() + .withEntityId(txn.getId()) + .withSavingsId(accountId) + .withClientId(account.getClient() != null ? account.getClient().getId() : null) + .withOfficeId(office != null ? office.getId() : null) + .build(); + } + + @Transactional + @Override + public CommandProcessingResult releaseAmount(final Long accountId, final Long transactionId) { + context.authenticatedUser(); + + final CurrentAccount account = findActiveAccount(accountId); + final Office office = account.getOffice(); + + final CurrentAccountTransaction holdTxn = currentAccountTransactionRepository.findById(transactionId) + .orElseThrow(() -> new GeneralPlatformDomainRuleException( + "error.msg.current.account.transaction.not.found", + "Hold transaction not found: " + transactionId, transactionId)); + + if (!holdTxn.getCurrentAccount().getId().equals(accountId)) { + throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.not.found", + "Transaction does not belong to this account.", transactionId); + } + + if (holdTxn.isReversed()) { + throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.already.reversed", + "Hold transaction is already released/reversed.", transactionId); + } + + final BigDecimal holdAmount = holdTxn.getAmount(); + final BigDecimal currentBalance = safeBalance(account.getAccountBalanceDerived()); + + // Mark hold transaction as reversed + holdTxn.setReversed(true); + currentAccountTransactionRepository.save(holdTxn); + + // Create release transaction + final CurrentAccountTransaction releaseTxn = CurrentAccountTransaction.releaseAmount( + account, office, holdAmount, LocalDate.now(), transactionId); + releaseTxn.setRunningBalanceDerived(currentBalance); + currentAccountTransactionRepository.saveAndFlush(releaseTxn); + + // Reduce onHold + final BigDecimal onHold = safeBalance(account.getOnHoldFundsDerived()); + account.setOnHoldFundsDerived(onHold.subtract(holdAmount).max(BigDecimal.ZERO)); + currentAccountRepository.save(account); + + return new CommandProcessingResultBuilder() + .withEntityId(releaseTxn.getId()) + .withSavingsId(accountId) + .build(); + } + + @Transactional + @Override + public CommandProcessingResult undoTransaction(final Long accountId, final Long transactionId, final boolean isReversal) { + context.authenticatedUser(); + + final CurrentAccount account = findActiveAccount(accountId); + + final CurrentAccountTransaction txn = currentAccountTransactionRepository.findById(transactionId) + .orElseThrow(() -> new GeneralPlatformDomainRuleException( + "error.msg.current.account.transaction.not.found", + "Transaction not found: " + transactionId, transactionId)); + + if (!txn.getCurrentAccount().getId().equals(accountId)) { + throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.not.found", + "Transaction does not belong to this account.", transactionId); + } + + if (txn.isReversed()) { + throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.already.reversed", + "Transaction is already reversed.", transactionId); + } + + reverseTransactionBalances(account, txn); + + txn.setReversed(true); + currentAccountTransactionRepository.save(txn); + currentAccountRepository.save(account); + + return new CommandProcessingResultBuilder() + .withEntityId(transactionId) + .withSavingsId(accountId) + .build(); + } + + @Transactional + @Override + public CommandProcessingResult reverseTransaction(final Long accountId, final Long transactionId, + final boolean isReversal, final JsonCommand command) { + context.authenticatedUser(); + + final CurrentAccount account = findActiveAccount(accountId); + + final CurrentAccountTransaction original = currentAccountTransactionRepository.findById(transactionId) + .orElseThrow(() -> new GeneralPlatformDomainRuleException( + "error.msg.current.account.transaction.not.found", + "Transaction not found: " + transactionId, transactionId)); + + if (!original.getCurrentAccount().getId().equals(accountId)) { + throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.not.found", + "Transaction does not belong to this account.", transactionId); + } + + if (original.isReversed()) { + throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.already.reversed", + "Transaction is already reversed.", transactionId); + } + + reverseTransactionBalances(account, original); + original.setReversed(true); + currentAccountTransactionRepository.save(original); + + // Create a reversal entry + final CurrentAccountTransaction reversalTxn = CurrentAccountTransaction.reversal( + account, account.getOffice(), original.getAmount(), LocalDate.now(), transactionId); + reversalTxn.setRunningBalanceDerived(safeBalance(account.getAccountBalanceDerived())); + currentAccountTransactionRepository.saveAndFlush(reversalTxn); + + currentAccountRepository.save(account); + + return new CommandProcessingResultBuilder() + .withEntityId(reversalTxn.getId()) + .withSavingsId(accountId) + .build(); + } + + @Transactional + @Override + public CommandProcessingResult adjustTransaction(final Long accountId, final Long transactionId, + final JsonCommand command) { + context.authenticatedUser(); + + final CurrentAccount account = findActiveAccount(accountId); + final Office office = account.getOffice(); + + final CurrentAccountTransaction original = currentAccountTransactionRepository.findById(transactionId) + .orElseThrow(() -> new GeneralPlatformDomainRuleException( + "error.msg.current.account.transaction.not.found", + "Transaction not found: " + transactionId, transactionId)); + + if (!original.getCurrentAccount().getId().equals(accountId)) { + throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.not.found", + "Transaction does not belong to this account.", transactionId); + } + + if (original.isReversed()) { + throw new GeneralPlatformDomainRuleException("error.msg.current.account.transaction.already.reversed", + "Transaction is already reversed.", transactionId); + } + + final BigDecimal newAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); + validatePositiveAmount(newAmount, accountId, "adjust"); + + LocalDate newDate = command.localDateValueOfParameterNamed("transactionDate"); + if (newDate == null) newDate = original.getTransactionDate(); + + // Undo original + reverseTransactionBalances(account, original); + original.setReversed(true); + currentAccountTransactionRepository.save(original); + + // Create adjusted transaction of the same type + final CurrentAccountTransaction adjusted; + if (original.getTransactionTypeEnum() == 1) { + adjusted = CurrentAccountTransaction.deposit(account, office, newAmount, newDate, + command.stringValueOfParameterNamed("accountNumber")); + } else { + adjusted = CurrentAccountTransaction.withdrawal(account, office, newAmount, newDate, + command.stringValueOfParameterNamed("accountNumber")); + } + adjusted.setOriginalTransactionId(transactionId); + adjusted.setRunningBalanceDerived(safeBalance(account.getAccountBalanceDerived())); + currentAccountTransactionRepository.saveAndFlush(adjusted); + + // Apply new transaction to balance + applyTransactionToBalance(account, adjusted); + currentAccountRepository.save(account); + + return new CommandProcessingResultBuilder() + .withEntityId(adjusted.getId()) + .withSavingsId(accountId) + .build(); + } + + // ── Helpers ───────────────────────────────────────────────────────────────── + + private CurrentAccount findActiveAccount(final Long accountId) { + final CurrentAccount account = currentAccountRepository.findById(accountId) + .orElseThrow(() -> new CurrentAccountNotFoundException(accountId)); + if (!Integer.valueOf(300).equals(account.getStatusEnum())) { + throw new GeneralPlatformDomainRuleException("error.msg.current.account.not.active", + "Current account is not in Active state.", accountId); + } + return account; + } + + private void validatePositiveAmount(final BigDecimal amount, final Long accountId, final String operation) { + if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { + throw new GeneralPlatformDomainRuleException( + "error.msg.current.account." + operation + ".invalid.amount", + "Transaction amount must be greater than zero for " + operation + ".", accountId); + } + } + + private BigDecimal safeBalance(final BigDecimal value) { + return value != null ? value : BigDecimal.ZERO; + } + + private void reverseTransactionBalances(final CurrentAccount account, final CurrentAccountTransaction txn) { + final BigDecimal amount = txn.getAmount(); + final Integer type = txn.getTransactionTypeEnum(); + + if (Integer.valueOf(1).equals(type)) { + // Was a deposit: reverse it + account.setAccountBalanceDerived(safeBalance(account.getAccountBalanceDerived()).subtract(amount)); + account.setTotalDepositsDerived(safeBalance(account.getTotalDepositsDerived()).subtract(amount).max(BigDecimal.ZERO)); + } else if (Integer.valueOf(2).equals(type)) { + // Was a withdrawal: reverse it + account.setAccountBalanceDerived(safeBalance(account.getAccountBalanceDerived()).add(amount)); + account.setTotalWithdrawalsDerived(safeBalance(account.getTotalWithdrawalsDerived()).subtract(amount).max(BigDecimal.ZERO)); + } + } + + private void applyTransactionToBalance(final CurrentAccount account, final CurrentAccountTransaction txn) { + final BigDecimal amount = txn.getAmount(); + final Integer type = txn.getTransactionTypeEnum(); + + if (Integer.valueOf(1).equals(type)) { + account.setAccountBalanceDerived(safeBalance(account.getAccountBalanceDerived()).add(amount)); + account.setTotalDepositsDerived(safeBalance(account.getTotalDepositsDerived()).add(amount)); + } else if (Integer.valueOf(2).equals(type)) { + account.setAccountBalanceDerived(safeBalance(account.getAccountBalanceDerived()).subtract(amount)); + account.setTotalWithdrawalsDerived(safeBalance(account.getTotalWithdrawalsDerived()).add(amount)); + } + } + + // GL: cashEntryType=DEBIT/CREDIT for SAVINGS_REFERENCE; controlEntryType for SAVINGS_CONTROL + private void postTransactionGl(final CurrentAccount account, final CurrentAccountTransaction txn, + final JournalEntryType cashEntryType, final JournalEntryType controlEntryType, final String description) { + try { + final CurrentProduct product = currentProductRepository.findById(account.getProductId()).orElse(null); + if (product == null) return; + final Integer accountingType = product.getAccountingType(); + if (accountingType == null || accountingType == 1) return; // NONE = 1 + + final GLAccount cashAccount = findGlAccount(account.getProductId(), + CashAccountsForSavings.SAVINGS_REFERENCE.getValue()); + final GLAccount controlAccount = findGlAccount(account.getProductId(), + CashAccountsForSavings.SAVINGS_CONTROL.getValue()); + + if (cashAccount == null || controlAccount == null) { + log.warn("[GL] Missing GL account mapping for current account product id={}", account.getProductId()); + return; + } + + final String ref = "CAT" + txn.getId(); + postGlEntry(account, cashAccount, cashEntryType, txn.getAmount(), txn.getTransactionDate(), ref, description); + postGlEntry(account, controlAccount, controlEntryType, txn.getAmount(), txn.getTransactionDate(), ref, description); + } catch (Exception e) { + log.error("[GL] Failed GL posting for CA transaction id={}: {}", txn.getId(), e.getMessage(), e); + } + } + + private GLAccount findGlAccount(final Long productId, final int financialAccountType) { + final var mapping = productToGLAccountMappingRepository + .findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId( + productId, PortfolioProductType.SAVING.getValue(), financialAccountType, null); + return mapping != null ? mapping.getGlAccount() : null; + } + + private void postGlEntry(final CurrentAccount account, final GLAccount glAccount, + final JournalEntryType entryType, final BigDecimal amount, + final LocalDate date, final String transactionRef, final String description) { + final JournalEntry entry = JournalEntry.createNew( + account.getOffice(), null, glAccount, + account.getCurrencyCode(), transactionRef, + false, date, entryType, amount, + description, + ENTITY_TYPE_SAVINGS, account.getId(), + null, null, null, null, null); + journalEntryRepository.save(entry); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformService.java index 38ad12394..69355d32f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformService.java @@ -1,56 +1,59 @@ -/** - * 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.util.Collection; -import java.util.Map; -import org.apache.fineract.infrastructure.core.data.PaginationParameters; -import org.apache.fineract.infrastructure.core.service.Page; -import org.apache.fineract.portfolio.account.data.AccountTransferDTO; -import org.apache.fineract.portfolio.savings.DepositAccountType; -import org.apache.fineract.portfolio.savings.data.DepositAccountData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; - -public interface DepositAccountReadPlatformService { - - Collection retrieveAll(DepositAccountType depositAccountType, PaginationParameters paginationParameters); - - Page retrieveAllPaged(DepositAccountType depositAccountType, PaginationParameters paginationParameters); - - Collection retrieveAllForLookup(DepositAccountType depositAccountType); - - DepositAccountData retrieveOne(DepositAccountType depositAccountType, Long accountId); - - DepositAccountData retrieveOneWithClosureTemplate(DepositAccountType depositAccountType, Long accountId); - - DepositAccountData retrieveOneWithChartSlabs(DepositAccountType depositAccountType, Long productId); - - Collection retrieveAllTransactions(DepositAccountType depositAccountType, Long accountId); - - DepositAccountData retrieveTemplate(DepositAccountType depositAccountType, Long clientId, Long groupId, Long productId, - boolean staffInSelectedOfficeOnly); - - Collection retrieveForMaturityUpdate(); - - SavingsAccountTransactionData retrieveRecurringAccountDepositTransactionTemplate(Long accountId); - - Collection retrieveDataForInterestTransfer(); - - Collection> retriveDataForRDScheduleCreation(); -} +/** + * 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.util.Collection; +import java.util.Map; +import org.apache.fineract.infrastructure.core.data.PaginationParameters; +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.portfolio.account.data.AccountTransferDTO; +import org.apache.fineract.portfolio.savings.DepositAccountType; +import org.apache.fineract.portfolio.savings.data.DepositAccountData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; + +public interface DepositAccountReadPlatformService { + + Collection retrieveAll(DepositAccountType depositAccountType, PaginationParameters paginationParameters); + + Page retrieveAllPaged(DepositAccountType depositAccountType, PaginationParameters paginationParameters); + + Page retrieveAllPaged(DepositAccountType depositAccountType, PaginationParameters paginationParameters, + String accountNo, String clientName); + + Collection retrieveAllForLookup(DepositAccountType depositAccountType); + + DepositAccountData retrieveOne(DepositAccountType depositAccountType, Long accountId); + + DepositAccountData retrieveOneWithClosureTemplate(DepositAccountType depositAccountType, Long accountId); + + DepositAccountData retrieveOneWithChartSlabs(DepositAccountType depositAccountType, Long productId); + + Collection retrieveAllTransactions(DepositAccountType depositAccountType, Long accountId); + + DepositAccountData retrieveTemplate(DepositAccountType depositAccountType, Long clientId, Long groupId, Long productId, + boolean staffInSelectedOfficeOnly); + + Collection retrieveForMaturityUpdate(); + + SavingsAccountTransactionData retrieveRecurringAccountDepositTransactionTemplate(Long accountId); + + Collection retrieveDataForInterestTransfer(); + + Collection> retriveDataForRDScheduleCreation(); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java index fbfde7e34..bd466b09e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java @@ -1,1939 +1,1973 @@ -/** - * 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 static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY_DB_FIELD; - -import java.math.BigDecimal; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import lombok.RequiredArgsConstructor; -import org.apache.fineract.infrastructure.codes.data.CodeValueData; -import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; -import org.apache.fineract.infrastructure.core.data.EnumOptionData; -import org.apache.fineract.infrastructure.core.data.PaginationParameters; -import org.apache.fineract.infrastructure.core.data.PaginationParametersDataValidator; -import org.apache.fineract.infrastructure.core.domain.ExternalId; -import org.apache.fineract.infrastructure.core.domain.JdbcSupport; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.core.service.Page; -import org.apache.fineract.infrastructure.core.service.PaginationHelper; -import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.organisation.monetary.data.CurrencyData; -import org.apache.fineract.organisation.staff.data.StaffData; -import org.apache.fineract.organisation.staff.service.StaffReadPlatformService; -import org.apache.fineract.portfolio.account.PortfolioAccountType; -import org.apache.fineract.portfolio.account.service.AccountNumberGenerator; -import org.apache.fineract.portfolio.account.data.AccountTransferDTO; -import org.apache.fineract.portfolio.account.data.AccountTransferData; -import org.apache.fineract.portfolio.account.domain.AccountTransferType; -import org.apache.fineract.portfolio.calendar.data.CalendarData; -import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType; -import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType; -import org.apache.fineract.portfolio.calendar.domain.CalendarType; -import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService; -import org.apache.fineract.portfolio.charge.data.ChargeData; -import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService; -import org.apache.fineract.portfolio.charge.util.ConvertChargeDataToSpecificChargeData; -import org.apache.fineract.portfolio.client.data.ClientData; -import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; -import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; -import org.apache.fineract.portfolio.common.service.CommonEnumerations; -import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService; -import org.apache.fineract.portfolio.group.data.GroupGeneralData; -import org.apache.fineract.portfolio.group.service.GroupReadPlatformService; -import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData; -import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartReadPlatformService; -import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData; -import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; -import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService; -import org.apache.fineract.portfolio.savings.SavingsApiConstants; -import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType; -import org.apache.fineract.portfolio.savings.DepositAccountType; -import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType; -import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType; -import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType; -import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; -import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; -import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; -import org.apache.fineract.portfolio.savings.data.DepositAccountData; -import org.apache.fineract.portfolio.savings.data.DepositAccountInterestRateChartData; -import org.apache.fineract.portfolio.savings.data.DepositProductData; -import org.apache.fineract.portfolio.savings.data.FixedDepositAccountData; -import org.apache.fineract.portfolio.savings.data.RecurringDepositAccountData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; -import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; -import org.apache.fineract.portfolio.savings.exception.DepositAccountNotFoundException; -import org.apache.fineract.portfolio.tax.data.TaxGroupData; -import org.apache.fineract.useradministration.domain.AppUser; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import lombok.extern.slf4j.Slf4j; -import org.springframework.util.CollectionUtils; - -@Slf4j -@RequiredArgsConstructor -public class DepositAccountReadPlatformServiceImpl implements DepositAccountReadPlatformService { - - private static final FixedDepositAccountMapper FIXED_DEPOSIT_ACCOUNT_MAPPER = new FixedDepositAccountMapper(); - private static final RecurringDepositAccountMapper RECURRING_DEPOSIT_ACCOUNT_MAPPER = new RecurringDepositAccountMapper(); - private static final DepositAccountLookupMapper DEPOSIT_ACCOUNT_LOOKUP_MAPPER = new DepositAccountLookupMapper(); - private static final DepositAccountForMaturityMapper DEPOSIT_ACCOUNT_FOR_MATURITY_MAPPER = new DepositAccountForMaturityMapper(); - // allowed column names for sorting the query result - private static final Set supportedOrderByValues = new HashSet<>(Arrays.asList("id", "accountNumbr", "officeId", "officeName")); - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - private final PlatformSecurityContext context; - private final JdbcTemplate jdbcTemplate; - private final DepositAccountInterestRateChartReadPlatformService accountChartReadPlatformService; - private final InterestRateChartReadPlatformService productChartReadPlatformService; - private final PaginationParametersDataValidator paginationParametersDataValidator; - private final DatabaseSpecificSQLGenerator sqlGenerator; - private final PaginationHelper paginationHelper; - private final SavingsAccountTransactionsMapper transactionsMapper = new SavingsAccountTransactionsMapper(); - private final ClientReadPlatformService clientReadPlatformService; - private final GroupReadPlatformService groupReadPlatformService; - private final DepositProductReadPlatformService depositProductReadPlatformService; - private final SavingsDropdownReadPlatformService savingsDropdownReadPlatformService; - private final ChargeReadPlatformService chargeReadPlatformService; - private final StaffReadPlatformService staffReadPlatformService; - private final DepositsDropdownReadPlatformService depositsDropdownReadPlatformService; - private final SavingsAccountReadPlatformService savingsAccountReadPlatformService; - private final RecurringAccountDepositTransactionTemplateMapper rdTransactionTemplateMapper = new RecurringAccountDepositTransactionTemplateMapper(); - private final DropdownReadPlatformService dropdownReadPlatformService; - private final CalendarReadPlatformService calendarReadPlatformService; - private final PaymentTypeReadService paymentTypeReadPlatformService; - private final AccountNumberGenerator accountNumberGenerator; - private final CodeValueReadPlatformService codeValueReadPlatformService; - - @Override - public Collection retrieveAll(final DepositAccountType depositAccountType, - final PaginationParameters paginationParameters) { - - this.context.authenticatedUser(); - this.paginationParametersDataValidator.validateParameterValues(paginationParameters, supportedOrderByValues, - depositAccountType.resourceName()); - - final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType); - if (depositAccountMapper == null) { - return null; - } - - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("select "); - sqlBuilder.append(depositAccountMapper.schema()); - sqlBuilder.append(" where sa.deposit_type_enum = ? "); - // always append at the end of a sql statement - sqlBuilder.append(paginationParameters.paginationSql()); - - return jdbcTemplate.query(sqlBuilder.toString(), depositAccountMapper, new Object[] { depositAccountType.getValue() }); // NOSONAR - } - - @Override - public Page retrieveAllPaged(final DepositAccountType depositAccountType, - final PaginationParameters paginationParameters) { - - this.paginationParametersDataValidator.validateParameterValues(paginationParameters, supportedOrderByValues, - depositAccountType.resourceName()); - - final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType); - if (depositAccountMapper == null) { - return null; - } - - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); - sqlBuilder.append(depositAccountMapper.schema()); - sqlBuilder.append(" where sa.deposit_type_enum = ? "); - sqlBuilder.append(paginationParameters.paginationSql()); - - return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), new Object[] { depositAccountType.getValue() }, - depositAccountMapper); - } - - @Override - public Collection retrieveAllForLookup(final DepositAccountType depositAccountType) { - - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("select "); - sqlBuilder.append(DEPOSIT_ACCOUNT_LOOKUP_MAPPER.schema()); - sqlBuilder.append(" where sa.deposit_type_enum = ? "); - - return this.jdbcTemplate.query(sqlBuilder.toString(), DEPOSIT_ACCOUNT_LOOKUP_MAPPER, depositAccountType.getValue()); - } - - @Override - public Collection retrieveForMaturityUpdate() { - - final StringBuilder sqlBuilder = new StringBuilder(200); - sqlBuilder.append("SELECT "); - sqlBuilder.append(DEPOSIT_ACCOUNT_FOR_MATURITY_MAPPER.schema()); - sqlBuilder.append(" WHERE da.deposit_type_enum in (?, ?) and da.status_enum = ?"); - - return this.jdbcTemplate.query(sqlBuilder.toString(), DEPOSIT_ACCOUNT_FOR_MATURITY_MAPPER, - DepositAccountType.FIXED_DEPOSIT.getValue(), DepositAccountType.RECURRING_DEPOSIT.getValue(), - SavingsAccountStatusType.ACTIVE.getValue()); - } - - @Override - public DepositAccountData retrieveOne(final DepositAccountType depositAccountType, final Long accountId) { - try { - this.context.authenticatedUser(); - - final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType); - if (depositAccountMapper == null) { - return null; - } - - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("select "); - sqlBuilder.append(depositAccountMapper.schema()); - sqlBuilder.append(" where sa.id = ? and sa.deposit_type_enum = ? "); - - return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), depositAccountMapper, accountId, depositAccountType.getValue()); - - } catch (final EmptyResultDataAccessException e) { - throw new DepositAccountNotFoundException(depositAccountType, accountId, e); - } - } - - @Override - public DepositAccountData retrieveOneWithClosureTemplate(final DepositAccountType depositAccountType, final Long accountId) { - try { - this.context.authenticatedUser(); - - final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType); - if (depositAccountMapper == null) { - return null; - } - - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("select "); - sqlBuilder.append(depositAccountMapper.schema()); - sqlBuilder.append(" where sa.id = ? and sa.deposit_type_enum = ? "); - - DepositAccountData account = this.jdbcTemplate.queryForObject(sqlBuilder.toString(), depositAccountMapper, accountId, - depositAccountType.getValue()); - Collection onAccountClosureOptions = SavingsEnumerations - .depositAccountOnClosureType(DepositAccountOnClosureType.values()); - final Collection paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); - final Collection savingsAccountDatas = this.savingsAccountReadPlatformService - .retrieveActiveForLookup(account.getClientId(), DepositAccountType.SAVINGS_DEPOSIT); - if (depositAccountType.isFixedDeposit()) { - account = FixedDepositAccountData.withClosureTemplateDetails((FixedDepositAccountData) account, onAccountClosureOptions, - paymentTypeOptions, savingsAccountDatas); - } else if (depositAccountType.isRecurringDeposit()) { - account = RecurringDepositAccountData.withClosureTemplateDetails((RecurringDepositAccountData) account, - onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas); - } - - return account; - - } catch (final EmptyResultDataAccessException e) { - throw new DepositAccountNotFoundException(depositAccountType, accountId, e); - } - } - - @Override - public DepositAccountData retrieveOneWithChartSlabs(final DepositAccountType depositAccountType, Long accountId) { - DepositAccountData depositAccount = this.retrieveOne(depositAccountType, accountId); - DepositAccountInterestRateChartData chart = this.accountChartReadPlatformService.retrieveOneWithSlabsOnAccountId(accountId); - - if (depositAccountType.isFixedDeposit()) { - depositAccount = FixedDepositAccountData.withInterestChart((FixedDepositAccountData) depositAccount, chart); - } else if (depositAccountType.isRecurringDeposit()) { - CalendarData calendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(accountId, - CalendarEntityType.SAVINGS.getValue()); - Integer frequency = 1; - EnumOptionData frequencyType = null; - if (calendar != null) { - frequency = calendar.getInterval() == -1 ? 1 : calendar.getInterval(); - final CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType - .fromInt(calendar.getFrequency().getId().intValue()); - final PeriodFrequencyType periodFrequencyType = CalendarFrequencyType.from(calendarFrequencyType); - frequencyType = CommonEnumerations.termFrequencyType(periodFrequencyType, "recurring.deposit.frequency."); - } - depositAccount = RecurringDepositAccountData.withInterestChartAndRecurringDetails((RecurringDepositAccountData) depositAccount, - chart, frequency, frequencyType); - } - - return depositAccount; - } - - @Override - public Collection retrieveAllTransactions(final DepositAccountType depositAccountType, - final Long accountId) { - - final String sql = "select " + this.transactionsMapper.schema() - + " where sa.id = ? and sa.deposit_type_enum = ? order by tr.transaction_date DESC, tr.id DESC"; - - return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { accountId, depositAccountType.getValue() }); // NOSONAR - } - - @Override - public DepositAccountData retrieveTemplate(final DepositAccountType depositAccountType, final Long clientId, final Long groupId, - final Long productId, final boolean staffInSelectedOfficeOnly) { - - final AppUser loggedInUser = this.context.authenticatedUser(); - Long officeId = loggedInUser.getOffice().getId(); - - ClientData client = null; - Collection savingsAccountDatas = null; - if (clientId != null) { - client = this.clientReadPlatformService.retrieveOne(clientId); - officeId = client.getOfficeId(); - savingsAccountDatas = this.savingsAccountReadPlatformService.retrieveActiveForLookup(clientId, - DepositAccountType.SAVINGS_DEPOSIT); - } - - GroupGeneralData group = null; - if (groupId != null) { - group = this.groupReadPlatformService.retrieveOne(groupId); - officeId = group.getOfficeId(); - } - - final Collection preClosurePenalInterestOnTypeOptions = this.depositsDropdownReadPlatformService - .retrievePreClosurePenalInterestOnTypeOptions(); - - final Collection periodFrequencyTypeOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions(); - final Collection productOptions = this.depositProductReadPlatformService - .retrieveAllForLookup(depositAccountType); - DepositAccountData template = null; - if (productId != null) { - - final DepositAccountTemplateMapper mapper = getDepositAccountTemplaMapper(depositAccountType, client, group); - - final String sql = "select " + mapper.schema() + " where sa.id = ? and sa.deposit_type_enum = ? "; - template = this.jdbcTemplate.queryForObject(sql, mapper, new Object[] { productId, depositAccountType.getValue() }); // NOSONAR - - final Collection interestCompoundingPeriodTypeOptions = this.savingsDropdownReadPlatformService - .retrieveCompoundingInterestPeriodTypeOptions(); - - final Collection interestPostingPeriodTypeOptions = this.savingsDropdownReadPlatformService - .retrieveInterestPostingPeriodTypeOptions(); - - final Collection interestCalculationTypeOptions = this.savingsDropdownReadPlatformService - .retrieveInterestCalculationTypeOptions(); - - final Collection interestCalculationDaysInYearTypeOptions = this.savingsDropdownReadPlatformService - .retrieveInterestCalculationDaysInYearTypeOptions(); - - final Collection lockinPeriodFrequencyTypeOptions = this.savingsDropdownReadPlatformService - .retrieveLockinPeriodFrequencyTypeOptions(); - - final Collection withdrawalFeeTypeOptions = this.savingsDropdownReadPlatformService - .retrievewithdrawalFeeTypeOptions(); - - final Collection transactions = null; - final Collection productCharges = this.chargeReadPlatformService.retrieveSavingsProductCharges(productId); - // update charges from Product charges - final Collection charges = fromChargesToSavingsCharges(productCharges); - - final boolean feeChargesOnly = false; - final Collection chargeOptions = this.chargeReadPlatformService - .retrieveSavingsProductApplicableCharges(feeChargesOnly); - - final Collection maturityInstructionOptions = this.depositsDropdownReadPlatformService - .maturityInstructionOptions(); - - Collection fieldOfficerOptions = null; - - if (officeId != null) { - - if (staffInSelectedOfficeOnly) { - // only bring back loan officers in selected branch/office - final Collection fieldOfficersInBranch = this.staffReadPlatformService - .retrieveAllLoanOfficersInOfficeById(officeId); - - if (!CollectionUtils.isEmpty(fieldOfficersInBranch)) { - fieldOfficerOptions = new ArrayList<>(fieldOfficersInBranch); - } - } else { - // by default bring back all officers in selected - // branch/office as well as officers in office above - // this office - final boolean restrictToLoanOfficersOnly = true; - final Collection loanOfficersInHierarchy = this.staffReadPlatformService - .retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(officeId, restrictToLoanOfficersOnly); - - if (!CollectionUtils.isEmpty(loanOfficersInHierarchy)) { - fieldOfficerOptions = new ArrayList<>(loanOfficersInHierarchy); - } - } - } - - // retrieve chart Slabs - final InterestRateChartData productChartData = this.productChartReadPlatformService.retrieveActiveChartWithTemplate(productId); - final DepositAccountInterestRateChartData accountChart = (productChartData != null) - ? DepositAccountInterestRateChartData.from(productChartData) - : null; - - if (depositAccountType.isFixedDeposit()) { - - template = FixedDepositAccountData.withTemplateOptions((FixedDepositAccountData) template, productOptions, - fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, - interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, - withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions, - periodFrequencyTypeOptions, savingsAccountDatas, maturityInstructionOptions); - - template = FixedDepositAccountData.withInterestChart((FixedDepositAccountData) template, accountChart); - - try { - final Collection accountTypeOpts = this.codeValueReadPlatformService - .retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_ACCOUNT_TYPE); - final Collection customerSegmentOpts = this.codeValueReadPlatformService - .retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_CUSTOMER_SEGMENT); - ((FixedDepositAccountData) template).setAccountTypeOptions(accountTypeOpts); - ((FixedDepositAccountData) template).setCustomerSegmentOptions(customerSegmentOpts); - } catch (final Exception ex) { - log.warn("Failed to load FD account type/customer segment options: {}", ex.getMessage()); - } - - try { - final String preview = accountNumberGenerator.previewAccountNumber(productId, officeId); - template.setPreviewAccountNo(preview); - } catch (final Exception ex) { - log.warn("Failed to generate preview account number for FD productId={}: {}", productId, ex.getMessage()); - } - } else if (depositAccountType.isRecurringDeposit()) { - // Save CBS fields from mapper result before factory methods create new objects - final RecurringDepositAccountData rdTemplateWithCbs = (RecurringDepositAccountData) template; - template = RecurringDepositAccountData.withTemplateOptions(rdTemplateWithCbs, productOptions, - fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, - interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, - withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions, - periodFrequencyTypeOptions); - template = RecurringDepositAccountData.withInterestChartAndRecurringDetails((RecurringDepositAccountData) template, - accountChart, null, null); - // Re-apply CBS product fields lost during factory method object creation - ((RecurringDepositAccountData) template).copyCbsFieldsFrom(rdTemplateWithCbs); - - // Populate account type / customer segment options from code values - try { - final Collection accountTypeOpts = this.codeValueReadPlatformService - .retrieveCodeValuesByCode(SavingsApiConstants.RD_PRODUCT_ACCOUNT_TYPE); - final Collection customerSegmentOpts = this.codeValueReadPlatformService - .retrieveCodeValuesByCode(SavingsApiConstants.RD_PRODUCT_CUSTOMER_SEGMENT); - ((RecurringDepositAccountData) template).setAccountTypeOptions(accountTypeOpts); - ((RecurringDepositAccountData) template).setCustomerSegmentOptions(customerSegmentOpts); - } catch (final Exception ex) { - log.warn("Failed to load RD account type/customer segment options: {}", ex.getMessage()); - } - - // Generate preview account number - try { - final String preview = accountNumberGenerator.previewAccountNumber(productId, officeId); - template.setPreviewAccountNo(preview); - } catch (final Exception ex) { - log.warn("Failed to generate preview account number for RD productId={}: {}", productId, ex.getMessage()); - } - } - - } else { - - String clientName = null; - if (client != null) { - clientName = client.getDisplayName(); - } - - String groupName = null; - if (group != null) { - groupName = group.getName(); - } - - final Collection fieldOfficerOptions = null; - final Collection interestCompoundingPeriodTypeOptions = null; - final Collection interestPostingPeriodTypeOptions = null; - final Collection interestCalculationTypeOptions = null; - final Collection interestCalculationDaysInYearTypeOptions = null; - final Collection lockinPeriodFrequencyTypeOptions = null; - final Collection withdrawalFeeTypeOptions = null; - - final Collection transactions = null; - final Collection charges = null; - final Collection maturityInstructionOptions = null; - - final boolean feeChargesOnly = true; - final Collection chargeOptions = this.chargeReadPlatformService - .retrieveSavingsProductApplicableCharges(feeChargesOnly); - - if (depositAccountType.isFixedDeposit()) { - - template = FixedDepositAccountData.withClientTemplate(clientId, clientName, groupId, groupName); - - template = FixedDepositAccountData.withTemplateOptions((FixedDepositAccountData) template, productOptions, - fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, - interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, - withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions, - periodFrequencyTypeOptions, savingsAccountDatas, maturityInstructionOptions); - } else if (depositAccountType.isRecurringDeposit()) { - - template = RecurringDepositAccountData.withClientTemplate(clientId, clientName, groupId, groupName); - - template = RecurringDepositAccountData.withTemplateOptions((RecurringDepositAccountData) template, productOptions, - fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, - interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, - withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions, - periodFrequencyTypeOptions); - } - } - - return template; - } - - @Override - public SavingsAccountTransactionData retrieveRecurringAccountDepositTransactionTemplate(final Long accountId) { - - try { - final String sql = "select " + this.rdTransactionTemplateMapper.schema() - + " where sa.id = ? and sa.deposit_type_enum = ? order by mss.installment limit 1"; - return this.jdbcTemplate.queryForObject(sql, this.rdTransactionTemplateMapper, // NOSONAR - accountId, accountId, DepositAccountType.RECURRING_DEPOSIT.getValue()); - } catch (final EmptyResultDataAccessException e) { - throw new DepositAccountNotFoundException(DepositAccountType.RECURRING_DEPOSIT, accountId, e); - } - } - - @Override - public Collection retrieveDataForInterestTransfer() { - final StringBuilder sqlBuilder = new StringBuilder(300); - AccountTransferMapper mapper = new AccountTransferMapper(); - sqlBuilder.append("SELECT "); - sqlBuilder.append(mapper.schema()); - sqlBuilder.append(" where da.transfer_interest_to_linked_account = true and "); - sqlBuilder.append( - "st.transaction_date > (select coalesce(max(sat.transaction_date),sa.activatedon_date) from m_savings_account_transaction sat where sat.transaction_type_enum = ? and sat.savings_account_id = sa.id and sat.is_reversed=false) "); - sqlBuilder.append( - "and st.transaction_type_enum = ? and sa.status_enum = ? and st.is_reversed=false and st.transaction_date > coalesce(sa.lockedin_until_date_derived,sa.activatedon_date)"); - - return this.jdbcTemplate.query(sqlBuilder.toString(), mapper, SavingsAccountTransactionType.WITHDRAWAL.getValue(), - SavingsAccountTransactionType.INTEREST_POSTING.getValue(), SavingsAccountStatusType.ACTIVE.getValue()); - } - - @Override - public Collection> retriveDataForRDScheduleCreation() { - String today = DATE_TIME_FORMATTER.format(DateUtils.getBusinessLocalDate()); - final StringBuilder sb = new StringBuilder(300); - sb.append(" select rd.savings_account_id savingsId, rd.mandatory_recommended_deposit_amount as amount,"); - sb.append(" mc.recurrence as recurrence ,"); - sb.append(" max(ms.duedate) as dueDate , max(ms.installment) as installment,"); - sb.append(" count(ms.installment) as futureInstallments"); - sb.append(" from m_deposit_account_term_and_preclosure dat "); - sb.append(" inner join m_savings_account sa on sa.id = dat.savings_account_id and sa.status_enum = ?"); - sb.append(" inner join m_deposit_account_recurring_detail rd on rd.savings_account_id = dat.savings_account_id "); - sb.append(" inner join m_calendar_instance mci on mci.entity_type_enum = ? and mci.entity_id = dat.savings_account_id "); - sb.append(" inner join m_calendar mc on mc.id = mci.calendar_id and mc.calendar_type_enum = ?"); - sb.append(" inner join m_mandatory_savings_schedule ms on ms.savings_account_id = dat.savings_account_id and ms.duedate > '" + today - + "'"); - sb.append(" where dat.deposit_period is null"); - sb.append(" group by ms.savings_account_id, rd.mandatory_recommended_deposit_amount, mc.recurrence, rd.savings_account_id"); - - return this.jdbcTemplate.queryForList(sb.toString(), SavingsAccountStatusType.ACTIVE.getValue(), - CalendarEntityType.SAVINGS.getValue(), CalendarType.COLLECTION.getValue()); - } - - private DepositAccountMapper getDepositAccountMapper(final DepositAccountType depositAccountType) { - if (depositAccountType.isFixedDeposit()) { - return FIXED_DEPOSIT_ACCOUNT_MAPPER; - } else if (depositAccountType.isRecurringDeposit()) { - return RECURRING_DEPOSIT_ACCOUNT_MAPPER; - } - return null; - } - - private DepositAccountTemplateMapper getDepositAccountTemplaMapper(final DepositAccountType depositAccountType, ClientData client, - GroupGeneralData group) { - if (depositAccountType.isFixedDeposit()) { - return new FixedDepositAccountTemplateMapper(client, group); - } else if (depositAccountType.isRecurringDeposit()) { - return new RecurringDepositAccountTemplateMapper(client, group); - } - return null; - } - - private Collection fromChargesToSavingsCharges(final Collection productCharges) { - final Collection savingsCharges = new ArrayList<>(); - for (final ChargeData chargeData : productCharges) { - final SavingsAccountChargeData savingsCharge = ConvertChargeDataToSpecificChargeData.toSavingsAccountChargeData(chargeData); - savingsCharges.add(savingsCharge); - } - return savingsCharges; - } - - private abstract static class DepositAccountMapper implements RowMapper { - - public static final String ACCOUNT_NO = "accountNo"; - public static final String ID = "id"; - public static final String EXTERNAL_ID = "externalId"; - public static final String GROUP_ID = "groupId"; - public static final String GROUP_NAME = "groupName"; - public static final String CLIENT_ID = "clientId"; - public static final String CLIENT_NAME = "clientName"; - public static final String PRODUCT_ID = "productId"; - public static final String PRODUCT_NAME = "productName"; - public static final String FIELD_OFFICER_ID = "fieldOfficerId"; - public static final String FIELD_OFFICER_NAME = "fieldOfficerName"; - public static final String STATUS_ENUM = "statusEnum"; - public static final String SUBMITTED_ON_DATE = "submittedOnDate"; - public static final String SUBMITTED_BY_USERNAME = "submittedByUsername"; - public static final String SUBMITTED_BY_LASTNAME = "submittedByLastname"; - public static final String SUBMITTED_BY_FIRSTNAME = "submittedByFirstname"; - public static final String REJECTED_ON_DATE = "rejectedOnDate"; - public static final String REJECTED_BY_USERNAME = "rejectedByUsername"; - public static final String REJECTED_BY_FIRSTNAME = "rejectedByFirstname"; - public static final String REJECTED_BY_LASTNAME = "rejectedByLastname"; - public static final String WITHDRAWN_ON_DATE = "withdrawnOnDate"; - public static final String WITHDRAWN_BY_USERNAME = "withdrawnByUsername"; - public static final String WITHDRAWN_BY_FIRSTNAME = "withdrawnByFirstname"; - public static final String WITHDRAWN_BY_LASTNAME = "withdrawnByLastname"; - public static final String APPROVED_ON_DATE = "approvedOnDate"; - public static final String APPROVED_BY_USERNAME = "approvedByUsername"; - public static final String APPROVED_BY_FIRSTNAME = "approvedByFirstname"; - public static final String APPROVED_BY_LASTNAME = "approvedByLastname"; - public static final String ACTIVATED_ON_DATE = "activatedOnDate"; - public static final String ACTIVATED_BY_USERNAME = "activatedByUsername"; - public static final String ACTIVATED_BY_FIRSTNAME = "activatedByFirstname"; - public static final String ACTIVATED_BY_LASTNAME = "activatedByLastname"; - public static final String CLOSED_ON_DATE = "closedOnDate"; - public static final String CLOSED_BY_USERNAME = "closedByUsername"; - public static final String CLOSED_BY_FIRSTNAME = "closedByFirstname"; - public static final String CLOSED_BY_LASTNAME = "closedByLastname"; - public static final String DEPOSIT_TYPE_ID = "depositTypeId"; - public static final String CURRENCY_CODE = "currencyCode"; - public static final String CURRENCY_NAME = "currencyName"; - public static final String CURRENCY_NAME_CODE = "currencyNameCode"; - public static final String CURRENCY_DISPLAY_SYMBOL = "currencyDisplaySymbol"; - public static final String CURRENCY_DIGITS = "currencyDigits"; - public static final String IN_MULTIPLES_OF = "inMultiplesOf"; - public static final String NOMINAL_ANNUAL_INTEREST_RATE = "nominalAnnualInterestRate"; - public static final String INTEREST_COMPOUNDING_PERIOD_TYPE = "interestCompoundingPeriodType"; - public static final String INTEREST_POSTING_PERIOD_TYPE = "interestPostingPeriodType"; - public static final String INTEREST_CALCULATION_TYPE = "interestCalculationType"; - public static final String INTEREST_CALCULATION_DAYS_IN_YEAR_TYPE = "interestCalculationDaysInYearType"; - public static final String MIN_REQUIRED_OPENING_BALANCE = "minRequiredOpeningBalance"; - public static final String LOCKIN_PERIOD_FREQUENCY = "lockinPeriodFrequency"; - public static final String LOCKIN_PERIOD_FREQUENCY_TYPE = "lockinPeriodFrequencyType"; - public static final String WITHDRAWAL_FEE_FOR_TRANSFERS = "withdrawalFeeForTransfers"; - public static final String MIN_BALANCE_FOR_INTEREST_CALCULATION = "minBalanceForInterestCalculation"; - public static final String TOTAL_DEPOSITS = "totalDeposits"; - public static final String TOTAL_WITHDRAWALS = "totalWithdrawals"; - public static final String TOTAL_WITHDRAWAL_FEES = "totalWithdrawalFees"; - public static final String TOTAL_ANNUAL_FEES = "totalAnnualFees"; - public static final String TOTAL_INTEREST_EARNED = "totalInterestEarned"; - public static final String TOTAL_INTEREST_POSTED = "totalInterestPosted"; - public static final String ACCOUNT_BALANCE = "accountBalance"; - public static final String TOTAL_FEE_CHARGE = "totalFeeCharge"; - public static final String TOTAL_PENALTY_CHARGE = "totalPenaltyCharge"; - public static final String TOTAL_WITHHOLD_TAX = "totalWithholdTax"; - public static final String INTEREST_POSTED_TILL_DATE = "interestPostedTillDate"; - public static final String WITH_HOLD_TAX = "withHoldTax"; - public static final String TAX_GROUP_ID = "taxGroupId"; - public static final String TAX_GROUP_NAME = "taxGroupName"; - private final String selectFieldsSql; - private final String selectTablesSql; - - protected DepositAccountMapper() { - final StringBuilder selectFieldsSqlBuilder = new StringBuilder(400); - selectFieldsSqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, "); - selectFieldsSqlBuilder.append("c.id as clientId, c.display_name as clientName, "); - selectFieldsSqlBuilder.append("g.id as groupId, g.display_name as groupName, "); - selectFieldsSqlBuilder.append("sp.id as productId, sp.name as productName, "); - selectFieldsSqlBuilder.append("s.id fieldOfficerId, s.display_name as fieldOfficerName, "); - selectFieldsSqlBuilder.append("sa.status_enum as statusEnum, "); - selectFieldsSqlBuilder.append("sa.submittedon_date as submittedOnDate,"); - selectFieldsSqlBuilder.append("sbu.username as submittedByUsername,"); - selectFieldsSqlBuilder.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,"); - selectFieldsSqlBuilder.append("sa.rejectedon_date as rejectedOnDate,"); - selectFieldsSqlBuilder.append("rbu.username as rejectedByUsername,"); - selectFieldsSqlBuilder.append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,"); - selectFieldsSqlBuilder.append("sa.withdrawnon_date as withdrawnOnDate,"); - selectFieldsSqlBuilder.append("wbu.username as withdrawnByUsername,"); - selectFieldsSqlBuilder.append("wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,"); - selectFieldsSqlBuilder.append("sa.approvedon_date as approvedOnDate,"); - selectFieldsSqlBuilder.append("abu.username as approvedByUsername,"); - selectFieldsSqlBuilder.append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,"); - selectFieldsSqlBuilder.append("sa.activatedon_date as activatedOnDate,"); - selectFieldsSqlBuilder.append("avbu.username as activatedByUsername,"); - selectFieldsSqlBuilder.append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname,"); - selectFieldsSqlBuilder.append("sa.closedon_date as closedOnDate,"); - selectFieldsSqlBuilder.append("cbu.username as closedByUsername,"); - selectFieldsSqlBuilder.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,"); - selectFieldsSqlBuilder.append( - "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); - selectFieldsSqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); - selectFieldsSqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); - selectFieldsSqlBuilder.append("sa.nominal_annual_interest_rate as nominalAnnualInterestRate, "); - selectFieldsSqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, "); - selectFieldsSqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, "); - selectFieldsSqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, "); - selectFieldsSqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, "); - selectFieldsSqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, "); - selectFieldsSqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,"); - selectFieldsSqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); - selectFieldsSqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); - selectFieldsSqlBuilder.append("sa.total_deposits_derived as totalDeposits, "); - selectFieldsSqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, "); - selectFieldsSqlBuilder.append("sa.interest_posted_till_date as interestPostedTillDate, "); - selectFieldsSqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, "); - selectFieldsSqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, "); - selectFieldsSqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, "); - selectFieldsSqlBuilder.append("sa.total_interest_posted_derived as totalInterestPosted, "); - selectFieldsSqlBuilder.append("sa.account_balance_derived as accountBalance, "); - selectFieldsSqlBuilder.append("sa.total_fees_charge_derived as totalFeeCharge, "); - selectFieldsSqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, "); - selectFieldsSqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax,"); - selectFieldsSqlBuilder.append("sa.deposit_type_enum as depositTypeId, "); - selectFieldsSqlBuilder.append("sa.cbs_account_type as accountType, sa.customer_segment as customerSegment, "); - selectFieldsSqlBuilder.append("sa.account_status as accountStatus, sa.mode_of_operation as modeOfOperation, "); - selectFieldsSqlBuilder.append("sa.special_category as specialCategory, sa.weaker_section as weakerSection, "); - selectFieldsSqlBuilder.append("sa.guardian_name as guardianName, sa.guardian_cif_number as guardianCifNumber, "); - selectFieldsSqlBuilder.append("sa.guardian_relationship as guardianRelationship, sa.guardian_client_id as guardianClientId, "); - selectFieldsSqlBuilder.append("sa.minor_date_of_birth as minorDateOfBirth, "); - selectFieldsSqlBuilder.append("sa.introducer_client_id as introducerClientId, sa.introducer_name as introducerName, "); - selectFieldsSqlBuilder.append("sa.introducer_cif_number as introducerCifNumber, "); - selectFieldsSqlBuilder.append("sa.linked_account_id as linkedAccountId, sa.linked_account_type as linkedAccountType, "); - selectFieldsSqlBuilder.append("sa.linked_account_no as linkedAccountNo, "); - selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation, "); - selectFieldsSqlBuilder.append("sa.withhold_tax as withHoldTax,"); - selectFieldsSqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName "); - - this.selectFieldsSql = selectFieldsSqlBuilder.toString(); - - final StringBuilder selectTablesSqlBuilder = new StringBuilder(400); - selectTablesSqlBuilder.append("from m_savings_account sa "); - selectTablesSqlBuilder.append("left join m_deposit_account_term_and_preclosure datp on sa.id = datp.savings_account_id "); - selectTablesSqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id "); - selectTablesSqlBuilder.append("left join m_code_value icv on icv.id = sp.interest_calculate_selector "); - selectTablesSqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); - selectTablesSqlBuilder.append("left join m_client c ON c.id = sa.client_id "); - selectTablesSqlBuilder.append("left join m_group g ON g.id = sa.group_id "); - selectTablesSqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id "); - selectTablesSqlBuilder.append("left join m_appuser sbu on sbu.id = sa.submittedon_userid "); - selectTablesSqlBuilder.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid "); - selectTablesSqlBuilder.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid "); - selectTablesSqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid "); - selectTablesSqlBuilder.append("left join m_appuser avbu on avbu.id = sa.activatedon_userid "); - selectTablesSqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid "); - selectTablesSqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id "); - - this.selectTablesSql = selectTablesSqlBuilder.toString(); - } - - public String selectFieldsSql() { - return this.selectFieldsSql; - } - - public String selectTablesSql() { - return this.selectTablesSql; - } - - public abstract String schema(); - - public DepositAccountData mapRow(final ResultSet rs) throws SQLException { - - final Long id = rs.getLong(ID); - final String accountNo = rs.getString(ACCOUNT_NO); - final String externalId = rs.getString(EXTERNAL_ID); - - final Long groupId = JdbcSupport.getLong(rs, GROUP_ID); - final String groupName = rs.getString(GROUP_NAME); - final Long clientId = JdbcSupport.getLong(rs, CLIENT_ID); - final String clientName = rs.getString(CLIENT_NAME); - - final Long productId = rs.getLong(PRODUCT_ID); - final String productName = rs.getString(PRODUCT_NAME); - - final Long fieldOfficerId = rs.getLong(FIELD_OFFICER_ID); - final String fieldOfficerName = rs.getString(FIELD_OFFICER_NAME); - - final Integer statusEnum = JdbcSupport.getInteger(rs, STATUS_ENUM); - final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum); - - final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, SUBMITTED_ON_DATE); - final String submittedByUsername = rs.getString(SUBMITTED_BY_USERNAME); - final String submittedByFirstname = rs.getString(SUBMITTED_BY_FIRSTNAME); - final String submittedByLastname = rs.getString(SUBMITTED_BY_LASTNAME); - - final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, REJECTED_ON_DATE); - final String rejectedByUsername = rs.getString(REJECTED_BY_USERNAME); - final String rejectedByFirstname = rs.getString(REJECTED_BY_FIRSTNAME); - final String rejectedByLastname = rs.getString(REJECTED_BY_LASTNAME); - - final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, WITHDRAWN_ON_DATE); - final String withdrawnByUsername = rs.getString(WITHDRAWN_BY_USERNAME); - final String withdrawnByFirstname = rs.getString(WITHDRAWN_BY_FIRSTNAME); - final String withdrawnByLastname = rs.getString(WITHDRAWN_BY_LASTNAME); - - final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, APPROVED_ON_DATE); - final String approvedByUsername = rs.getString(APPROVED_BY_USERNAME); - final String approvedByFirstname = rs.getString(APPROVED_BY_FIRSTNAME); - final String approvedByLastname = rs.getString(APPROVED_BY_LASTNAME); - - final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, ACTIVATED_ON_DATE); - final String activatedByUsername = rs.getString(ACTIVATED_BY_USERNAME); - final String activatedByFirstname = rs.getString(ACTIVATED_BY_FIRSTNAME); - final String activatedByLastname = rs.getString(ACTIVATED_BY_LASTNAME); - - final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, CLOSED_ON_DATE); - final String closedByUsername = rs.getString(CLOSED_BY_USERNAME); - final String closedByFirstname = rs.getString(CLOSED_BY_FIRSTNAME); - final String closedByLastname = rs.getString(CLOSED_BY_LASTNAME); - - final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, - submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, - rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, - approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, activatedByUsername, activatedByFirstname, - activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname); - - final Integer depositTypeId = JdbcSupport.getInteger(rs, DEPOSIT_TYPE_ID); - final EnumOptionData depositType = (depositTypeId == null) ? null : SavingsEnumerations.depositType(depositTypeId); - - final String currencyCode = rs.getString(CURRENCY_CODE); - final String currencyName = rs.getString(CURRENCY_NAME); - final String currencyNameCode = rs.getString(CURRENCY_NAME_CODE); - final String currencyDisplaySymbol = rs.getString(CURRENCY_DISPLAY_SYMBOL); - final Integer currencyDigits = JdbcSupport.getInteger(rs, CURRENCY_DIGITS); - final Integer inMultiplesOf = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, - currencyNameCode); - - final BigDecimal nominalAnnualInterestRate = rs.getBigDecimal(NOMINAL_ANNUAL_INTEREST_RATE); - - final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations.compoundingInterestPeriodType( - SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, INTEREST_COMPOUNDING_PERIOD_TYPE))); - - final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType( - SavingsPostingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, INTEREST_POSTING_PERIOD_TYPE))); - - final EnumOptionData interestCalculationType = SavingsEnumerations - .interestCalculationType(SavingsInterestCalculationType.fromInt(JdbcSupport.getInteger(rs, INTEREST_CALCULATION_TYPE))); - - final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations.interestCalculationDaysInYearType( - SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs, INTEREST_CALCULATION_DAYS_IN_YEAR_TYPE))); - - final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, MIN_REQUIRED_OPENING_BALANCE); - - final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, LOCKIN_PERIOD_FREQUENCY); - EnumOptionData lockinPeriodFrequencyType = null; - final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, LOCKIN_PERIOD_FREQUENCY_TYPE); - if (lockinPeriodFrequencyTypeValue != null) { - final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); - lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType); - } - - final boolean withdrawalFeeForTransfers = rs.getBoolean(WITHDRAWAL_FEE_FOR_TRANSFERS); - final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, - MIN_BALANCE_FOR_INTEREST_CALCULATION); - - final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_DEPOSITS); - final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_WITHDRAWALS); - final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_WITHDRAWAL_FEES); - final BigDecimal totalAnnualFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_ANNUAL_FEES); - - final BigDecimal totalInterestEarned = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_INTEREST_EARNED); - final BigDecimal totalInterestPosted = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_INTEREST_POSTED); - final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, ACCOUNT_BALANCE); - final BigDecimal totalFeeCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_FEE_CHARGE); - final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_PENALTY_CHARGE); - final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_WITHHOLD_TAX); - final BigDecimal totalOverdraftInterestDerived = null; - final LocalDate interestPostedTillDate = JdbcSupport.getLocalDate(rs, INTEREST_POSTED_TILL_DATE); - - final boolean withHoldTax = rs.getBoolean(WITH_HOLD_TAX); - final Long taxGroupId = JdbcSupport.getLong(rs, TAX_GROUP_ID); - final String taxGroupName = rs.getString(TAX_GROUP_NAME); - TaxGroupData taxGroupData = null; - if (taxGroupId != null) { - taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); - } - - final BigDecimal availableBalance = null; - final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, - totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge, - totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax, null, null, availableBalance, - interestPostedTillDate); - - return DepositAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, - fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, - interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, depositType, - minBalanceForInterestCalculation, withHoldTax, taxGroupData); - } - } - - private static class FixedDepositAccountMapper extends DepositAccountMapper { - - public static final String PRE_CLOSURE_PENAL_APPLICABLE = "preClosurePenalApplicable"; - public static final String PRE_CLOSURE_PENAL_INTEREST = "preClosurePenalInterest"; - public static final String PRE_CLOSURE_PENAL_INTEREST_ON_ID = "preClosurePenalInterestOnId"; - public static final String MIN_DEPOSIT_TERM = "minDepositTerm"; - public static final String MAX_DEPOSIT_TERM = "maxDepositTerm"; - public static final String MIN_DEPOSIT_TERM_TYPE_ID = "minDepositTermTypeId"; - public static final String MAX_DEPOSIT_TERM_TYPE_ID = "maxDepositTermTypeId"; - public static final String IN_MULTIPLES_OF_DEPOSIT_TERM = "inMultiplesOfDepositTerm"; - public static final String IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID = "inMultiplesOfDepositTermTypeId"; - public static final String DEPOSIT_AMOUNT = "depositAmount"; - public static final String MATURITY_AMOUNT = "maturityAmount"; - public static final String MATURITY_DATE = "maturityDate"; - public static final String DEPOSIT_PERIOD = "depositPeriod"; - public static final String DEPOSIT_PERIOD_FREQUENCY_TYPE_ID = "depositPeriodFrequencyTypeId"; - public static final String ON_ACCOUNT_CLOSURE_ID = "onAccountClosureId"; - public static final String TRANSFER_INTEREST_TO_SAVINGS = "transferInterestToSavings"; - public static final String TRANSFER_TO_SAVINGS_ID = "transferToSavingsId"; - private final String schemaSql; - - FixedDepositAccountMapper() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append(super.selectFieldsSql()); - - sqlBuilder.append(", datp.pre_closure_penal_applicable as preClosurePenalApplicable, "); - sqlBuilder.append("datp.pre_closure_penal_interest as preClosurePenalInterest, "); - sqlBuilder.append("datp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, "); - sqlBuilder.append("datp.min_deposit_term as minDepositTerm, "); - sqlBuilder.append("datp.max_deposit_term as maxDepositTerm, "); - sqlBuilder.append("datp.min_deposit_term_type_enum as minDepositTermTypeId, "); - sqlBuilder.append("datp.max_deposit_term_type_enum as maxDepositTermTypeId, "); - sqlBuilder.append("datp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, "); - sqlBuilder.append("datp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, "); - sqlBuilder.append("datp.deposit_amount as depositAmount, "); - sqlBuilder.append("datp.maturity_amount as maturityAmount, "); - sqlBuilder.append("datp.maturity_date as maturityDate, "); - sqlBuilder.append("datp.deposit_period as depositPeriod, "); - sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, "); - sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId, "); - sqlBuilder.append("datp.transfer_interest_to_linked_account as transferInterestToSavings, "); - sqlBuilder.append("datp.transfer_to_savings_account_id as transferToSavingsId, "); - sqlBuilder.append("sp.fd_type as fdType, sp.product_code as productCode, sp.effective_date as effectiveDate, "); - sqlBuilder.append("sp.effective_to as effectiveTo, sp.end_date as endDate, sp.introducer_required as introducerRequired, "); - sqlBuilder.append("sp.share_linking_enabled as shareLinkingEnabled, "); - sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, "); - sqlBuilder.append("sp.dormancy_alert as dormancyAlert, sp.is_dormancy_tracking_active as isDormancyTrackingActive, "); - sqlBuilder.append("sp.days_to_inactive as daysToInactive, sp.days_to_dormancy as daysToDormancy, "); - sqlBuilder.append("sp.days_to_escheat as daysToEscheat, sp.interest_calculate_selector as interestCalculateSelector, "); - sqlBuilder.append("icv.code_value as interestCalculationTypeName, sp.average_period as averagePeriod, "); - sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay, "); - sqlBuilder.append("sp.last_interest_date as lastInterestDate, sp.enable_lockin_period as enableLockinPeriod, "); - sqlBuilder.append("sp.balance_frequency as balanceFrequency, "); - sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, "); - sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, sp.msme as msme, "); - sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, "); - sqlBuilder.append("sp.loan_facility as loanFacility, sp.premature_withdrawal_option as prematureWithdrawalOption, "); - sqlBuilder.append("sp.tenure_flexibility as tenureFlexibility, sp.lien_marked as lienMarked, "); - sqlBuilder.append("sp.maturity_instructions as maturityInstructions, sp.credit_maturity_proceeds as creditMaturityProceeds, "); - sqlBuilder.append("sp.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, "); - sqlBuilder.append("sp.panel_request_applicable as panelRequestApplicable, "); - sqlBuilder.append("sp.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, "); - sqlBuilder.append("sp.deceased_settlement as deceasedSettlement, sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate "); - - sqlBuilder.append(super.selectTablesSql()); - - this.schemaSql = sqlBuilder.toString(); - } - - @Override - public String schema() { - return this.schemaSql; - } - - @Override - public FixedDepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final DepositAccountData depositAccountData = super.mapRow(rs); - final boolean preClosurePenalApplicable = rs.getBoolean(PRE_CLOSURE_PENAL_APPLICABLE); - final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, PRE_CLOSURE_PENAL_INTEREST); - final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, PRE_CLOSURE_PENAL_INTEREST_ON_ID); - final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null - : SavingsEnumerations.preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId); - - final Integer minDepositTerm = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM); - final Integer maxDepositTerm = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM); - final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM_TYPE_ID); - final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null - : SavingsEnumerations.depositTermFrequencyType(minDepositTermTypeId); - final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM_TYPE_ID); - final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null - : SavingsEnumerations.depositTermFrequencyType(maxDepositTermTypeId); - final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM); - final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID); - final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null - : SavingsEnumerations.depositTermFrequencyType(inMultiplesOfDepositTermTypeId); - - final BigDecimal depositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, DEPOSIT_AMOUNT); - final BigDecimal maturityAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, MATURITY_AMOUNT); - final LocalDate maturityDate = JdbcSupport.getLocalDate(rs, MATURITY_DATE); - final Integer depositPeriod = JdbcSupport.getInteger(rs, DEPOSIT_PERIOD); - final Integer depositPeriodFrequencyTypeId = JdbcSupport.getInteger(rs, DEPOSIT_PERIOD_FREQUENCY_TYPE_ID); - final EnumOptionData depositPeriodFrequencyType = (depositPeriodFrequencyTypeId == null) ? null - : SavingsEnumerations.depositPeriodFrequency(depositPeriodFrequencyTypeId); - - final Integer onAccountClosureId = JdbcSupport.getInteger(rs, ON_ACCOUNT_CLOSURE_ID); - final EnumOptionData onAccountClosureType = (onAccountClosureId == null) ? null - : SavingsEnumerations.depositAccountOnClosureType(onAccountClosureId); - final Boolean transferInterestToSavings = rs.getBoolean(TRANSFER_INTEREST_TO_SAVINGS); - - final Long transferToSavingsId = JdbcSupport.getLong(rs, TRANSFER_TO_SAVINGS_ID); - - final FixedDepositAccountData result = FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest, - preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, - inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod, - depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, transferToSavingsId); - result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("fdType"), rs.getString("productCode"), - JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"), - JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"), - rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"), - JdbcSupport.getLong(rs, "daysToInactive"), JdbcSupport.getLong(rs, "daysToDormancy"), - JdbcSupport.getLong(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"), - rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"), - JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"), - rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"), - rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"), - rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"), - rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"), - rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"), - rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"), - rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate")); - return result; - } - } - - private static class RecurringDepositAccountMapper extends DepositAccountMapper { - - public static final String PRE_CLOSURE_PENAL_APPLICABLE = "preClosurePenalApplicable"; - public static final String PRE_CLOSURE_PENAL_INTEREST = "preClosurePenalInterest"; - public static final String PRE_CLOSURE_PENAL_INTEREST_ON_ID = "preClosurePenalInterestOnId"; - public static final String MIN_DEPOSIT_TERM = "minDepositTerm"; - public static final String MAX_DEPOSIT_TERM = "maxDepositTerm"; - public static final String MIN_DEPOSIT_TERM_TYPE_ID = "minDepositTermTypeId"; - public static final String MAX_DEPOSIT_TERM_TYPE_ID = "maxDepositTermTypeId"; - public static final String IN_MULTIPLES_OF_DEPOSIT_TERM = "inMultiplesOfDepositTerm"; - public static final String IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID = "inMultiplesOfDepositTermTypeId"; - public static final String DEPOSIT_AMOUNT = "depositAmount"; - public static final String MATURITY_AMOUNT = "maturityAmount"; - public static final String MATURITY_DATE = "maturityDate"; - public static final String DEPOSIT_PERIOD = "depositPeriod"; - public static final String DEPOSIT_PERIOD_FREQUENCY_TYPE_ID = "depositPeriodFrequencyTypeId"; - public static final String MANDATORY_RECOMMENDED_DEPOSIT_AMOUNT = "mandatoryRecommendedDepositAmount"; - public static final String TOTAL_OVERDUE_AMOUNT = "totalOverdueAmount"; - public static final String NO_OF_OVERDUE_INSTALLMENTS = "noOfOverdueInstallments"; - public static final String IS_MANDATORY_DEPOSIT = "isMandatoryDeposit"; - public static final String ALLOW_WITHDRAWAL = "allowWithdrawal"; - public static final String ADJUST_ADVANCE_TOWARDS_FUTURE_PAYMENTS = "adjustAdvanceTowardsFuturePayments"; - public static final String IS_CALENDAR_INHERITED = "isCalendarInherited"; - public static final String ON_ACCOUNT_CLOSURE_ID = "onAccountClosureId"; - public static final String EXPECTED_FIRST_DEPOSIT_ON_DATE = "expectedFirstDepositOnDate"; - private final String schemaSql; - - RecurringDepositAccountMapper() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append(this.selectFieldsSql()); - - sqlBuilder.append(", datp.pre_closure_penal_applicable as preClosurePenalApplicable, "); - sqlBuilder.append("datp.pre_closure_penal_interest as preClosurePenalInterest, "); - sqlBuilder.append("datp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, "); - sqlBuilder.append("dard.mandatory_recommended_deposit_amount as mandatoryRecommendedDepositAmount, "); - sqlBuilder.append("dard.total_overdue_amount as totalOverdueAmount, "); - sqlBuilder.append("dard.no_of_overdue_installments as noOfOverdueInstallments, "); - sqlBuilder.append("dard.is_mandatory as isMandatoryDeposit, "); - sqlBuilder.append("dard.allow_withdrawal as allowWithdrawal, "); - sqlBuilder.append("dard.adjust_advance_towards_future_payments as adjustAdvanceTowardsFuturePayments, "); - sqlBuilder.append("dard.is_calendar_inherited as isCalendarInherited, "); - sqlBuilder.append("datp.min_deposit_term as minDepositTerm, "); - sqlBuilder.append("datp.max_deposit_term as maxDepositTerm, "); - sqlBuilder.append("datp.min_deposit_term_type_enum as minDepositTermTypeId, "); - sqlBuilder.append("datp.max_deposit_term_type_enum as maxDepositTermTypeId, "); - sqlBuilder.append("datp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, "); - sqlBuilder.append("datp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, "); - sqlBuilder.append("datp.deposit_amount as depositAmount, "); - sqlBuilder.append("datp.maturity_amount as maturityAmount, "); - sqlBuilder.append("datp.expected_firstdepositon_date as expectedFirstDepositOnDate, "); - sqlBuilder.append("datp.maturity_date as maturityDate, "); - sqlBuilder.append("datp.deposit_period as depositPeriod, "); - sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, "); - sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId, "); - sqlBuilder.append("sp.product_code as productCode, sp.effective_date as effectiveDate, "); - sqlBuilder.append("sp.effective_to as effectiveTo, sp.end_date as endDate, sp.introducer_required as introducerRequired, "); - sqlBuilder.append("sp.share_linking_enabled as shareLinkingEnabled, "); - sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, "); - sqlBuilder.append("sp.dormancy_alert as dormancyAlert, sp.is_dormancy_tracking_active as isDormancyTrackingActive, "); - sqlBuilder.append("sp.days_to_inactive as daysToInactive, sp.days_to_dormancy as daysToDormancy, "); - sqlBuilder.append("sp.days_to_escheat as daysToEscheat, sp.interest_calculate_selector as interestCalculateSelector, "); - sqlBuilder.append("icv.code_value as interestCalculationTypeName, sp.average_period as averagePeriod, "); - sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay, "); - sqlBuilder.append("sp.last_interest_date as lastInterestDate, sp.enable_lockin_period as enableLockinPeriod, "); - sqlBuilder.append("sp.balance_frequency as balanceFrequency, "); - sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, "); - sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, sp.msme as msme, "); - sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, "); - sqlBuilder.append("sp.loan_facility as loanFacility, sp.premature_withdrawal_option as prematureWithdrawalOption, "); - sqlBuilder.append("sp.tenure_flexibility as tenureFlexibility, sp.lien_marked as lienMarked, "); - sqlBuilder.append("sp.maturity_instructions as maturityInstructions, sp.credit_maturity_proceeds as creditMaturityProceeds, "); - sqlBuilder.append("sp.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, "); - sqlBuilder.append("sp.panel_request_applicable as panelRequestApplicable, "); - sqlBuilder.append("sp.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, "); - sqlBuilder.append("sp.deceased_settlement as deceasedSettlement, sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate "); - - sqlBuilder.append(this.selectTablesSql()); - sqlBuilder.append("left join m_deposit_account_recurring_detail dard on sa.id = dard.savings_account_id "); - - this.schemaSql = sqlBuilder.toString(); - } - - @Override - public String schema() { - return this.schemaSql; - } - - @Override - public RecurringDepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final DepositAccountData depositAccountData = super.mapRow(rs); - - final boolean preClosurePenalApplicable = rs.getBoolean(PRE_CLOSURE_PENAL_APPLICABLE); - final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, PRE_CLOSURE_PENAL_INTEREST); - final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, PRE_CLOSURE_PENAL_INTEREST_ON_ID); - final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null - : SavingsEnumerations.preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId); - final Integer minDepositTerm = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM); - final Integer maxDepositTerm = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM); - final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM_TYPE_ID); - final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null - : SavingsEnumerations.depositTermFrequencyType(minDepositTermTypeId); - final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM_TYPE_ID); - final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null - : SavingsEnumerations.depositTermFrequencyType(maxDepositTermTypeId); - final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM); - final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID); - final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null - : SavingsEnumerations.depositTermFrequencyType(inMultiplesOfDepositTermTypeId); - - final BigDecimal depositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, DEPOSIT_AMOUNT); - final BigDecimal maturityAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, MATURITY_AMOUNT); - final LocalDate maturityDate = JdbcSupport.getLocalDate(rs, MATURITY_DATE); - final Integer depositPeriod = JdbcSupport.getInteger(rs, DEPOSIT_PERIOD); - final Integer depositPeriodFrequencyTypeId = JdbcSupport.getInteger(rs, DEPOSIT_PERIOD_FREQUENCY_TYPE_ID); - final EnumOptionData depositPeriodFrequencyType = (depositPeriodFrequencyTypeId == null) ? null - : SavingsEnumerations.depositPeriodFrequency(depositPeriodFrequencyTypeId); - final BigDecimal mandatoryRecommendedDepositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, - MANDATORY_RECOMMENDED_DEPOSIT_AMOUNT); - final BigDecimal totalOverdueAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_OVERDUE_AMOUNT); - final Integer noOfOverdueInstallments = JdbcSupport.getInteger(rs, NO_OF_OVERDUE_INSTALLMENTS); - final boolean isMandatoryDeposit = rs.getBoolean(IS_MANDATORY_DEPOSIT); - final boolean allowWithdrawal = rs.getBoolean(ALLOW_WITHDRAWAL); - final boolean adjustAdvanceTowardsFuturePayments = rs.getBoolean(ADJUST_ADVANCE_TOWARDS_FUTURE_PAYMENTS); - final boolean isCalendarInherited = rs.getBoolean(IS_CALENDAR_INHERITED); - - final Integer onAccountClosureId = JdbcSupport.getInteger(rs, ON_ACCOUNT_CLOSURE_ID); - final EnumOptionData onAccountClosureType = (onAccountClosureId == null) ? null - : SavingsEnumerations.depositAccountOnClosureType(onAccountClosureId); - final LocalDate expectedFirstDepositOnDate = JdbcSupport.getLocalDate(rs, EXPECTED_FIRST_DEPOSIT_ON_DATE); - - final RecurringDepositAccountData result = RecurringDepositAccountData.instance(depositAccountData, - preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, - minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, - maturityAmount, maturityDate, depositPeriod, depositPeriodFrequencyType, mandatoryRecommendedDepositAmount, - onAccountClosureType, expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, - allowWithdrawal, adjustAdvanceTowardsFuturePayments, isCalendarInherited); - - result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("productCode"), - JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"), - JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"), - rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"), - JdbcSupport.getInteger(rs, "daysToInactive"), JdbcSupport.getInteger(rs, "daysToDormancy"), - JdbcSupport.getInteger(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"), - rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"), - JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"), - rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"), - rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"), - rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"), - rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"), - rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"), - rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"), - rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate")); - - return result; - - } - } - - private static final class DepositAccountLookupMapper implements RowMapper { - - public static final String ID = "id"; - public static final String ACCOUNT_NUMBER = "accountNumber"; - public static final String DEPOSIT_TYPE_ID = "depositTypeId"; - - public String schema() { - return " sa.id as id, sa.account_no as accountNumber, sa.deposit_type_enum as depositTypeId from m_savings_account sa "; - } - - @Override - public DepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong(ID); - final String name = rs.getString(ACCOUNT_NUMBER); - final Integer depositTypeId = JdbcSupport.getInteger(rs, DEPOSIT_TYPE_ID); - final EnumOptionData depositType = (depositTypeId == null) ? null : SavingsEnumerations.depositType(depositTypeId); - - return DepositAccountData.lookup(id, name, depositType); - } - } - - private static final class SavingsAccountTransactionsMapper implements RowMapper { - - public static final String TRANSACTION_ID = "transactionId"; - public static final String TRANSACTION_TYPE = "transactionType"; - public static final String TRANSACTION_DATE = "transactionDate"; - public static final String TRANSACTION_AMOUNT = "transactionAmount"; - public static final String RUNNING_BALANCE = "runningBalance"; - public static final String REVERSED = "reversed"; - public static final String SAVINGS_ID = "savingsId"; - public static final String ACCOUNT_NO = "accountNo"; - public static final String PAYMENT_TYPE = "paymentType"; - public static final String PAYMENT_TYPE_NAME = "paymentTypeName"; - public static final String ACCOUNT_NUMBER = "accountNumber"; - public static final String CHECK_NUMBER = "checkNumber"; - public static final String ROUTING_CODE = "routingCode"; - public static final String RECEIPT_NUMBER = "receiptNumber"; - public static final String BANK_NUMBER = "bankNumber"; - public static final String CURRENCY_CODE = "currencyCode"; - public static final String CURRENCY_NAME = "currencyName"; - public static final String CURRENCY_NAME_CODE = "currencyNameCode"; - public static final String CURRENCY_DISPLAY_SYMBOL = "currencyDisplaySymbol"; - public static final String CURRENCY_DIGITS = "currencyDigits"; - public static final String IN_MULTIPLES_OF = "inMultiplesOf"; - public static final String FROM_TRANSFER_ID = "fromTransferId"; - public static final String TO_TRANSFER_ID = "toTransferId"; - public static final String FROM_TRANSFER_DATE = "fromTransferDate"; - public static final String FROM_TRANSFER_AMOUNT = "fromTransferAmount"; - public static final String FROM_TRANSFER_REVERSED = "fromTransferReversed"; - public static final String FROM_TRANSFER_DESCRIPTION = "fromTransferDescription"; - public static final String TO_TRANSFER_DATE = "toTransferDate"; - public static final String TO_TRANSFER_AMOUNT = "toTransferAmount"; - public static final String TO_TRANSFER_REVERSED = "toTransferReversed"; - public static final String TO_TRANSFER_DESCRIPTION = "toTransferDescription"; - public static final String SUBMITTED_BY_USERNAME = "submittedByUsername"; - public static final String SUBMITTED_ON_DATE = "submittedOnDate"; - private final String schemaSql; - - SavingsAccountTransactionsMapper() { - - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, "); - sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,"); - sqlBuilder.append("tr.running_balance_derived as runningBalance, tr.is_reversed as reversed,"); - sqlBuilder.append("tr.submitted_on_date as submittedOnDate,"); - sqlBuilder.append("fromtran.id as fromTransferId, fromtran.is_reversed as fromTransferReversed,"); - sqlBuilder.append("fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount,"); - sqlBuilder.append("fromtran.description as fromTransferDescription,"); - sqlBuilder.append("totran.id as toTransferId, totran.is_reversed as toTransferReversed,"); - sqlBuilder.append("totran.transaction_date as toTransferDate, totran.amount as toTransferAmount,"); - sqlBuilder.append("totran.description as toTransferDescription,"); - sqlBuilder.append("sa.id as savingsId, sa.account_no as accountNo,"); - sqlBuilder.append(" au.username as submittedByUsername, "); - sqlBuilder.append("pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, "); - sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, "); - sqlBuilder.append( - "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, 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("pt.value as paymentTypeName "); - sqlBuilder.append("from m_savings_account sa "); - sqlBuilder.append("join m_savings_account_transaction tr on tr.savings_account_id = sa.id "); - sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); - sqlBuilder.append("left join m_account_transfer_transaction fromtran on fromtran.from_savings_transaction_id = tr.id "); - sqlBuilder.append("left join m_account_transfer_transaction totran on totran.to_savings_transaction_id = tr.id "); - sqlBuilder.append("left join m_payment_detail pd on tr.payment_detail_id = pd.id "); - sqlBuilder.append("left join m_payment_type pt on pd.payment_type_id = pt.id "); - sqlBuilder.append("left join m_appuser au on au.id = tr." + CREATED_BY_DB_FIELD); - this.schemaSql = sqlBuilder.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - final Long id = rs.getLong(TRANSACTION_ID); - final int transactionTypeInt = JdbcSupport.getInteger(rs, TRANSACTION_TYPE); - final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(transactionTypeInt); - - final LocalDate date = JdbcSupport.getLocalDate(rs, TRANSACTION_DATE); - final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, SUBMITTED_ON_DATE); - final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, TRANSACTION_AMOUNT); - final BigDecimal outstandingChargeAmount = null; - final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, RUNNING_BALANCE); - final boolean reversed = rs.getBoolean(REVERSED); - - final Long savingsId = rs.getLong(SAVINGS_ID); - final String accountNo = rs.getString(ACCOUNT_NO); - - PaymentDetailData paymentDetailData = null; - if (transactionType.isDepositOrWithdrawal()) { - final Long paymentTypeId = JdbcSupport.getLong(rs, PAYMENT_TYPE); - if (paymentTypeId != null) { - final String typeName = rs.getString(PAYMENT_TYPE_NAME); - final PaymentTypeData paymentType = PaymentTypeData.builder().id(paymentTypeId).name(typeName).build(); - final String accountNumber = rs.getString(ACCOUNT_NUMBER); - final String checkNumber = rs.getString(CHECK_NUMBER); - final String routingCode = rs.getString(ROUTING_CODE); - final String receiptNumber = rs.getString(RECEIPT_NUMBER); - final String bankNumber = rs.getString(BANK_NUMBER); - paymentDetailData = new PaymentDetailData(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber, - bankNumber); - } - } - - final String currencyCode = rs.getString(CURRENCY_CODE); - final String currencyName = rs.getString(CURRENCY_NAME); - final String currencyNameCode = rs.getString(CURRENCY_NAME_CODE); - final String currencyDisplaySymbol = rs.getString(CURRENCY_DISPLAY_SYMBOL); - final Integer currencyDigits = JdbcSupport.getInteger(rs, CURRENCY_DIGITS); - final Integer inMultiplesOf = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, - currencyNameCode); - - AccountTransferData transfer = null; - final Long fromTransferId = JdbcSupport.getLong(rs, FROM_TRANSFER_ID); - final Long toTransferId = JdbcSupport.getLong(rs, TO_TRANSFER_ID); - if (fromTransferId != null) { - final LocalDate fromTransferDate = JdbcSupport.getLocalDate(rs, FROM_TRANSFER_DATE); - final BigDecimal fromTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, FROM_TRANSFER_AMOUNT); - final boolean fromTransferReversed = rs.getBoolean(FROM_TRANSFER_REVERSED); - final String fromTransferDescription = rs.getString(FROM_TRANSFER_DESCRIPTION); - - transfer = AccountTransferData.transferBasicDetails(fromTransferId, currency, fromTransferAmount, fromTransferDate, - fromTransferDescription, fromTransferReversed); - } else if (toTransferId != null) { - final LocalDate toTransferDate = JdbcSupport.getLocalDate(rs, TO_TRANSFER_DATE); - final BigDecimal toTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, TO_TRANSFER_AMOUNT); - final boolean toTransferReversed = rs.getBoolean(TO_TRANSFER_REVERSED); - final String toTransferDescription = rs.getString(TO_TRANSFER_DESCRIPTION); - - transfer = AccountTransferData.transferBasicDetails(toTransferId, currency, toTransferAmount, toTransferDate, - toTransferDescription, toTransferReversed); - } - final boolean postInterestAsOn = false; - final String submittedByUsername = rs.getString(SUBMITTED_BY_USERNAME); - final String note = null; - return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency, - amount, outstandingChargeAmount, runningBalance, reversed, transfer, postInterestAsOn, submittedByUsername, note, - submittedOnDate); - } - } - - private abstract static class DepositAccountTemplateMapper implements RowMapper { - - public static final String DEPOSIT_TYPE_ID = "depositTypeId"; - public static final String PRODUCT_ID = "productId"; - public static final String PRODUCT_NAME = "productName"; - public static final String CURRENCY_CODE = "currencyCode"; - public static final String CURRENCY_NAME = "currencyName"; - public static final String CURRENCY_NAME_CODE = "currencyNameCode"; - public static final String CURRENCY_DISPLAY_SYMBOL = "currencyDisplaySymbol"; - public static final String CURRENCY_DIGITS = "currencyDigits"; - public static final String IN_MULTIPLES_OF = "inMultiplesOf"; - public static final String NOMINAL_ANNUAL_ITEREST_RATE = "nominalAnnualIterestRate"; - public static final String INTEREST_COMPOUNDING_PERIOD_TYPE = "interestCompoundingPeriodType"; - public static final String INTEREST_POSTING_PERIOD_TYPE = "interestPostingPeriodType"; - public static final String INTEREST_CALCULATION_TYPE = "interestCalculationType"; - public static final String INTEREST_CALCULATION_DAYS_IN_YEAR_TYPE = "interestCalculationDaysInYearType"; - public static final String MIN_REQUIRED_OPENING_BALANCE = "minRequiredOpeningBalance"; - public static final String LOCKIN_PERIOD_FREQUENCY = "lockinPeriodFrequency"; - public static final String LOCKIN_PERIOD_FREQUENCY_TYPE = "lockinPeriodFrequencyType"; - public static final String WITHDRAWAL_FEE_FOR_TRANSFERS = "withdrawalFeeForTransfers"; - public static final String MIN_BALANCE_FOR_INTEREST_CALCULATION = "minBalanceForInterestCalculation"; - public static final String WITH_HOLD_TAX = "withHoldTax"; - public static final String TAX_GROUP_ID = "taxGroupId"; - public static final String TAX_GROUP_NAME = "taxGroupName"; - private final String selectFieldsSql; - private final String selectTablesSql; - - private final ClientData client; - private final GroupGeneralData group; - - protected DepositAccountTemplateMapper(final ClientData client, final GroupGeneralData group) { - this.client = client; - this.group = group; - - final StringBuilder selectFieldsSqlBuilder = new StringBuilder(400); - selectFieldsSqlBuilder.append("sa.id as productId, sa.name as productName, "); - selectFieldsSqlBuilder.append( - "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); - selectFieldsSqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); - selectFieldsSqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); - selectFieldsSqlBuilder.append("sa.nominal_annual_interest_rate as nominalAnnualIterestRate, "); - selectFieldsSqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, "); - selectFieldsSqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, "); - selectFieldsSqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, "); - selectFieldsSqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, "); - selectFieldsSqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, "); - selectFieldsSqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,"); - selectFieldsSqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); - selectFieldsSqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); - selectFieldsSqlBuilder.append("sa.deposit_type_enum as depositTypeId, "); - selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation, "); - selectFieldsSqlBuilder.append("sa.withhold_tax as withHoldTax,"); - selectFieldsSqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName "); - - this.selectFieldsSql = selectFieldsSqlBuilder.toString(); - - final StringBuilder selectTablesSqlBuilder = new StringBuilder(400); - selectTablesSqlBuilder.append("from m_savings_product sa "); - selectTablesSqlBuilder.append("left join m_deposit_product_term_and_preclosure dptp on sa.id = dptp.savings_product_id "); - selectTablesSqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); - selectTablesSqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id "); - - this.selectTablesSql = selectTablesSqlBuilder.toString(); - } - - public String selectFieldsSql() { - return this.selectFieldsSql; - } - - public String selectTablesSql() { - return this.selectTablesSql; - } - - public abstract String schema(); - - public DepositAccountData mapRow(final ResultSet rs) throws SQLException { - - final Integer depositTypeId = JdbcSupport.getInteger(rs, DEPOSIT_TYPE_ID); - final EnumOptionData depositType = (depositTypeId == null) ? null : SavingsEnumerations.depositType(depositTypeId); - final Long productId = rs.getLong(PRODUCT_ID); - final String productName = rs.getString(PRODUCT_NAME); - - final String currencyCode = rs.getString(CURRENCY_CODE); - final String currencyName = rs.getString(CURRENCY_NAME); - final String currencyNameCode = rs.getString(CURRENCY_NAME_CODE); - final String currencyDisplaySymbol = rs.getString(CURRENCY_DISPLAY_SYMBOL); - final Integer currencyDigits = JdbcSupport.getInteger(rs, CURRENCY_DIGITS); - final Integer inMultiplesOf = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, - currencyNameCode); - - final BigDecimal nominalAnnualIterestRate = rs.getBigDecimal(NOMINAL_ANNUAL_ITEREST_RATE); - - final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations.compoundingInterestPeriodType( - SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, INTEREST_COMPOUNDING_PERIOD_TYPE))); - - final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType( - SavingsPostingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, INTEREST_POSTING_PERIOD_TYPE))); - - final EnumOptionData interestCalculationType = SavingsEnumerations - .interestCalculationType(SavingsInterestCalculationType.fromInt(JdbcSupport.getInteger(rs, INTEREST_CALCULATION_TYPE))); - - final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations.interestCalculationDaysInYearType( - SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs, INTEREST_CALCULATION_DAYS_IN_YEAR_TYPE))); - - final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, MIN_REQUIRED_OPENING_BALANCE); - - final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, LOCKIN_PERIOD_FREQUENCY); - EnumOptionData lockinPeriodFrequencyType = null; - final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, LOCKIN_PERIOD_FREQUENCY_TYPE); - if (lockinPeriodFrequencyTypeValue != null) { - final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); - lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType); - } - - final boolean withdrawalFeeForTransfers = rs.getBoolean(WITHDRAWAL_FEE_FOR_TRANSFERS); - final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, - MIN_BALANCE_FOR_INTEREST_CALCULATION); - - Long clientId = null; - String clientName = null; - if (this.client != null) { - clientId = this.client.getId(); - clientName = this.client.getDisplayName(); - } - - Long groupId = null; - String groupName = null; - if (this.group != null) { - groupId = this.group.getId(); - groupName = this.group.getName(); - } - - final Long fieldOfficerId = null; - final String fieldOfficerName = null; - final SavingsAccountStatusEnumData status = null; - final SavingsAccountSummaryData summary = null; - final SavingsAccountApplicationTimelineData timeline = SavingsAccountApplicationTimelineData.templateDefault(); - - final boolean withHoldTax = rs.getBoolean(WITH_HOLD_TAX); - final Long taxGroupId = JdbcSupport.getLong(rs, TAX_GROUP_ID); - final String taxGroupName = rs.getString(TAX_GROUP_NAME); - TaxGroupData taxGroupData = null; - if (taxGroupId != null) { - taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); - } - - return DepositAccountData.instance(null, null, null, groupId, groupName, clientId, clientName, productId, productName, - fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualIterestRate, interestCompoundingPeriodType, - interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, depositType, - minBalanceForInterestCalculation, withHoldTax, taxGroupData); - } - } - - private static class FixedDepositAccountTemplateMapper extends DepositAccountTemplateMapper { - - public static final String PRE_CLOSURE_PENAL_APPLICABLE = "preClosurePenalApplicable"; - public static final String PRE_CLOSURE_PENAL_INTEREST = "preClosurePenalInterest"; - public static final String PRE_CLOSURE_PENAL_INTEREST_ON_ID = "preClosurePenalInterestOnId"; - public static final String MIN_DEPOSIT_TERM = "minDepositTerm"; - public static final String MAX_DEPOSIT_TERM = "maxDepositTerm"; - public static final String MIN_DEPOSIT_TERM_TYPE_ID = "minDepositTermTypeId"; - public static final String MAX_DEPOSIT_TERM_TYPE_ID = "maxDepositTermTypeId"; - public static final String IN_MULTIPLES_OF_DEPOSIT_TERM = "inMultiplesOfDepositTerm"; - public static final String IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID = "inMultiplesOfDepositTermTypeId"; - private final String schemaSql; - - FixedDepositAccountTemplateMapper(final ClientData client, final GroupGeneralData group) { - super(client, group); - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append(super.selectFieldsSql()); - - sqlBuilder.append(", dptp.pre_closure_penal_applicable as preClosurePenalApplicable, "); - sqlBuilder.append("dptp.pre_closure_penal_interest as preClosurePenalInterest, "); - sqlBuilder.append("dptp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, "); - sqlBuilder.append("sa.account_type as accountType, sa.customer_segment as customerSegment, sa.fd_type as fdType, "); - sqlBuilder.append("sa.product_code as productCode, "); - sqlBuilder.append("sa.effective_date as effectiveDate, sa.effective_to as effectiveTo, sa.end_date as endDate, "); - sqlBuilder.append("sa.introducer_required as introducerRequired, sa.share_linking_enabled as shareLinkingEnabled, "); - sqlBuilder.append("sa.nomination_facility as nominationFacility, sa.dormancy_alert as dormancyAlert, "); - sqlBuilder.append("sa.is_dormancy_tracking_active as isDormancyTrackingActive, "); - sqlBuilder.append("sa.days_to_inactive as daysToInactive, sa.days_to_dormancy as daysToDormancy, "); - sqlBuilder.append("sa.days_to_escheat as daysToEscheat, "); - sqlBuilder.append("sa.interest_calculate_selector as interestCalculateSelector, "); - sqlBuilder.append("icv.code_value as interestCalculationTypeName, "); - sqlBuilder.append("sa.average_period as averagePeriod, "); - sqlBuilder.append("sa.auto_post as autoPost, sa.frequency_fix_day as frequencyFixDay, "); - sqlBuilder.append("sa.last_interest_date as lastInterestDate, sa.enable_lockin_period as enableLockinPeriod, "); - sqlBuilder.append("sa.balance_frequency as balanceFrequency, "); - sqlBuilder.append("sa.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, "); - sqlBuilder.append("sa.balance_swap_option as balanceSwapOption, sa.msme as msme, "); - sqlBuilder.append("sa.auto_debit_facility as autoDebitFacility, sa.loan_facility as loanFacility, "); - sqlBuilder.append("sa.premature_withdrawal_option as prematureWithdrawalOption, sa.tenure_flexibility as tenureFlexibility, "); - sqlBuilder.append("sa.lien_marked as lienMarked, sa.maturity_instructions as maturityInstructions, "); - sqlBuilder.append("sa.credit_maturity_proceeds as creditMaturityProceeds, "); - sqlBuilder.append("sa.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, "); - sqlBuilder.append("sa.panel_request_applicable as panelRequestApplicable, "); - sqlBuilder.append("sa.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, "); - sqlBuilder.append("sa.deceased_settlement as deceasedSettlement, "); - sqlBuilder.append("sa.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate, "); - sqlBuilder.append("dptp.min_deposit_term as minDepositTerm, "); - sqlBuilder.append("dptp.max_deposit_term as maxDepositTerm, "); - sqlBuilder.append("dptp.min_deposit_term_type_enum as minDepositTermTypeId, "); - sqlBuilder.append("dptp.max_deposit_term_type_enum as maxDepositTermTypeId, "); - sqlBuilder.append("dptp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, "); - sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId "); - - sqlBuilder.append(super.selectTablesSql()); - sqlBuilder.append("left join m_code_value icv on icv.id = sa.interest_calculate_selector "); - - this.schemaSql = sqlBuilder.toString(); - } - - @Override - public String schema() { - return this.schemaSql; - } - - @Override - public FixedDepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final DepositAccountData depositAccountData = super.mapRow(rs); - - final boolean preClosurePenalApplicable = rs.getBoolean(PRE_CLOSURE_PENAL_APPLICABLE); - final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, PRE_CLOSURE_PENAL_INTEREST); - final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, PRE_CLOSURE_PENAL_INTEREST_ON_ID); - final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null - : SavingsEnumerations.preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId); - - final Integer minDepositTerm = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM); - final Integer maxDepositTerm = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM); - final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM_TYPE_ID); - final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null - : SavingsEnumerations.depositTermFrequencyType(minDepositTermTypeId); - final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM_TYPE_ID); - final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null - : SavingsEnumerations.depositTermFrequencyType(maxDepositTermTypeId); - final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM); - final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID); - final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null - : SavingsEnumerations.depositTermFrequencyType(inMultiplesOfDepositTermTypeId); - - final BigDecimal depositAmount = null; - final BigDecimal maturityAmount = null; - final LocalDate maturityDate = null; - final Integer depositPeriod = null; - final EnumOptionData depositPeriodFrequencyType = null; - final EnumOptionData onAccountClosureType = null; - final Boolean transferInterestToSavings = false; - - final FixedDepositAccountData result = FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, - preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, - maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, - maturityDate, depositPeriod, depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, null); - result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("fdType"), rs.getString("productCode"), - JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"), - JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"), - rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"), - JdbcSupport.getLong(rs, "daysToInactive"), JdbcSupport.getLong(rs, "daysToDormancy"), - JdbcSupport.getLong(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"), - rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"), - JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"), - rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"), - rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"), - rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"), - rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"), - rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"), - rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"), - rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate")); - return result; - } - } - - private static class RecurringDepositAccountTemplateMapper extends DepositAccountTemplateMapper { - - public static final String PRE_CLOSURE_PENAL_APPLICABLE = "preClosurePenalApplicable"; - public static final String PRE_CLOSURE_PENAL_INTEREST = "preClosurePenalInterest"; - public static final String PRE_CLOSURE_PENAL_INTEREST_ON_ID = "preClosurePenalInterestOnId"; - public static final String MIN_DEPOSIT_TERM = "minDepositTerm"; - public static final String MAX_DEPOSIT_TERM = "maxDepositTerm"; - public static final String MIN_DEPOSIT_TERM_TYPE_ID = "minDepositTermTypeId"; - public static final String MAX_DEPOSIT_TERM_TYPE_ID = "maxDepositTermTypeId"; - public static final String IN_MULTIPLES_OF_DEPOSIT_TERM = "inMultiplesOfDepositTerm"; - public static final String IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID = "inMultiplesOfDepositTermTypeId"; - public static final String IS_MANDATORY_DEPOSIT = "isMandatoryDeposit"; - public static final String ALLOW_WITHDRAWAL = "allowWithdrawal"; - public static final String ADJUST_ADVANCE_TOWARDS_FUTURE_PAYMENTS = "adjustAdvanceTowardsFuturePayments"; - private final String schemaSql; - - RecurringDepositAccountTemplateMapper(final ClientData client, final GroupGeneralData group) { - super(client, group); - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append(this.selectFieldsSql()); - - sqlBuilder.append(", dptp.pre_closure_penal_applicable as preClosurePenalApplicable, "); - sqlBuilder.append("dptp.pre_closure_penal_interest as preClosurePenalInterest, "); - sqlBuilder.append("dptp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, "); - sqlBuilder.append("dprd.is_mandatory as isMandatoryDeposit, "); - sqlBuilder.append("dprd.allow_withdrawal as allowWithdrawal, "); - sqlBuilder.append("dprd.adjust_advance_towards_future_payments as adjustAdvanceTowardsFuturePayments, "); - sqlBuilder.append("dptp.min_deposit_term as minDepositTerm, "); - sqlBuilder.append("dptp.max_deposit_term as maxDepositTerm, "); - sqlBuilder.append("dptp.min_deposit_term_type_enum as minDepositTermTypeId, "); - sqlBuilder.append("dptp.max_deposit_term_type_enum as maxDepositTermTypeId, "); - sqlBuilder.append("dptp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, "); - sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, "); - sqlBuilder.append("sa.account_type as accountType, sa.customer_segment as customerSegment, "); - sqlBuilder.append("sa.product_code as productCode, "); - sqlBuilder.append("sa.effective_date as effectiveDate, sa.effective_to as effectiveTo, sa.end_date as endDate, "); - sqlBuilder.append("sa.introducer_required as introducerRequired, sa.share_linking_enabled as shareLinkingEnabled, "); - sqlBuilder.append("sa.nomination_facility as nominationFacility, sa.dormancy_alert as dormancyAlert, "); - sqlBuilder.append("sa.is_dormancy_tracking_active as isDormancyTrackingActive, "); - sqlBuilder.append("sa.days_to_inactive as daysToInactive, "); - sqlBuilder.append("sa.days_to_dormancy as daysToDormancy, "); - sqlBuilder.append("sa.days_to_escheat as daysToEscheat, "); - sqlBuilder.append("sa.interest_calculate_selector as interestCalculateSelector, "); - sqlBuilder.append("icv.code_value as interestCalculationTypeName, "); - sqlBuilder.append("sa.average_period as averagePeriod, "); - sqlBuilder.append("sa.auto_post as autoPost, sa.frequency_fix_day as frequencyFixDay, "); - sqlBuilder.append("sa.last_interest_date as lastInterestDate, sa.enable_lockin_period as enableLockinPeriod, "); - sqlBuilder.append("sa.balance_frequency as balanceFrequency, "); - sqlBuilder.append("sa.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, "); - sqlBuilder.append("sa.balance_swap_option as balanceSwapOption, sa.msme as msme, "); - sqlBuilder.append("sa.auto_debit_facility as autoDebitFacility, sa.loan_facility as loanFacility, "); - sqlBuilder.append("sa.premature_withdrawal_option as prematureWithdrawalOption, sa.tenure_flexibility as tenureFlexibility, "); - sqlBuilder.append("sa.lien_marked as lienMarked, sa.maturity_instructions as maturityInstructions, "); - sqlBuilder.append("sa.credit_maturity_proceeds as creditMaturityProceeds, "); - sqlBuilder.append("sa.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, "); - sqlBuilder.append("sa.panel_request_applicable as panelRequestApplicable, "); - sqlBuilder.append("sa.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, "); - sqlBuilder.append("sa.deceased_settlement as deceasedSettlement, "); - sqlBuilder.append("sa.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate "); - - sqlBuilder.append(this.selectTablesSql()); - sqlBuilder.append("left join m_deposit_product_recurring_detail dprd on sa.id = dprd.savings_product_id "); - sqlBuilder.append("left join m_code_value icv on icv.id = sa.interest_calculate_selector "); - - this.schemaSql = sqlBuilder.toString(); - } - - @Override - public String schema() { - return this.schemaSql; - } - - @Override - public RecurringDepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final DepositAccountData depositAccountData = super.mapRow(rs); - - final boolean preClosurePenalApplicable = rs.getBoolean(PRE_CLOSURE_PENAL_APPLICABLE); - final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, PRE_CLOSURE_PENAL_INTEREST); - final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, PRE_CLOSURE_PENAL_INTEREST_ON_ID); - final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null - : SavingsEnumerations.preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId); - - final Integer minDepositTerm = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM); - final Integer maxDepositTerm = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM); - final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM_TYPE_ID); - final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null - : SavingsEnumerations.depositTermFrequencyType(minDepositTermTypeId); - final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM_TYPE_ID); - final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null - : SavingsEnumerations.depositTermFrequencyType(maxDepositTermTypeId); - final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM); - final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID); - final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null - : SavingsEnumerations.depositTermFrequencyType(inMultiplesOfDepositTermTypeId); - final boolean isMandatoryDeposit = rs.getBoolean(IS_MANDATORY_DEPOSIT); - final boolean allowWithdrawal = rs.getBoolean(ALLOW_WITHDRAWAL); - final boolean adjustAdvanceTowardsFuturePayments = rs.getBoolean(ADJUST_ADVANCE_TOWARDS_FUTURE_PAYMENTS); - final boolean isCalendarInherited = false; - - final BigDecimal depositAmount = null; - final BigDecimal maturityAmount = null; - final LocalDate maturityDate = null; - final Integer depositPeriod = null; - final EnumOptionData depositPeriodFrequencyType = null; - final LocalDate expectedFirstDepositOnDate = null; - final BigDecimal mandatoryRecommendedDepositAmount = null; - final EnumOptionData onAccountClosureType = null; - final BigDecimal totalOverdueAmount = null; - final Integer noOfOverdueInstallments = null; - - final RecurringDepositAccountData result = RecurringDepositAccountData.instance(depositAccountData, - preClosurePenalApplicable, preClosurePenalInterest, - preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, - inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod, - depositPeriodFrequencyType, mandatoryRecommendedDepositAmount, onAccountClosureType, expectedFirstDepositOnDate, - totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal, adjustAdvanceTowardsFuturePayments, - isCalendarInherited); - - // CBS product fields - result.setCbsProductFields( - rs.getString("accountType"), - rs.getString("customerSegment"), - rs.getString("productCode"), - JdbcSupport.getLocalDate(rs, "effectiveDate"), - JdbcSupport.getLocalDate(rs, "effectiveTo"), - JdbcSupport.getLocalDate(rs, "endDate"), - rs.getBoolean("introducerRequired"), - rs.getBoolean("shareLinkingEnabled"), - rs.getBoolean("nominationFacility"), - rs.getBoolean("dormancyAlert"), - rs.getBoolean("isDormancyTrackingActive"), - JdbcSupport.getInteger(rs, "daysToInactive"), - JdbcSupport.getInteger(rs, "daysToDormancy"), - JdbcSupport.getInteger(rs, "daysToEscheat"), - JdbcSupport.getInteger(rs, "interestCalculateSelector"), - rs.getString("interestCalculationTypeName"), - rs.getString("averagePeriod"), - rs.getBoolean("autoPost"), - JdbcSupport.getInteger(rs, "frequencyFixDay"), - JdbcSupport.getLocalDate(rs, "lastInterestDate"), - rs.getBoolean("enableLockinPeriod"), - rs.getString("balanceFrequency"), - rs.getBoolean("accountClosedZeroBalanceInDayClosing"), - rs.getBoolean("balanceSwapOption"), - rs.getBoolean("msme"), - rs.getBoolean("autoDebitFacility"), - rs.getBoolean("loanFacility"), - rs.getBoolean("prematureWithdrawalOption"), - rs.getBoolean("tenureFlexibility"), - rs.getBoolean("lienMarked"), - rs.getBoolean("maturityInstructions"), - rs.getBoolean("creditMaturityProceeds"), - rs.getBoolean("penaltyGracePeriodMissedInstallments"), - rs.getBoolean("panelRequestApplicable"), - rs.getBoolean("noPenaltyNomineeHeirClosure"), - rs.getBoolean("deceasedSettlement"), - rs.getBoolean("allowDebitTxnAfterDueDate") - ); - - return result; - } - } - - private static final class DepositAccountForMaturityMapper implements RowMapper { - - public static final String ID = "id"; - public static final String ACCOUNT_NUMBER = "accountNumber"; - public static final String DEPOSIT_TYPE_ID = "depositTypeId"; - - public String schema() { - LocalDate today = DateUtils.getBusinessLocalDate(); - String formattedToday = DATE_TIME_FORMATTER.format(today); - final StringBuilder sqlBuilder = new StringBuilder(200); - sqlBuilder.append("da.id as id, "); - sqlBuilder.append("da.account_no as accountNumber, "); - sqlBuilder.append("da.deposit_type_enum as depositTypeId "); - sqlBuilder.append("FROM m_savings_account da "); - sqlBuilder.append("inner join m_deposit_account_term_and_preclosure dat on dat.savings_account_id = da.id "); - sqlBuilder.append("and dat.maturity_date is not null and dat.maturity_date <= '" + formattedToday + "' "); - - return sqlBuilder.toString(); - } - - @Override - public DepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong(ID); - final String name = rs.getString(ACCOUNT_NUMBER); - final Integer depositTypeId = JdbcSupport.getInteger(rs, DEPOSIT_TYPE_ID); - final EnumOptionData depositType = (depositTypeId == null) ? null : SavingsEnumerations.depositType(depositTypeId); - - return DepositAccountData.lookup(id, name, depositType); - } - } - - private static final class RecurringAccountDepositTransactionTemplateMapper implements RowMapper { - - public static final String ID = "id"; - public static final String ACCOUNT_NO = "accountNo"; - public static final String DUEAMOUNT = "dueamount"; - public static final String OUTSTANDING_CHARGE_AMOUNT = "outstandingChargeAmount"; - public static final String DUEDATE = "duedate"; - public static final String CURRENCY_CODE = "currencyCode"; - public static final String CURRENCY_NAME = "currencyName"; - public static final String CURRENCY_NAME_CODE = "currencyNameCode"; - public static final String CURRENCY_DISPLAY_SYMBOL = "currencyDisplaySymbol"; - public static final String CURRENCY_DIGITS = "currencyDigits"; - public static final String IN_MULTIPLES_OF = "inMultiplesOf"; - public static final String RUNNING_BALANCE = "runningBalance"; - private final String schemaSql; - - RecurringAccountDepositTransactionTemplateMapper() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("sa.id as id, sa.account_no as accountNo, "); - sqlBuilder.append( - "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, 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("sa.account_balance_derived as runningBalance, "); - sqlBuilder.append( - "mss.duedate as duedate, (mss.deposit_amount - coalesce(mss.deposit_amount_completed_derived,0)) as dueamount, "); - sqlBuilder.append("coalesce(sac.amount_outstanding_derived,0.0) AS outstandingChargeAmount "); - sqlBuilder.append("from m_savings_account sa "); - sqlBuilder.append("join m_mandatory_savings_schedule mss on mss.savings_account_id=sa.id and mss.completed_derived = false "); - sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); - sqlBuilder.append("LEFT JOIN(SELECT s.savings_account_id AS savings_account_id "); - sqlBuilder.append(",SUM(COALESCE(s.amount_outstanding_derived,0.0)) AS amount_outstanding_derived "); - sqlBuilder.append("FROM m_savings_account_charge s "); - sqlBuilder.append("JOIN m_charge c ON c.id = s.charge_id AND c.charge_time_enum = 3 "); - sqlBuilder.append("WHERE s.savings_account_id = ? "); - sqlBuilder.append("AND s.is_active = TRUE GROUP BY s.savings_account_id)sac ON sac.savings_account_id = sa.id "); - - this.schemaSql = sqlBuilder.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long savingsId = rs.getLong(ID); - final String accountNo = rs.getString(ACCOUNT_NO); - final BigDecimal dueamount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, DUEAMOUNT); - final BigDecimal outstandingChargeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, OUTSTANDING_CHARGE_AMOUNT); - final LocalDate duedate = JdbcSupport.getLocalDate(rs, DUEDATE); - final String currencyCode = rs.getString(CURRENCY_CODE); - final String currencyName = rs.getString(CURRENCY_NAME); - final String currencyNameCode = rs.getString(CURRENCY_NAME_CODE); - final String currencyDisplaySymbol = rs.getString(CURRENCY_DISPLAY_SYMBOL); - final Integer currencyDigits = JdbcSupport.getInteger(rs, CURRENCY_DIGITS); - final Integer inMultiplesOf = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, - currencyNameCode); - final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations - .transactionType(SavingsAccountTransactionType.DEPOSIT.getValue()); - final PaymentDetailData paymentDetailData = null; - final AccountTransferData transfer = null; - final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, RUNNING_BALANCE); - final boolean postInterestAsOn = false; - final String submittedByUsername = null; - final String note = null; - final LocalDate submittedOnDate = DateUtils.getBusinessLocalDate(); - return SavingsAccountTransactionData.create(savingsId, transactionType, paymentDetailData, savingsId, accountNo, duedate, - currency, dueamount, outstandingChargeAmount, runningBalance, false, transfer, postInterestAsOn, submittedByUsername, - note, submittedOnDate); - } - } - - private static class AccountTransferMapper implements RowMapper { - - public static final String FROM_ACC = "fromAcc"; - public static final String TO_ACC = "toAcc"; - public static final String AMOUNT = "amount"; - public static final String TRANSACTION_DATE = "transactionDate"; - public static final String TRANSFER_INTEREST_TO_SAVINGS = "transfer interest to savings"; - private final String schemaSql; - - AccountTransferMapper() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append( - "sa.id as fromAcc ,aa.linked_savings_account_id as toAcc,st.amount as amount, st.transaction_date as transactionDate ") - .append(" from m_deposit_account_term_and_preclosure da ") - .append(" inner join m_savings_account sa on da.savings_account_id = sa.id") - .append(" inner join m_savings_account_transaction st on st.savings_account_id = sa.id") - .append(" inner join m_portfolio_account_associations aa on aa.savings_account_id=sa.id"); - schemaSql = sqlBuilder.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public AccountTransferDTO mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { - final Long fromAccountId = rs.getLong(FROM_ACC); - final Long toAccountId = rs.getLong(TO_ACC); - final BigDecimal transactionAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, AMOUNT); - final boolean isRegularTransaction = false; - final boolean isExceptionForBalanceCheck = false; - final LocalDate transactionDate = JdbcSupport.getLocalDate(rs, TRANSACTION_DATE); - return new AccountTransferDTO(transactionDate, transactionAmount, PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, - fromAccountId, toAccountId, TRANSFER_INTEREST_TO_SAVINGS, null, null, null, null, null, null, null, - AccountTransferType.INTEREST_TRANSFER.getValue(), null, null, ExternalId.empty(), null, null, null, - isRegularTransaction, 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.service; + +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY_DB_FIELD; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.infrastructure.codes.data.CodeValueData; +import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.data.PaginationParameters; +import org.apache.fineract.infrastructure.core.data.PaginationParametersDataValidator; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.domain.JdbcSupport; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.infrastructure.core.service.PaginationHelper; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.staff.data.StaffData; +import org.apache.fineract.organisation.staff.service.StaffReadPlatformService; +import org.apache.fineract.portfolio.account.PortfolioAccountType; +import org.apache.fineract.portfolio.account.service.AccountNumberGenerator; +import org.apache.fineract.portfolio.account.data.AccountTransferDTO; +import org.apache.fineract.portfolio.account.data.AccountTransferData; +import org.apache.fineract.portfolio.account.domain.AccountTransferType; +import org.apache.fineract.portfolio.calendar.data.CalendarData; +import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType; +import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType; +import org.apache.fineract.portfolio.calendar.domain.CalendarType; +import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService; +import org.apache.fineract.portfolio.charge.data.ChargeData; +import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService; +import org.apache.fineract.portfolio.charge.util.ConvertChargeDataToSpecificChargeData; +import org.apache.fineract.portfolio.client.data.ClientData; +import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; +import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; +import org.apache.fineract.portfolio.common.service.CommonEnumerations; +import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService; +import org.apache.fineract.portfolio.group.data.GroupGeneralData; +import org.apache.fineract.portfolio.group.service.GroupReadPlatformService; +import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartReadPlatformService; +import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData; +import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; +import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService; +import org.apache.fineract.portfolio.savings.SavingsApiConstants; +import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType; +import org.apache.fineract.portfolio.savings.DepositAccountType; +import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType; +import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType; +import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; +import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; +import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; +import org.apache.fineract.portfolio.savings.data.DepositAccountData; +import org.apache.fineract.portfolio.savings.data.DepositAccountInterestRateChartData; +import org.apache.fineract.portfolio.savings.data.DepositProductData; +import org.apache.fineract.portfolio.savings.data.FixedDepositAccountData; +import org.apache.fineract.portfolio.savings.data.RecurringDepositAccountData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; +import org.apache.fineract.portfolio.savings.exception.DepositAccountNotFoundException; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; +import org.apache.fineract.useradministration.domain.AppUser; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; + +@Slf4j +@RequiredArgsConstructor +public class DepositAccountReadPlatformServiceImpl implements DepositAccountReadPlatformService { + + private static final FixedDepositAccountMapper FIXED_DEPOSIT_ACCOUNT_MAPPER = new FixedDepositAccountMapper(); + private static final RecurringDepositAccountMapper RECURRING_DEPOSIT_ACCOUNT_MAPPER = new RecurringDepositAccountMapper(); + private static final DepositAccountLookupMapper DEPOSIT_ACCOUNT_LOOKUP_MAPPER = new DepositAccountLookupMapper(); + private static final DepositAccountForMaturityMapper DEPOSIT_ACCOUNT_FOR_MATURITY_MAPPER = new DepositAccountForMaturityMapper(); + // allowed column names for sorting the query result + private static final Set supportedOrderByValues = new HashSet<>(Arrays.asList("id", "accountNumbr", "officeId", "officeName")); + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private final PlatformSecurityContext context; + private final JdbcTemplate jdbcTemplate; + private final DepositAccountInterestRateChartReadPlatformService accountChartReadPlatformService; + private final InterestRateChartReadPlatformService productChartReadPlatformService; + private final PaginationParametersDataValidator paginationParametersDataValidator; + private final DatabaseSpecificSQLGenerator sqlGenerator; + private final PaginationHelper paginationHelper; + private final SavingsAccountTransactionsMapper transactionsMapper = new SavingsAccountTransactionsMapper(); + private final ClientReadPlatformService clientReadPlatformService; + private final GroupReadPlatformService groupReadPlatformService; + private final DepositProductReadPlatformService depositProductReadPlatformService; + private final SavingsDropdownReadPlatformService savingsDropdownReadPlatformService; + private final ChargeReadPlatformService chargeReadPlatformService; + private final StaffReadPlatformService staffReadPlatformService; + private final DepositsDropdownReadPlatformService depositsDropdownReadPlatformService; + private final SavingsAccountReadPlatformService savingsAccountReadPlatformService; + private final RecurringAccountDepositTransactionTemplateMapper rdTransactionTemplateMapper = new RecurringAccountDepositTransactionTemplateMapper(); + private final DropdownReadPlatformService dropdownReadPlatformService; + private final CalendarReadPlatformService calendarReadPlatformService; + private final PaymentTypeReadService paymentTypeReadPlatformService; + private final AccountNumberGenerator accountNumberGenerator; + private final CodeValueReadPlatformService codeValueReadPlatformService; + + @Override + public Collection retrieveAll(final DepositAccountType depositAccountType, + final PaginationParameters paginationParameters) { + + this.context.authenticatedUser(); + this.paginationParametersDataValidator.validateParameterValues(paginationParameters, supportedOrderByValues, + depositAccountType.resourceName()); + + final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType); + if (depositAccountMapper == null) { + return null; + } + + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("select "); + sqlBuilder.append(depositAccountMapper.schema()); + sqlBuilder.append(" where sa.deposit_type_enum = ? "); + // always append at the end of a sql statement + sqlBuilder.append(paginationParameters.paginationSql()); + + return jdbcTemplate.query(sqlBuilder.toString(), depositAccountMapper, new Object[] { depositAccountType.getValue() }); // NOSONAR + } + + @Override + public Page retrieveAllPaged(final DepositAccountType depositAccountType, + final PaginationParameters paginationParameters) { + + this.paginationParametersDataValidator.validateParameterValues(paginationParameters, supportedOrderByValues, + depositAccountType.resourceName()); + + final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType); + if (depositAccountMapper == null) { + return null; + } + + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); + sqlBuilder.append(depositAccountMapper.schema()); + sqlBuilder.append(" where sa.deposit_type_enum = ? "); + sqlBuilder.append(paginationParameters.paginationSql()); + + return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), new Object[] { depositAccountType.getValue() }, + depositAccountMapper); + } + + @Override + public Page retrieveAllPaged(final DepositAccountType depositAccountType, + final PaginationParameters paginationParameters, final String accountNo, final String clientName) { + + this.paginationParametersDataValidator.validateParameterValues(paginationParameters, supportedOrderByValues, + depositAccountType.resourceName()); + + final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType); + if (depositAccountMapper == null) { + return null; + } + + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); + sqlBuilder.append(depositAccountMapper.schema()); + sqlBuilder.append(" where sa.deposit_type_enum = ? "); + + final List params = new ArrayList<>(); + params.add(depositAccountType.getValue()); + if (StringUtils.isNotBlank(accountNo)) { + sqlBuilder.append(" and sa.account_no like ? "); + params.add("%" + accountNo + "%"); + } + if (StringUtils.isNotBlank(clientName)) { + sqlBuilder.append(" and c.display_name like ? "); + params.add("%" + clientName + "%"); + } + sqlBuilder.append(paginationParameters.paginationSql()); + + return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), params.toArray(), depositAccountMapper); + } + + @Override + public Collection retrieveAllForLookup(final DepositAccountType depositAccountType) { + + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("select "); + sqlBuilder.append(DEPOSIT_ACCOUNT_LOOKUP_MAPPER.schema()); + sqlBuilder.append(" where sa.deposit_type_enum = ? "); + + return this.jdbcTemplate.query(sqlBuilder.toString(), DEPOSIT_ACCOUNT_LOOKUP_MAPPER, depositAccountType.getValue()); + } + + @Override + public Collection retrieveForMaturityUpdate() { + + final StringBuilder sqlBuilder = new StringBuilder(200); + sqlBuilder.append("SELECT "); + sqlBuilder.append(DEPOSIT_ACCOUNT_FOR_MATURITY_MAPPER.schema()); + sqlBuilder.append(" WHERE da.deposit_type_enum in (?, ?) and da.status_enum = ?"); + + return this.jdbcTemplate.query(sqlBuilder.toString(), DEPOSIT_ACCOUNT_FOR_MATURITY_MAPPER, + DepositAccountType.FIXED_DEPOSIT.getValue(), DepositAccountType.RECURRING_DEPOSIT.getValue(), + SavingsAccountStatusType.ACTIVE.getValue()); + } + + @Override + public DepositAccountData retrieveOne(final DepositAccountType depositAccountType, final Long accountId) { + try { + this.context.authenticatedUser(); + + final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType); + if (depositAccountMapper == null) { + return null; + } + + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("select "); + sqlBuilder.append(depositAccountMapper.schema()); + sqlBuilder.append(" where sa.id = ? and sa.deposit_type_enum = ? "); + + return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), depositAccountMapper, accountId, depositAccountType.getValue()); + + } catch (final EmptyResultDataAccessException e) { + throw new DepositAccountNotFoundException(depositAccountType, accountId, e); + } + } + + @Override + public DepositAccountData retrieveOneWithClosureTemplate(final DepositAccountType depositAccountType, final Long accountId) { + try { + this.context.authenticatedUser(); + + final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType); + if (depositAccountMapper == null) { + return null; + } + + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("select "); + sqlBuilder.append(depositAccountMapper.schema()); + sqlBuilder.append(" where sa.id = ? and sa.deposit_type_enum = ? "); + + DepositAccountData account = this.jdbcTemplate.queryForObject(sqlBuilder.toString(), depositAccountMapper, accountId, + depositAccountType.getValue()); + Collection onAccountClosureOptions = SavingsEnumerations + .depositAccountOnClosureType(DepositAccountOnClosureType.values()); + final Collection paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); + final Collection savingsAccountDatas = this.savingsAccountReadPlatformService + .retrieveActiveForLookup(account.getClientId(), DepositAccountType.SAVINGS_DEPOSIT); + if (depositAccountType.isFixedDeposit()) { + account = FixedDepositAccountData.withClosureTemplateDetails((FixedDepositAccountData) account, onAccountClosureOptions, + paymentTypeOptions, savingsAccountDatas); + } else if (depositAccountType.isRecurringDeposit()) { + account = RecurringDepositAccountData.withClosureTemplateDetails((RecurringDepositAccountData) account, + onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas); + } + + return account; + + } catch (final EmptyResultDataAccessException e) { + throw new DepositAccountNotFoundException(depositAccountType, accountId, e); + } + } + + @Override + public DepositAccountData retrieveOneWithChartSlabs(final DepositAccountType depositAccountType, Long accountId) { + DepositAccountData depositAccount = this.retrieveOne(depositAccountType, accountId); + DepositAccountInterestRateChartData chart = this.accountChartReadPlatformService.retrieveOneWithSlabsOnAccountId(accountId); + + if (depositAccountType.isFixedDeposit()) { + depositAccount = FixedDepositAccountData.withInterestChart((FixedDepositAccountData) depositAccount, chart); + } else if (depositAccountType.isRecurringDeposit()) { + CalendarData calendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(accountId, + CalendarEntityType.SAVINGS.getValue()); + Integer frequency = 1; + EnumOptionData frequencyType = null; + if (calendar != null) { + frequency = calendar.getInterval() == -1 ? 1 : calendar.getInterval(); + final CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType + .fromInt(calendar.getFrequency().getId().intValue()); + final PeriodFrequencyType periodFrequencyType = CalendarFrequencyType.from(calendarFrequencyType); + frequencyType = CommonEnumerations.termFrequencyType(periodFrequencyType, "recurring.deposit.frequency."); + } + depositAccount = RecurringDepositAccountData.withInterestChartAndRecurringDetails((RecurringDepositAccountData) depositAccount, + chart, frequency, frequencyType); + } + + return depositAccount; + } + + @Override + public Collection retrieveAllTransactions(final DepositAccountType depositAccountType, + final Long accountId) { + + final String sql = "select " + this.transactionsMapper.schema() + + " where sa.id = ? and sa.deposit_type_enum = ? order by tr.transaction_date DESC, tr.id DESC"; + + return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { accountId, depositAccountType.getValue() }); // NOSONAR + } + + @Override + public DepositAccountData retrieveTemplate(final DepositAccountType depositAccountType, final Long clientId, final Long groupId, + final Long productId, final boolean staffInSelectedOfficeOnly) { + + final AppUser loggedInUser = this.context.authenticatedUser(); + Long officeId = loggedInUser.getOffice().getId(); + + ClientData client = null; + Collection savingsAccountDatas = null; + if (clientId != null) { + client = this.clientReadPlatformService.retrieveOne(clientId); + officeId = client.getOfficeId(); + savingsAccountDatas = this.savingsAccountReadPlatformService.retrieveActiveForLookup(clientId, + DepositAccountType.SAVINGS_DEPOSIT); + } + + GroupGeneralData group = null; + if (groupId != null) { + group = this.groupReadPlatformService.retrieveOne(groupId); + officeId = group.getOfficeId(); + } + + final Collection preClosurePenalInterestOnTypeOptions = this.depositsDropdownReadPlatformService + .retrievePreClosurePenalInterestOnTypeOptions(); + + final Collection periodFrequencyTypeOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions(); + final Collection productOptions = this.depositProductReadPlatformService + .retrieveAllForLookup(depositAccountType); + DepositAccountData template = null; + if (productId != null) { + + final DepositAccountTemplateMapper mapper = getDepositAccountTemplaMapper(depositAccountType, client, group); + + final String sql = "select " + mapper.schema() + " where sa.id = ? and sa.deposit_type_enum = ? "; + template = this.jdbcTemplate.queryForObject(sql, mapper, new Object[] { productId, depositAccountType.getValue() }); // NOSONAR + + final Collection interestCompoundingPeriodTypeOptions = this.savingsDropdownReadPlatformService + .retrieveCompoundingInterestPeriodTypeOptions(); + + final Collection interestPostingPeriodTypeOptions = this.savingsDropdownReadPlatformService + .retrieveInterestPostingPeriodTypeOptions(); + + final Collection interestCalculationTypeOptions = this.savingsDropdownReadPlatformService + .retrieveInterestCalculationTypeOptions(); + + final Collection interestCalculationDaysInYearTypeOptions = this.savingsDropdownReadPlatformService + .retrieveInterestCalculationDaysInYearTypeOptions(); + + final Collection lockinPeriodFrequencyTypeOptions = this.savingsDropdownReadPlatformService + .retrieveLockinPeriodFrequencyTypeOptions(); + + final Collection withdrawalFeeTypeOptions = this.savingsDropdownReadPlatformService + .retrievewithdrawalFeeTypeOptions(); + + final Collection transactions = null; + final Collection productCharges = this.chargeReadPlatformService.retrieveSavingsProductCharges(productId); + // update charges from Product charges + final Collection charges = fromChargesToSavingsCharges(productCharges); + + final boolean feeChargesOnly = false; + final Collection chargeOptions = this.chargeReadPlatformService + .retrieveSavingsProductApplicableCharges(feeChargesOnly); + + final Collection maturityInstructionOptions = this.depositsDropdownReadPlatformService + .maturityInstructionOptions(); + + Collection fieldOfficerOptions = null; + + if (officeId != null) { + + if (staffInSelectedOfficeOnly) { + // only bring back loan officers in selected branch/office + final Collection fieldOfficersInBranch = this.staffReadPlatformService + .retrieveAllLoanOfficersInOfficeById(officeId); + + if (!CollectionUtils.isEmpty(fieldOfficersInBranch)) { + fieldOfficerOptions = new ArrayList<>(fieldOfficersInBranch); + } + } else { + // by default bring back all officers in selected + // branch/office as well as officers in office above + // this office + final boolean restrictToLoanOfficersOnly = true; + final Collection loanOfficersInHierarchy = this.staffReadPlatformService + .retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(officeId, restrictToLoanOfficersOnly); + + if (!CollectionUtils.isEmpty(loanOfficersInHierarchy)) { + fieldOfficerOptions = new ArrayList<>(loanOfficersInHierarchy); + } + } + } + + // retrieve chart Slabs + final InterestRateChartData productChartData = this.productChartReadPlatformService.retrieveActiveChartWithTemplate(productId); + final DepositAccountInterestRateChartData accountChart = (productChartData != null) + ? DepositAccountInterestRateChartData.from(productChartData) + : null; + + if (depositAccountType.isFixedDeposit()) { + + template = FixedDepositAccountData.withTemplateOptions((FixedDepositAccountData) template, productOptions, + fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, + withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions, + periodFrequencyTypeOptions, savingsAccountDatas, maturityInstructionOptions); + + template = FixedDepositAccountData.withInterestChart((FixedDepositAccountData) template, accountChart); + + try { + final Collection accountTypeOpts = this.codeValueReadPlatformService + .retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_ACCOUNT_TYPE); + final Collection customerSegmentOpts = this.codeValueReadPlatformService + .retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_CUSTOMER_SEGMENT); + ((FixedDepositAccountData) template).setAccountTypeOptions(accountTypeOpts); + ((FixedDepositAccountData) template).setCustomerSegmentOptions(customerSegmentOpts); + } catch (final Exception ex) { + log.warn("Failed to load FD account type/customer segment options: {}", ex.getMessage()); + } + + try { + final String preview = accountNumberGenerator.previewAccountNumber(productId, officeId); + template.setPreviewAccountNo(preview); + } catch (final Exception ex) { + log.warn("Failed to generate preview account number for FD productId={}: {}", productId, ex.getMessage()); + } + } else if (depositAccountType.isRecurringDeposit()) { + // Save CBS fields from mapper result before factory methods create new objects + final RecurringDepositAccountData rdTemplateWithCbs = (RecurringDepositAccountData) template; + template = RecurringDepositAccountData.withTemplateOptions(rdTemplateWithCbs, productOptions, + fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, + withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions, + periodFrequencyTypeOptions); + template = RecurringDepositAccountData.withInterestChartAndRecurringDetails((RecurringDepositAccountData) template, + accountChart, null, null); + // Re-apply CBS product fields lost during factory method object creation + ((RecurringDepositAccountData) template).copyCbsFieldsFrom(rdTemplateWithCbs); + + // Populate account type / customer segment options from code values + try { + final Collection accountTypeOpts = this.codeValueReadPlatformService + .retrieveCodeValuesByCode(SavingsApiConstants.RD_PRODUCT_ACCOUNT_TYPE); + final Collection customerSegmentOpts = this.codeValueReadPlatformService + .retrieveCodeValuesByCode(SavingsApiConstants.RD_PRODUCT_CUSTOMER_SEGMENT); + ((RecurringDepositAccountData) template).setAccountTypeOptions(accountTypeOpts); + ((RecurringDepositAccountData) template).setCustomerSegmentOptions(customerSegmentOpts); + } catch (final Exception ex) { + log.warn("Failed to load RD account type/customer segment options: {}", ex.getMessage()); + } + + // Generate preview account number + try { + final String preview = accountNumberGenerator.previewAccountNumber(productId, officeId); + template.setPreviewAccountNo(preview); + } catch (final Exception ex) { + log.warn("Failed to generate preview account number for RD productId={}: {}", productId, ex.getMessage()); + } + } + + } else { + + String clientName = null; + if (client != null) { + clientName = client.getDisplayName(); + } + + String groupName = null; + if (group != null) { + groupName = group.getName(); + } + + final Collection fieldOfficerOptions = null; + final Collection interestCompoundingPeriodTypeOptions = null; + final Collection interestPostingPeriodTypeOptions = null; + final Collection interestCalculationTypeOptions = null; + final Collection interestCalculationDaysInYearTypeOptions = null; + final Collection lockinPeriodFrequencyTypeOptions = null; + final Collection withdrawalFeeTypeOptions = null; + + final Collection transactions = null; + final Collection charges = null; + final Collection maturityInstructionOptions = null; + + final boolean feeChargesOnly = true; + final Collection chargeOptions = this.chargeReadPlatformService + .retrieveSavingsProductApplicableCharges(feeChargesOnly); + + if (depositAccountType.isFixedDeposit()) { + + template = FixedDepositAccountData.withClientTemplate(clientId, clientName, groupId, groupName); + + template = FixedDepositAccountData.withTemplateOptions((FixedDepositAccountData) template, productOptions, + fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, + withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions, + periodFrequencyTypeOptions, savingsAccountDatas, maturityInstructionOptions); + } else if (depositAccountType.isRecurringDeposit()) { + + template = RecurringDepositAccountData.withClientTemplate(clientId, clientName, groupId, groupName); + + template = RecurringDepositAccountData.withTemplateOptions((RecurringDepositAccountData) template, productOptions, + fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, + interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, + withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions, + periodFrequencyTypeOptions); + } + } + + return template; + } + + @Override + public SavingsAccountTransactionData retrieveRecurringAccountDepositTransactionTemplate(final Long accountId) { + + try { + final String sql = "select " + this.rdTransactionTemplateMapper.schema() + + " where sa.id = ? and sa.deposit_type_enum = ? order by mss.installment limit 1"; + return this.jdbcTemplate.queryForObject(sql, this.rdTransactionTemplateMapper, // NOSONAR + accountId, accountId, DepositAccountType.RECURRING_DEPOSIT.getValue()); + } catch (final EmptyResultDataAccessException e) { + throw new DepositAccountNotFoundException(DepositAccountType.RECURRING_DEPOSIT, accountId, e); + } + } + + @Override + public Collection retrieveDataForInterestTransfer() { + final StringBuilder sqlBuilder = new StringBuilder(300); + AccountTransferMapper mapper = new AccountTransferMapper(); + sqlBuilder.append("SELECT "); + sqlBuilder.append(mapper.schema()); + sqlBuilder.append(" where da.transfer_interest_to_linked_account = true and "); + sqlBuilder.append( + "st.transaction_date > (select coalesce(max(sat.transaction_date),sa.activatedon_date) from m_savings_account_transaction sat where sat.transaction_type_enum = ? and sat.savings_account_id = sa.id and sat.is_reversed=false) "); + sqlBuilder.append( + "and st.transaction_type_enum = ? and sa.status_enum = ? and st.is_reversed=false and st.transaction_date > coalesce(sa.lockedin_until_date_derived,sa.activatedon_date)"); + + return this.jdbcTemplate.query(sqlBuilder.toString(), mapper, SavingsAccountTransactionType.WITHDRAWAL.getValue(), + SavingsAccountTransactionType.INTEREST_POSTING.getValue(), SavingsAccountStatusType.ACTIVE.getValue()); + } + + @Override + public Collection> retriveDataForRDScheduleCreation() { + String today = DATE_TIME_FORMATTER.format(DateUtils.getBusinessLocalDate()); + final StringBuilder sb = new StringBuilder(300); + sb.append(" select rd.savings_account_id savingsId, rd.mandatory_recommended_deposit_amount as amount,"); + sb.append(" mc.recurrence as recurrence ,"); + sb.append(" max(ms.duedate) as dueDate , max(ms.installment) as installment,"); + sb.append(" count(ms.installment) as futureInstallments"); + sb.append(" from m_deposit_account_term_and_preclosure dat "); + sb.append(" inner join m_savings_account sa on sa.id = dat.savings_account_id and sa.status_enum = ?"); + sb.append(" inner join m_deposit_account_recurring_detail rd on rd.savings_account_id = dat.savings_account_id "); + sb.append(" inner join m_calendar_instance mci on mci.entity_type_enum = ? and mci.entity_id = dat.savings_account_id "); + sb.append(" inner join m_calendar mc on mc.id = mci.calendar_id and mc.calendar_type_enum = ?"); + sb.append(" inner join m_mandatory_savings_schedule ms on ms.savings_account_id = dat.savings_account_id and ms.duedate > '" + today + + "'"); + sb.append(" where dat.deposit_period is null"); + sb.append(" group by ms.savings_account_id, rd.mandatory_recommended_deposit_amount, mc.recurrence, rd.savings_account_id"); + + return this.jdbcTemplate.queryForList(sb.toString(), SavingsAccountStatusType.ACTIVE.getValue(), + CalendarEntityType.SAVINGS.getValue(), CalendarType.COLLECTION.getValue()); + } + + private DepositAccountMapper getDepositAccountMapper(final DepositAccountType depositAccountType) { + if (depositAccountType.isFixedDeposit()) { + return FIXED_DEPOSIT_ACCOUNT_MAPPER; + } else if (depositAccountType.isRecurringDeposit()) { + return RECURRING_DEPOSIT_ACCOUNT_MAPPER; + } + return null; + } + + private DepositAccountTemplateMapper getDepositAccountTemplaMapper(final DepositAccountType depositAccountType, ClientData client, + GroupGeneralData group) { + if (depositAccountType.isFixedDeposit()) { + return new FixedDepositAccountTemplateMapper(client, group); + } else if (depositAccountType.isRecurringDeposit()) { + return new RecurringDepositAccountTemplateMapper(client, group); + } + return null; + } + + private Collection fromChargesToSavingsCharges(final Collection productCharges) { + final Collection savingsCharges = new ArrayList<>(); + for (final ChargeData chargeData : productCharges) { + final SavingsAccountChargeData savingsCharge = ConvertChargeDataToSpecificChargeData.toSavingsAccountChargeData(chargeData); + savingsCharges.add(savingsCharge); + } + return savingsCharges; + } + + private abstract static class DepositAccountMapper implements RowMapper { + + public static final String ACCOUNT_NO = "accountNo"; + public static final String ID = "id"; + public static final String EXTERNAL_ID = "externalId"; + public static final String GROUP_ID = "groupId"; + public static final String GROUP_NAME = "groupName"; + public static final String CLIENT_ID = "clientId"; + public static final String CLIENT_NAME = "clientName"; + public static final String PRODUCT_ID = "productId"; + public static final String PRODUCT_NAME = "productName"; + public static final String FIELD_OFFICER_ID = "fieldOfficerId"; + public static final String FIELD_OFFICER_NAME = "fieldOfficerName"; + public static final String STATUS_ENUM = "statusEnum"; + public static final String SUBMITTED_ON_DATE = "submittedOnDate"; + public static final String SUBMITTED_BY_USERNAME = "submittedByUsername"; + public static final String SUBMITTED_BY_LASTNAME = "submittedByLastname"; + public static final String SUBMITTED_BY_FIRSTNAME = "submittedByFirstname"; + public static final String REJECTED_ON_DATE = "rejectedOnDate"; + public static final String REJECTED_BY_USERNAME = "rejectedByUsername"; + public static final String REJECTED_BY_FIRSTNAME = "rejectedByFirstname"; + public static final String REJECTED_BY_LASTNAME = "rejectedByLastname"; + public static final String WITHDRAWN_ON_DATE = "withdrawnOnDate"; + public static final String WITHDRAWN_BY_USERNAME = "withdrawnByUsername"; + public static final String WITHDRAWN_BY_FIRSTNAME = "withdrawnByFirstname"; + public static final String WITHDRAWN_BY_LASTNAME = "withdrawnByLastname"; + public static final String APPROVED_ON_DATE = "approvedOnDate"; + public static final String APPROVED_BY_USERNAME = "approvedByUsername"; + public static final String APPROVED_BY_FIRSTNAME = "approvedByFirstname"; + public static final String APPROVED_BY_LASTNAME = "approvedByLastname"; + public static final String ACTIVATED_ON_DATE = "activatedOnDate"; + public static final String ACTIVATED_BY_USERNAME = "activatedByUsername"; + public static final String ACTIVATED_BY_FIRSTNAME = "activatedByFirstname"; + public static final String ACTIVATED_BY_LASTNAME = "activatedByLastname"; + public static final String CLOSED_ON_DATE = "closedOnDate"; + public static final String CLOSED_BY_USERNAME = "closedByUsername"; + public static final String CLOSED_BY_FIRSTNAME = "closedByFirstname"; + public static final String CLOSED_BY_LASTNAME = "closedByLastname"; + public static final String DEPOSIT_TYPE_ID = "depositTypeId"; + public static final String CURRENCY_CODE = "currencyCode"; + public static final String CURRENCY_NAME = "currencyName"; + public static final String CURRENCY_NAME_CODE = "currencyNameCode"; + public static final String CURRENCY_DISPLAY_SYMBOL = "currencyDisplaySymbol"; + public static final String CURRENCY_DIGITS = "currencyDigits"; + public static final String IN_MULTIPLES_OF = "inMultiplesOf"; + public static final String NOMINAL_ANNUAL_INTEREST_RATE = "nominalAnnualInterestRate"; + public static final String INTEREST_COMPOUNDING_PERIOD_TYPE = "interestCompoundingPeriodType"; + public static final String INTEREST_POSTING_PERIOD_TYPE = "interestPostingPeriodType"; + public static final String INTEREST_CALCULATION_TYPE = "interestCalculationType"; + public static final String INTEREST_CALCULATION_DAYS_IN_YEAR_TYPE = "interestCalculationDaysInYearType"; + public static final String MIN_REQUIRED_OPENING_BALANCE = "minRequiredOpeningBalance"; + public static final String LOCKIN_PERIOD_FREQUENCY = "lockinPeriodFrequency"; + public static final String LOCKIN_PERIOD_FREQUENCY_TYPE = "lockinPeriodFrequencyType"; + public static final String WITHDRAWAL_FEE_FOR_TRANSFERS = "withdrawalFeeForTransfers"; + public static final String MIN_BALANCE_FOR_INTEREST_CALCULATION = "minBalanceForInterestCalculation"; + public static final String TOTAL_DEPOSITS = "totalDeposits"; + public static final String TOTAL_WITHDRAWALS = "totalWithdrawals"; + public static final String TOTAL_WITHDRAWAL_FEES = "totalWithdrawalFees"; + public static final String TOTAL_ANNUAL_FEES = "totalAnnualFees"; + public static final String TOTAL_INTEREST_EARNED = "totalInterestEarned"; + public static final String TOTAL_INTEREST_POSTED = "totalInterestPosted"; + public static final String ACCOUNT_BALANCE = "accountBalance"; + public static final String TOTAL_FEE_CHARGE = "totalFeeCharge"; + public static final String TOTAL_PENALTY_CHARGE = "totalPenaltyCharge"; + public static final String TOTAL_WITHHOLD_TAX = "totalWithholdTax"; + public static final String INTEREST_POSTED_TILL_DATE = "interestPostedTillDate"; + public static final String WITH_HOLD_TAX = "withHoldTax"; + public static final String TAX_GROUP_ID = "taxGroupId"; + public static final String TAX_GROUP_NAME = "taxGroupName"; + private final String selectFieldsSql; + private final String selectTablesSql; + + protected DepositAccountMapper() { + final StringBuilder selectFieldsSqlBuilder = new StringBuilder(400); + selectFieldsSqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, "); + selectFieldsSqlBuilder.append("c.id as clientId, c.display_name as clientName, "); + selectFieldsSqlBuilder.append("g.id as groupId, g.display_name as groupName, "); + selectFieldsSqlBuilder.append("sp.id as productId, sp.name as productName, "); + selectFieldsSqlBuilder.append("s.id fieldOfficerId, s.display_name as fieldOfficerName, "); + selectFieldsSqlBuilder.append("sa.status_enum as statusEnum, "); + selectFieldsSqlBuilder.append("sa.submittedon_date as submittedOnDate,"); + selectFieldsSqlBuilder.append("sbu.username as submittedByUsername,"); + selectFieldsSqlBuilder.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,"); + selectFieldsSqlBuilder.append("sa.rejectedon_date as rejectedOnDate,"); + selectFieldsSqlBuilder.append("rbu.username as rejectedByUsername,"); + selectFieldsSqlBuilder.append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,"); + selectFieldsSqlBuilder.append("sa.withdrawnon_date as withdrawnOnDate,"); + selectFieldsSqlBuilder.append("wbu.username as withdrawnByUsername,"); + selectFieldsSqlBuilder.append("wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,"); + selectFieldsSqlBuilder.append("sa.approvedon_date as approvedOnDate,"); + selectFieldsSqlBuilder.append("abu.username as approvedByUsername,"); + selectFieldsSqlBuilder.append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,"); + selectFieldsSqlBuilder.append("sa.activatedon_date as activatedOnDate,"); + selectFieldsSqlBuilder.append("avbu.username as activatedByUsername,"); + selectFieldsSqlBuilder.append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname,"); + selectFieldsSqlBuilder.append("sa.closedon_date as closedOnDate,"); + selectFieldsSqlBuilder.append("cbu.username as closedByUsername,"); + selectFieldsSqlBuilder.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,"); + selectFieldsSqlBuilder.append( + "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); + selectFieldsSqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); + selectFieldsSqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); + selectFieldsSqlBuilder.append("sa.nominal_annual_interest_rate as nominalAnnualInterestRate, "); + selectFieldsSqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, "); + selectFieldsSqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, "); + selectFieldsSqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, "); + selectFieldsSqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, "); + selectFieldsSqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, "); + selectFieldsSqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,"); + selectFieldsSqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); + selectFieldsSqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); + selectFieldsSqlBuilder.append("sa.total_deposits_derived as totalDeposits, "); + selectFieldsSqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, "); + selectFieldsSqlBuilder.append("sa.interest_posted_till_date as interestPostedTillDate, "); + selectFieldsSqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, "); + selectFieldsSqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, "); + selectFieldsSqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, "); + selectFieldsSqlBuilder.append("sa.total_interest_posted_derived as totalInterestPosted, "); + selectFieldsSqlBuilder.append("sa.account_balance_derived as accountBalance, "); + selectFieldsSqlBuilder.append("sa.total_fees_charge_derived as totalFeeCharge, "); + selectFieldsSqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, "); + selectFieldsSqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax,"); + selectFieldsSqlBuilder.append("sa.deposit_type_enum as depositTypeId, "); + selectFieldsSqlBuilder.append("sa.cbs_account_type as accountType, sa.customer_segment as customerSegment, "); + selectFieldsSqlBuilder.append("sa.account_status as accountStatus, sa.mode_of_operation as modeOfOperation, "); + selectFieldsSqlBuilder.append("sa.special_category as specialCategory, sa.weaker_section as weakerSection, "); + selectFieldsSqlBuilder.append("sa.guardian_name as guardianName, sa.guardian_cif_number as guardianCifNumber, "); + selectFieldsSqlBuilder.append("sa.guardian_relationship as guardianRelationship, sa.guardian_client_id as guardianClientId, "); + selectFieldsSqlBuilder.append("sa.minor_date_of_birth as minorDateOfBirth, "); + selectFieldsSqlBuilder.append("sa.introducer_client_id as introducerClientId, sa.introducer_name as introducerName, "); + selectFieldsSqlBuilder.append("sa.introducer_cif_number as introducerCifNumber, "); + selectFieldsSqlBuilder.append("sa.linked_account_id as linkedAccountId, sa.linked_account_type as linkedAccountType, "); + selectFieldsSqlBuilder.append("sa.linked_account_no as linkedAccountNo, "); + selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation, "); + selectFieldsSqlBuilder.append("sa.withhold_tax as withHoldTax,"); + selectFieldsSqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName "); + + this.selectFieldsSql = selectFieldsSqlBuilder.toString(); + + final StringBuilder selectTablesSqlBuilder = new StringBuilder(400); + selectTablesSqlBuilder.append("from m_savings_account sa "); + selectTablesSqlBuilder.append("left join m_deposit_account_term_and_preclosure datp on sa.id = datp.savings_account_id "); + selectTablesSqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id "); + selectTablesSqlBuilder.append("left join m_code_value icv on icv.id = sp.interest_calculate_selector "); + selectTablesSqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); + selectTablesSqlBuilder.append("left join m_client c ON c.id = sa.client_id "); + selectTablesSqlBuilder.append("left join m_group g ON g.id = sa.group_id "); + selectTablesSqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id "); + selectTablesSqlBuilder.append("left join m_appuser sbu on sbu.id = sa.submittedon_userid "); + selectTablesSqlBuilder.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid "); + selectTablesSqlBuilder.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid "); + selectTablesSqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid "); + selectTablesSqlBuilder.append("left join m_appuser avbu on avbu.id = sa.activatedon_userid "); + selectTablesSqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid "); + selectTablesSqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id "); + + this.selectTablesSql = selectTablesSqlBuilder.toString(); + } + + public String selectFieldsSql() { + return this.selectFieldsSql; + } + + public String selectTablesSql() { + return this.selectTablesSql; + } + + public abstract String schema(); + + public DepositAccountData mapRow(final ResultSet rs) throws SQLException { + + final Long id = rs.getLong(ID); + final String accountNo = rs.getString(ACCOUNT_NO); + final String externalId = rs.getString(EXTERNAL_ID); + + final Long groupId = JdbcSupport.getLong(rs, GROUP_ID); + final String groupName = rs.getString(GROUP_NAME); + final Long clientId = JdbcSupport.getLong(rs, CLIENT_ID); + final String clientName = rs.getString(CLIENT_NAME); + + final Long productId = rs.getLong(PRODUCT_ID); + final String productName = rs.getString(PRODUCT_NAME); + + final Long fieldOfficerId = rs.getLong(FIELD_OFFICER_ID); + final String fieldOfficerName = rs.getString(FIELD_OFFICER_NAME); + + final Integer statusEnum = JdbcSupport.getInteger(rs, STATUS_ENUM); + final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum); + + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, SUBMITTED_ON_DATE); + final String submittedByUsername = rs.getString(SUBMITTED_BY_USERNAME); + final String submittedByFirstname = rs.getString(SUBMITTED_BY_FIRSTNAME); + final String submittedByLastname = rs.getString(SUBMITTED_BY_LASTNAME); + + final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, REJECTED_ON_DATE); + final String rejectedByUsername = rs.getString(REJECTED_BY_USERNAME); + final String rejectedByFirstname = rs.getString(REJECTED_BY_FIRSTNAME); + final String rejectedByLastname = rs.getString(REJECTED_BY_LASTNAME); + + final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, WITHDRAWN_ON_DATE); + final String withdrawnByUsername = rs.getString(WITHDRAWN_BY_USERNAME); + final String withdrawnByFirstname = rs.getString(WITHDRAWN_BY_FIRSTNAME); + final String withdrawnByLastname = rs.getString(WITHDRAWN_BY_LASTNAME); + + final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, APPROVED_ON_DATE); + final String approvedByUsername = rs.getString(APPROVED_BY_USERNAME); + final String approvedByFirstname = rs.getString(APPROVED_BY_FIRSTNAME); + final String approvedByLastname = rs.getString(APPROVED_BY_LASTNAME); + + final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, ACTIVATED_ON_DATE); + final String activatedByUsername = rs.getString(ACTIVATED_BY_USERNAME); + final String activatedByFirstname = rs.getString(ACTIVATED_BY_FIRSTNAME); + final String activatedByLastname = rs.getString(ACTIVATED_BY_LASTNAME); + + final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, CLOSED_ON_DATE); + final String closedByUsername = rs.getString(CLOSED_BY_USERNAME); + final String closedByFirstname = rs.getString(CLOSED_BY_FIRSTNAME); + final String closedByLastname = rs.getString(CLOSED_BY_LASTNAME); + + final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, + submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, + rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, + approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, activatedByUsername, activatedByFirstname, + activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname); + + final Integer depositTypeId = JdbcSupport.getInteger(rs, DEPOSIT_TYPE_ID); + final EnumOptionData depositType = (depositTypeId == null) ? null : SavingsEnumerations.depositType(depositTypeId); + + final String currencyCode = rs.getString(CURRENCY_CODE); + final String currencyName = rs.getString(CURRENCY_NAME); + final String currencyNameCode = rs.getString(CURRENCY_NAME_CODE); + final String currencyDisplaySymbol = rs.getString(CURRENCY_DISPLAY_SYMBOL); + final Integer currencyDigits = JdbcSupport.getInteger(rs, CURRENCY_DIGITS); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, + currencyNameCode); + + final BigDecimal nominalAnnualInterestRate = rs.getBigDecimal(NOMINAL_ANNUAL_INTEREST_RATE); + + final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations.compoundingInterestPeriodType( + SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, INTEREST_COMPOUNDING_PERIOD_TYPE))); + + final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType( + SavingsPostingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, INTEREST_POSTING_PERIOD_TYPE))); + + final EnumOptionData interestCalculationType = SavingsEnumerations + .interestCalculationType(SavingsInterestCalculationType.fromInt(JdbcSupport.getInteger(rs, INTEREST_CALCULATION_TYPE))); + + final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations.interestCalculationDaysInYearType( + SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs, INTEREST_CALCULATION_DAYS_IN_YEAR_TYPE))); + + final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, MIN_REQUIRED_OPENING_BALANCE); + + final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, LOCKIN_PERIOD_FREQUENCY); + EnumOptionData lockinPeriodFrequencyType = null; + final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, LOCKIN_PERIOD_FREQUENCY_TYPE); + if (lockinPeriodFrequencyTypeValue != null) { + final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); + lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType); + } + + final boolean withdrawalFeeForTransfers = rs.getBoolean(WITHDRAWAL_FEE_FOR_TRANSFERS); + final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + MIN_BALANCE_FOR_INTEREST_CALCULATION); + + final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_DEPOSITS); + final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_WITHDRAWALS); + final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_WITHDRAWAL_FEES); + final BigDecimal totalAnnualFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_ANNUAL_FEES); + + final BigDecimal totalInterestEarned = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_INTEREST_EARNED); + final BigDecimal totalInterestPosted = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_INTEREST_POSTED); + final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, ACCOUNT_BALANCE); + final BigDecimal totalFeeCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_FEE_CHARGE); + final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_PENALTY_CHARGE); + final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_WITHHOLD_TAX); + final BigDecimal totalOverdraftInterestDerived = null; + final LocalDate interestPostedTillDate = JdbcSupport.getLocalDate(rs, INTEREST_POSTED_TILL_DATE); + + final boolean withHoldTax = rs.getBoolean(WITH_HOLD_TAX); + final Long taxGroupId = JdbcSupport.getLong(rs, TAX_GROUP_ID); + final String taxGroupName = rs.getString(TAX_GROUP_NAME); + TaxGroupData taxGroupData = null; + if (taxGroupId != null) { + taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); + } + + final BigDecimal availableBalance = null; + final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, + totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge, + totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax, null, null, availableBalance, + interestPostedTillDate); + + return DepositAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, + fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, + interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, depositType, + minBalanceForInterestCalculation, withHoldTax, taxGroupData); + } + } + + private static class FixedDepositAccountMapper extends DepositAccountMapper { + + public static final String PRE_CLOSURE_PENAL_APPLICABLE = "preClosurePenalApplicable"; + public static final String PRE_CLOSURE_PENAL_INTEREST = "preClosurePenalInterest"; + public static final String PRE_CLOSURE_PENAL_INTEREST_ON_ID = "preClosurePenalInterestOnId"; + public static final String MIN_DEPOSIT_TERM = "minDepositTerm"; + public static final String MAX_DEPOSIT_TERM = "maxDepositTerm"; + public static final String MIN_DEPOSIT_TERM_TYPE_ID = "minDepositTermTypeId"; + public static final String MAX_DEPOSIT_TERM_TYPE_ID = "maxDepositTermTypeId"; + public static final String IN_MULTIPLES_OF_DEPOSIT_TERM = "inMultiplesOfDepositTerm"; + public static final String IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID = "inMultiplesOfDepositTermTypeId"; + public static final String DEPOSIT_AMOUNT = "depositAmount"; + public static final String MATURITY_AMOUNT = "maturityAmount"; + public static final String MATURITY_DATE = "maturityDate"; + public static final String DEPOSIT_PERIOD = "depositPeriod"; + public static final String DEPOSIT_PERIOD_FREQUENCY_TYPE_ID = "depositPeriodFrequencyTypeId"; + public static final String ON_ACCOUNT_CLOSURE_ID = "onAccountClosureId"; + public static final String TRANSFER_INTEREST_TO_SAVINGS = "transferInterestToSavings"; + public static final String TRANSFER_TO_SAVINGS_ID = "transferToSavingsId"; + private final String schemaSql; + + FixedDepositAccountMapper() { + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append(super.selectFieldsSql()); + + sqlBuilder.append(", datp.pre_closure_penal_applicable as preClosurePenalApplicable, "); + sqlBuilder.append("datp.pre_closure_penal_interest as preClosurePenalInterest, "); + sqlBuilder.append("datp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, "); + sqlBuilder.append("datp.min_deposit_term as minDepositTerm, "); + sqlBuilder.append("datp.max_deposit_term as maxDepositTerm, "); + sqlBuilder.append("datp.min_deposit_term_type_enum as minDepositTermTypeId, "); + sqlBuilder.append("datp.max_deposit_term_type_enum as maxDepositTermTypeId, "); + sqlBuilder.append("datp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, "); + sqlBuilder.append("datp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, "); + sqlBuilder.append("datp.deposit_amount as depositAmount, "); + sqlBuilder.append("datp.maturity_amount as maturityAmount, "); + sqlBuilder.append("datp.maturity_date as maturityDate, "); + sqlBuilder.append("datp.deposit_period as depositPeriod, "); + sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, "); + sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId, "); + sqlBuilder.append("datp.transfer_interest_to_linked_account as transferInterestToSavings, "); + sqlBuilder.append("datp.transfer_to_savings_account_id as transferToSavingsId, "); + sqlBuilder.append("sp.fd_type as fdType, sp.product_code as productCode, sp.effective_date as effectiveDate, "); + sqlBuilder.append("sp.effective_to as effectiveTo, sp.end_date as endDate, sp.introducer_required as introducerRequired, "); + sqlBuilder.append("sp.share_linking_enabled as shareLinkingEnabled, "); + sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, "); + sqlBuilder.append("sp.dormancy_alert as dormancyAlert, sp.is_dormancy_tracking_active as isDormancyTrackingActive, "); + sqlBuilder.append("sp.days_to_inactive as daysToInactive, sp.days_to_dormancy as daysToDormancy, "); + sqlBuilder.append("sp.days_to_escheat as daysToEscheat, sp.interest_calculate_selector as interestCalculateSelector, "); + sqlBuilder.append("icv.code_value as interestCalculationTypeName, sp.average_period as averagePeriod, "); + sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay, "); + sqlBuilder.append("sp.last_interest_date as lastInterestDate, sp.enable_lockin_period as enableLockinPeriod, "); + sqlBuilder.append("sp.balance_frequency as balanceFrequency, "); + sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, "); + sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, sp.msme as msme, "); + sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, "); + sqlBuilder.append("sp.loan_facility as loanFacility, sp.premature_withdrawal_option as prematureWithdrawalOption, "); + sqlBuilder.append("sp.tenure_flexibility as tenureFlexibility, sp.lien_marked as lienMarked, "); + sqlBuilder.append("sp.maturity_instructions as maturityInstructions, sp.credit_maturity_proceeds as creditMaturityProceeds, "); + sqlBuilder.append("sp.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, "); + sqlBuilder.append("sp.panel_request_applicable as panelRequestApplicable, "); + sqlBuilder.append("sp.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, "); + sqlBuilder.append("sp.deceased_settlement as deceasedSettlement, sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate "); + + sqlBuilder.append(super.selectTablesSql()); + + this.schemaSql = sqlBuilder.toString(); + } + + @Override + public String schema() { + return this.schemaSql; + } + + @Override + public FixedDepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final DepositAccountData depositAccountData = super.mapRow(rs); + final boolean preClosurePenalApplicable = rs.getBoolean(PRE_CLOSURE_PENAL_APPLICABLE); + final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, PRE_CLOSURE_PENAL_INTEREST); + final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, PRE_CLOSURE_PENAL_INTEREST_ON_ID); + final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null + : SavingsEnumerations.preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId); + + final Integer minDepositTerm = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM); + final Integer maxDepositTerm = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM); + final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM_TYPE_ID); + final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null + : SavingsEnumerations.depositTermFrequencyType(minDepositTermTypeId); + final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM_TYPE_ID); + final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null + : SavingsEnumerations.depositTermFrequencyType(maxDepositTermTypeId); + final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM); + final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID); + final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null + : SavingsEnumerations.depositTermFrequencyType(inMultiplesOfDepositTermTypeId); + + final BigDecimal depositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, DEPOSIT_AMOUNT); + final BigDecimal maturityAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, MATURITY_AMOUNT); + final LocalDate maturityDate = JdbcSupport.getLocalDate(rs, MATURITY_DATE); + final Integer depositPeriod = JdbcSupport.getInteger(rs, DEPOSIT_PERIOD); + final Integer depositPeriodFrequencyTypeId = JdbcSupport.getInteger(rs, DEPOSIT_PERIOD_FREQUENCY_TYPE_ID); + final EnumOptionData depositPeriodFrequencyType = (depositPeriodFrequencyTypeId == null) ? null + : SavingsEnumerations.depositPeriodFrequency(depositPeriodFrequencyTypeId); + + final Integer onAccountClosureId = JdbcSupport.getInteger(rs, ON_ACCOUNT_CLOSURE_ID); + final EnumOptionData onAccountClosureType = (onAccountClosureId == null) ? null + : SavingsEnumerations.depositAccountOnClosureType(onAccountClosureId); + final Boolean transferInterestToSavings = rs.getBoolean(TRANSFER_INTEREST_TO_SAVINGS); + + final Long transferToSavingsId = JdbcSupport.getLong(rs, TRANSFER_TO_SAVINGS_ID); + + final FixedDepositAccountData result = FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest, + preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, + inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod, + depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, transferToSavingsId); + result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("fdType"), rs.getString("productCode"), + JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"), + JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"), + rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"), + JdbcSupport.getLong(rs, "daysToInactive"), JdbcSupport.getLong(rs, "daysToDormancy"), + JdbcSupport.getLong(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"), + rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"), + JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"), + rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"), + rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"), + rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"), + rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"), + rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"), + rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"), + rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate")); + return result; + } + } + + private static class RecurringDepositAccountMapper extends DepositAccountMapper { + + public static final String PRE_CLOSURE_PENAL_APPLICABLE = "preClosurePenalApplicable"; + public static final String PRE_CLOSURE_PENAL_INTEREST = "preClosurePenalInterest"; + public static final String PRE_CLOSURE_PENAL_INTEREST_ON_ID = "preClosurePenalInterestOnId"; + public static final String MIN_DEPOSIT_TERM = "minDepositTerm"; + public static final String MAX_DEPOSIT_TERM = "maxDepositTerm"; + public static final String MIN_DEPOSIT_TERM_TYPE_ID = "minDepositTermTypeId"; + public static final String MAX_DEPOSIT_TERM_TYPE_ID = "maxDepositTermTypeId"; + public static final String IN_MULTIPLES_OF_DEPOSIT_TERM = "inMultiplesOfDepositTerm"; + public static final String IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID = "inMultiplesOfDepositTermTypeId"; + public static final String DEPOSIT_AMOUNT = "depositAmount"; + public static final String MATURITY_AMOUNT = "maturityAmount"; + public static final String MATURITY_DATE = "maturityDate"; + public static final String DEPOSIT_PERIOD = "depositPeriod"; + public static final String DEPOSIT_PERIOD_FREQUENCY_TYPE_ID = "depositPeriodFrequencyTypeId"; + public static final String MANDATORY_RECOMMENDED_DEPOSIT_AMOUNT = "mandatoryRecommendedDepositAmount"; + public static final String TOTAL_OVERDUE_AMOUNT = "totalOverdueAmount"; + public static final String NO_OF_OVERDUE_INSTALLMENTS = "noOfOverdueInstallments"; + public static final String IS_MANDATORY_DEPOSIT = "isMandatoryDeposit"; + public static final String ALLOW_WITHDRAWAL = "allowWithdrawal"; + public static final String ADJUST_ADVANCE_TOWARDS_FUTURE_PAYMENTS = "adjustAdvanceTowardsFuturePayments"; + public static final String IS_CALENDAR_INHERITED = "isCalendarInherited"; + public static final String ON_ACCOUNT_CLOSURE_ID = "onAccountClosureId"; + public static final String EXPECTED_FIRST_DEPOSIT_ON_DATE = "expectedFirstDepositOnDate"; + private final String schemaSql; + + RecurringDepositAccountMapper() { + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append(this.selectFieldsSql()); + + sqlBuilder.append(", datp.pre_closure_penal_applicable as preClosurePenalApplicable, "); + sqlBuilder.append("datp.pre_closure_penal_interest as preClosurePenalInterest, "); + sqlBuilder.append("datp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, "); + sqlBuilder.append("dard.mandatory_recommended_deposit_amount as mandatoryRecommendedDepositAmount, "); + sqlBuilder.append("dard.total_overdue_amount as totalOverdueAmount, "); + sqlBuilder.append("dard.no_of_overdue_installments as noOfOverdueInstallments, "); + sqlBuilder.append("dard.is_mandatory as isMandatoryDeposit, "); + sqlBuilder.append("dard.allow_withdrawal as allowWithdrawal, "); + sqlBuilder.append("dard.adjust_advance_towards_future_payments as adjustAdvanceTowardsFuturePayments, "); + sqlBuilder.append("dard.is_calendar_inherited as isCalendarInherited, "); + sqlBuilder.append("datp.min_deposit_term as minDepositTerm, "); + sqlBuilder.append("datp.max_deposit_term as maxDepositTerm, "); + sqlBuilder.append("datp.min_deposit_term_type_enum as minDepositTermTypeId, "); + sqlBuilder.append("datp.max_deposit_term_type_enum as maxDepositTermTypeId, "); + sqlBuilder.append("datp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, "); + sqlBuilder.append("datp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, "); + sqlBuilder.append("datp.deposit_amount as depositAmount, "); + sqlBuilder.append("datp.maturity_amount as maturityAmount, "); + sqlBuilder.append("datp.expected_firstdepositon_date as expectedFirstDepositOnDate, "); + sqlBuilder.append("datp.maturity_date as maturityDate, "); + sqlBuilder.append("datp.deposit_period as depositPeriod, "); + sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, "); + sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId, "); + sqlBuilder.append("sp.product_code as productCode, sp.effective_date as effectiveDate, "); + sqlBuilder.append("sp.effective_to as effectiveTo, sp.end_date as endDate, sp.introducer_required as introducerRequired, "); + sqlBuilder.append("sp.share_linking_enabled as shareLinkingEnabled, "); + sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, "); + sqlBuilder.append("sp.dormancy_alert as dormancyAlert, sp.is_dormancy_tracking_active as isDormancyTrackingActive, "); + sqlBuilder.append("sp.days_to_inactive as daysToInactive, sp.days_to_dormancy as daysToDormancy, "); + sqlBuilder.append("sp.days_to_escheat as daysToEscheat, sp.interest_calculate_selector as interestCalculateSelector, "); + sqlBuilder.append("icv.code_value as interestCalculationTypeName, sp.average_period as averagePeriod, "); + sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay, "); + sqlBuilder.append("sp.last_interest_date as lastInterestDate, sp.enable_lockin_period as enableLockinPeriod, "); + sqlBuilder.append("sp.balance_frequency as balanceFrequency, "); + sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, "); + sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, sp.msme as msme, "); + sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, "); + sqlBuilder.append("sp.loan_facility as loanFacility, sp.premature_withdrawal_option as prematureWithdrawalOption, "); + sqlBuilder.append("sp.tenure_flexibility as tenureFlexibility, sp.lien_marked as lienMarked, "); + sqlBuilder.append("sp.maturity_instructions as maturityInstructions, sp.credit_maturity_proceeds as creditMaturityProceeds, "); + sqlBuilder.append("sp.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, "); + sqlBuilder.append("sp.panel_request_applicable as panelRequestApplicable, "); + sqlBuilder.append("sp.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, "); + sqlBuilder.append("sp.deceased_settlement as deceasedSettlement, sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate "); + + sqlBuilder.append(this.selectTablesSql()); + sqlBuilder.append("left join m_deposit_account_recurring_detail dard on sa.id = dard.savings_account_id "); + + this.schemaSql = sqlBuilder.toString(); + } + + @Override + public String schema() { + return this.schemaSql; + } + + @Override + public RecurringDepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final DepositAccountData depositAccountData = super.mapRow(rs); + + final boolean preClosurePenalApplicable = rs.getBoolean(PRE_CLOSURE_PENAL_APPLICABLE); + final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, PRE_CLOSURE_PENAL_INTEREST); + final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, PRE_CLOSURE_PENAL_INTEREST_ON_ID); + final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null + : SavingsEnumerations.preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId); + final Integer minDepositTerm = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM); + final Integer maxDepositTerm = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM); + final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM_TYPE_ID); + final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null + : SavingsEnumerations.depositTermFrequencyType(minDepositTermTypeId); + final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM_TYPE_ID); + final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null + : SavingsEnumerations.depositTermFrequencyType(maxDepositTermTypeId); + final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM); + final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID); + final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null + : SavingsEnumerations.depositTermFrequencyType(inMultiplesOfDepositTermTypeId); + + final BigDecimal depositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, DEPOSIT_AMOUNT); + final BigDecimal maturityAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, MATURITY_AMOUNT); + final LocalDate maturityDate = JdbcSupport.getLocalDate(rs, MATURITY_DATE); + final Integer depositPeriod = JdbcSupport.getInteger(rs, DEPOSIT_PERIOD); + final Integer depositPeriodFrequencyTypeId = JdbcSupport.getInteger(rs, DEPOSIT_PERIOD_FREQUENCY_TYPE_ID); + final EnumOptionData depositPeriodFrequencyType = (depositPeriodFrequencyTypeId == null) ? null + : SavingsEnumerations.depositPeriodFrequency(depositPeriodFrequencyTypeId); + final BigDecimal mandatoryRecommendedDepositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + MANDATORY_RECOMMENDED_DEPOSIT_AMOUNT); + final BigDecimal totalOverdueAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, TOTAL_OVERDUE_AMOUNT); + final Integer noOfOverdueInstallments = JdbcSupport.getInteger(rs, NO_OF_OVERDUE_INSTALLMENTS); + final boolean isMandatoryDeposit = rs.getBoolean(IS_MANDATORY_DEPOSIT); + final boolean allowWithdrawal = rs.getBoolean(ALLOW_WITHDRAWAL); + final boolean adjustAdvanceTowardsFuturePayments = rs.getBoolean(ADJUST_ADVANCE_TOWARDS_FUTURE_PAYMENTS); + final boolean isCalendarInherited = rs.getBoolean(IS_CALENDAR_INHERITED); + + final Integer onAccountClosureId = JdbcSupport.getInteger(rs, ON_ACCOUNT_CLOSURE_ID); + final EnumOptionData onAccountClosureType = (onAccountClosureId == null) ? null + : SavingsEnumerations.depositAccountOnClosureType(onAccountClosureId); + final LocalDate expectedFirstDepositOnDate = JdbcSupport.getLocalDate(rs, EXPECTED_FIRST_DEPOSIT_ON_DATE); + + final RecurringDepositAccountData result = RecurringDepositAccountData.instance(depositAccountData, + preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, + minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, + maturityAmount, maturityDate, depositPeriod, depositPeriodFrequencyType, mandatoryRecommendedDepositAmount, + onAccountClosureType, expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, + allowWithdrawal, adjustAdvanceTowardsFuturePayments, isCalendarInherited); + + result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("productCode"), + JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"), + JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"), + rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"), + JdbcSupport.getInteger(rs, "daysToInactive"), JdbcSupport.getInteger(rs, "daysToDormancy"), + JdbcSupport.getInteger(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"), + rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"), + JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"), + rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"), + rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"), + rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"), + rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"), + rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"), + rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"), + rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate")); + + return result; + + } + } + + private static final class DepositAccountLookupMapper implements RowMapper { + + public static final String ID = "id"; + public static final String ACCOUNT_NUMBER = "accountNumber"; + public static final String DEPOSIT_TYPE_ID = "depositTypeId"; + + public String schema() { + return " sa.id as id, sa.account_no as accountNumber, sa.deposit_type_enum as depositTypeId from m_savings_account sa "; + } + + @Override + public DepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = rs.getLong(ID); + final String name = rs.getString(ACCOUNT_NUMBER); + final Integer depositTypeId = JdbcSupport.getInteger(rs, DEPOSIT_TYPE_ID); + final EnumOptionData depositType = (depositTypeId == null) ? null : SavingsEnumerations.depositType(depositTypeId); + + return DepositAccountData.lookup(id, name, depositType); + } + } + + private static final class SavingsAccountTransactionsMapper implements RowMapper { + + public static final String TRANSACTION_ID = "transactionId"; + public static final String TRANSACTION_TYPE = "transactionType"; + public static final String TRANSACTION_DATE = "transactionDate"; + public static final String TRANSACTION_AMOUNT = "transactionAmount"; + public static final String RUNNING_BALANCE = "runningBalance"; + public static final String REVERSED = "reversed"; + public static final String SAVINGS_ID = "savingsId"; + public static final String ACCOUNT_NO = "accountNo"; + public static final String PAYMENT_TYPE = "paymentType"; + public static final String PAYMENT_TYPE_NAME = "paymentTypeName"; + public static final String ACCOUNT_NUMBER = "accountNumber"; + public static final String CHECK_NUMBER = "checkNumber"; + public static final String ROUTING_CODE = "routingCode"; + public static final String RECEIPT_NUMBER = "receiptNumber"; + public static final String BANK_NUMBER = "bankNumber"; + public static final String CURRENCY_CODE = "currencyCode"; + public static final String CURRENCY_NAME = "currencyName"; + public static final String CURRENCY_NAME_CODE = "currencyNameCode"; + public static final String CURRENCY_DISPLAY_SYMBOL = "currencyDisplaySymbol"; + public static final String CURRENCY_DIGITS = "currencyDigits"; + public static final String IN_MULTIPLES_OF = "inMultiplesOf"; + public static final String FROM_TRANSFER_ID = "fromTransferId"; + public static final String TO_TRANSFER_ID = "toTransferId"; + public static final String FROM_TRANSFER_DATE = "fromTransferDate"; + public static final String FROM_TRANSFER_AMOUNT = "fromTransferAmount"; + public static final String FROM_TRANSFER_REVERSED = "fromTransferReversed"; + public static final String FROM_TRANSFER_DESCRIPTION = "fromTransferDescription"; + public static final String TO_TRANSFER_DATE = "toTransferDate"; + public static final String TO_TRANSFER_AMOUNT = "toTransferAmount"; + public static final String TO_TRANSFER_REVERSED = "toTransferReversed"; + public static final String TO_TRANSFER_DESCRIPTION = "toTransferDescription"; + public static final String SUBMITTED_BY_USERNAME = "submittedByUsername"; + public static final String SUBMITTED_ON_DATE = "submittedOnDate"; + private final String schemaSql; + + SavingsAccountTransactionsMapper() { + + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, "); + sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,"); + sqlBuilder.append("tr.running_balance_derived as runningBalance, tr.is_reversed as reversed,"); + sqlBuilder.append("tr.submitted_on_date as submittedOnDate,"); + sqlBuilder.append("fromtran.id as fromTransferId, fromtran.is_reversed as fromTransferReversed,"); + sqlBuilder.append("fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount,"); + sqlBuilder.append("fromtran.description as fromTransferDescription,"); + sqlBuilder.append("totran.id as toTransferId, totran.is_reversed as toTransferReversed,"); + sqlBuilder.append("totran.transaction_date as toTransferDate, totran.amount as toTransferAmount,"); + sqlBuilder.append("totran.description as toTransferDescription,"); + sqlBuilder.append("sa.id as savingsId, sa.account_no as accountNo,"); + sqlBuilder.append(" au.username as submittedByUsername, "); + sqlBuilder.append("pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, "); + sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, "); + sqlBuilder.append( + "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, 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("pt.value as paymentTypeName "); + sqlBuilder.append("from m_savings_account sa "); + sqlBuilder.append("join m_savings_account_transaction tr on tr.savings_account_id = sa.id "); + sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); + sqlBuilder.append("left join m_account_transfer_transaction fromtran on fromtran.from_savings_transaction_id = tr.id "); + sqlBuilder.append("left join m_account_transfer_transaction totran on totran.to_savings_transaction_id = tr.id "); + sqlBuilder.append("left join m_payment_detail pd on tr.payment_detail_id = pd.id "); + sqlBuilder.append("left join m_payment_type pt on pd.payment_type_id = pt.id "); + sqlBuilder.append("left join m_appuser au on au.id = tr." + CREATED_BY_DB_FIELD); + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + final Long id = rs.getLong(TRANSACTION_ID); + final int transactionTypeInt = JdbcSupport.getInteger(rs, TRANSACTION_TYPE); + final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(transactionTypeInt); + + final LocalDate date = JdbcSupport.getLocalDate(rs, TRANSACTION_DATE); + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, SUBMITTED_ON_DATE); + final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, TRANSACTION_AMOUNT); + final BigDecimal outstandingChargeAmount = null; + final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, RUNNING_BALANCE); + final boolean reversed = rs.getBoolean(REVERSED); + + final Long savingsId = rs.getLong(SAVINGS_ID); + final String accountNo = rs.getString(ACCOUNT_NO); + + PaymentDetailData paymentDetailData = null; + if (transactionType.isDepositOrWithdrawal()) { + final Long paymentTypeId = JdbcSupport.getLong(rs, PAYMENT_TYPE); + if (paymentTypeId != null) { + final String typeName = rs.getString(PAYMENT_TYPE_NAME); + final PaymentTypeData paymentType = PaymentTypeData.builder().id(paymentTypeId).name(typeName).build(); + final String accountNumber = rs.getString(ACCOUNT_NUMBER); + final String checkNumber = rs.getString(CHECK_NUMBER); + final String routingCode = rs.getString(ROUTING_CODE); + final String receiptNumber = rs.getString(RECEIPT_NUMBER); + final String bankNumber = rs.getString(BANK_NUMBER); + paymentDetailData = new PaymentDetailData(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber, + bankNumber); + } + } + + final String currencyCode = rs.getString(CURRENCY_CODE); + final String currencyName = rs.getString(CURRENCY_NAME); + final String currencyNameCode = rs.getString(CURRENCY_NAME_CODE); + final String currencyDisplaySymbol = rs.getString(CURRENCY_DISPLAY_SYMBOL); + final Integer currencyDigits = JdbcSupport.getInteger(rs, CURRENCY_DIGITS); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, + currencyNameCode); + + AccountTransferData transfer = null; + final Long fromTransferId = JdbcSupport.getLong(rs, FROM_TRANSFER_ID); + final Long toTransferId = JdbcSupport.getLong(rs, TO_TRANSFER_ID); + if (fromTransferId != null) { + final LocalDate fromTransferDate = JdbcSupport.getLocalDate(rs, FROM_TRANSFER_DATE); + final BigDecimal fromTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, FROM_TRANSFER_AMOUNT); + final boolean fromTransferReversed = rs.getBoolean(FROM_TRANSFER_REVERSED); + final String fromTransferDescription = rs.getString(FROM_TRANSFER_DESCRIPTION); + + transfer = AccountTransferData.transferBasicDetails(fromTransferId, currency, fromTransferAmount, fromTransferDate, + fromTransferDescription, fromTransferReversed); + } else if (toTransferId != null) { + final LocalDate toTransferDate = JdbcSupport.getLocalDate(rs, TO_TRANSFER_DATE); + final BigDecimal toTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, TO_TRANSFER_AMOUNT); + final boolean toTransferReversed = rs.getBoolean(TO_TRANSFER_REVERSED); + final String toTransferDescription = rs.getString(TO_TRANSFER_DESCRIPTION); + + transfer = AccountTransferData.transferBasicDetails(toTransferId, currency, toTransferAmount, toTransferDate, + toTransferDescription, toTransferReversed); + } + final boolean postInterestAsOn = false; + final String submittedByUsername = rs.getString(SUBMITTED_BY_USERNAME); + final String note = null; + return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency, + amount, outstandingChargeAmount, runningBalance, reversed, transfer, postInterestAsOn, submittedByUsername, note, + submittedOnDate); + } + } + + private abstract static class DepositAccountTemplateMapper implements RowMapper { + + public static final String DEPOSIT_TYPE_ID = "depositTypeId"; + public static final String PRODUCT_ID = "productId"; + public static final String PRODUCT_NAME = "productName"; + public static final String CURRENCY_CODE = "currencyCode"; + public static final String CURRENCY_NAME = "currencyName"; + public static final String CURRENCY_NAME_CODE = "currencyNameCode"; + public static final String CURRENCY_DISPLAY_SYMBOL = "currencyDisplaySymbol"; + public static final String CURRENCY_DIGITS = "currencyDigits"; + public static final String IN_MULTIPLES_OF = "inMultiplesOf"; + public static final String NOMINAL_ANNUAL_ITEREST_RATE = "nominalAnnualIterestRate"; + public static final String INTEREST_COMPOUNDING_PERIOD_TYPE = "interestCompoundingPeriodType"; + public static final String INTEREST_POSTING_PERIOD_TYPE = "interestPostingPeriodType"; + public static final String INTEREST_CALCULATION_TYPE = "interestCalculationType"; + public static final String INTEREST_CALCULATION_DAYS_IN_YEAR_TYPE = "interestCalculationDaysInYearType"; + public static final String MIN_REQUIRED_OPENING_BALANCE = "minRequiredOpeningBalance"; + public static final String LOCKIN_PERIOD_FREQUENCY = "lockinPeriodFrequency"; + public static final String LOCKIN_PERIOD_FREQUENCY_TYPE = "lockinPeriodFrequencyType"; + public static final String WITHDRAWAL_FEE_FOR_TRANSFERS = "withdrawalFeeForTransfers"; + public static final String MIN_BALANCE_FOR_INTEREST_CALCULATION = "minBalanceForInterestCalculation"; + public static final String WITH_HOLD_TAX = "withHoldTax"; + public static final String TAX_GROUP_ID = "taxGroupId"; + public static final String TAX_GROUP_NAME = "taxGroupName"; + private final String selectFieldsSql; + private final String selectTablesSql; + + private final ClientData client; + private final GroupGeneralData group; + + protected DepositAccountTemplateMapper(final ClientData client, final GroupGeneralData group) { + this.client = client; + this.group = group; + + final StringBuilder selectFieldsSqlBuilder = new StringBuilder(400); + selectFieldsSqlBuilder.append("sa.id as productId, sa.name as productName, "); + selectFieldsSqlBuilder.append( + "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); + selectFieldsSqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); + selectFieldsSqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); + selectFieldsSqlBuilder.append("sa.nominal_annual_interest_rate as nominalAnnualIterestRate, "); + selectFieldsSqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, "); + selectFieldsSqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, "); + selectFieldsSqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, "); + selectFieldsSqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, "); + selectFieldsSqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, "); + selectFieldsSqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,"); + selectFieldsSqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); + selectFieldsSqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); + selectFieldsSqlBuilder.append("sa.deposit_type_enum as depositTypeId, "); + selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation, "); + selectFieldsSqlBuilder.append("sa.withhold_tax as withHoldTax,"); + selectFieldsSqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName "); + + this.selectFieldsSql = selectFieldsSqlBuilder.toString(); + + final StringBuilder selectTablesSqlBuilder = new StringBuilder(400); + selectTablesSqlBuilder.append("from m_savings_product sa "); + selectTablesSqlBuilder.append("left join m_deposit_product_term_and_preclosure dptp on sa.id = dptp.savings_product_id "); + selectTablesSqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); + selectTablesSqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id "); + + this.selectTablesSql = selectTablesSqlBuilder.toString(); + } + + public String selectFieldsSql() { + return this.selectFieldsSql; + } + + public String selectTablesSql() { + return this.selectTablesSql; + } + + public abstract String schema(); + + public DepositAccountData mapRow(final ResultSet rs) throws SQLException { + + final Integer depositTypeId = JdbcSupport.getInteger(rs, DEPOSIT_TYPE_ID); + final EnumOptionData depositType = (depositTypeId == null) ? null : SavingsEnumerations.depositType(depositTypeId); + final Long productId = rs.getLong(PRODUCT_ID); + final String productName = rs.getString(PRODUCT_NAME); + + final String currencyCode = rs.getString(CURRENCY_CODE); + final String currencyName = rs.getString(CURRENCY_NAME); + final String currencyNameCode = rs.getString(CURRENCY_NAME_CODE); + final String currencyDisplaySymbol = rs.getString(CURRENCY_DISPLAY_SYMBOL); + final Integer currencyDigits = JdbcSupport.getInteger(rs, CURRENCY_DIGITS); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, + currencyNameCode); + + final BigDecimal nominalAnnualIterestRate = rs.getBigDecimal(NOMINAL_ANNUAL_ITEREST_RATE); + + final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations.compoundingInterestPeriodType( + SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, INTEREST_COMPOUNDING_PERIOD_TYPE))); + + final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType( + SavingsPostingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, INTEREST_POSTING_PERIOD_TYPE))); + + final EnumOptionData interestCalculationType = SavingsEnumerations + .interestCalculationType(SavingsInterestCalculationType.fromInt(JdbcSupport.getInteger(rs, INTEREST_CALCULATION_TYPE))); + + final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations.interestCalculationDaysInYearType( + SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs, INTEREST_CALCULATION_DAYS_IN_YEAR_TYPE))); + + final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, MIN_REQUIRED_OPENING_BALANCE); + + final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, LOCKIN_PERIOD_FREQUENCY); + EnumOptionData lockinPeriodFrequencyType = null; + final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, LOCKIN_PERIOD_FREQUENCY_TYPE); + if (lockinPeriodFrequencyTypeValue != null) { + final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); + lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType); + } + + final boolean withdrawalFeeForTransfers = rs.getBoolean(WITHDRAWAL_FEE_FOR_TRANSFERS); + final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + MIN_BALANCE_FOR_INTEREST_CALCULATION); + + Long clientId = null; + String clientName = null; + if (this.client != null) { + clientId = this.client.getId(); + clientName = this.client.getDisplayName(); + } + + Long groupId = null; + String groupName = null; + if (this.group != null) { + groupId = this.group.getId(); + groupName = this.group.getName(); + } + + final Long fieldOfficerId = null; + final String fieldOfficerName = null; + final SavingsAccountStatusEnumData status = null; + final SavingsAccountSummaryData summary = null; + final SavingsAccountApplicationTimelineData timeline = SavingsAccountApplicationTimelineData.templateDefault(); + + final boolean withHoldTax = rs.getBoolean(WITH_HOLD_TAX); + final Long taxGroupId = JdbcSupport.getLong(rs, TAX_GROUP_ID); + final String taxGroupName = rs.getString(TAX_GROUP_NAME); + TaxGroupData taxGroupData = null; + if (taxGroupId != null) { + taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); + } + + return DepositAccountData.instance(null, null, null, groupId, groupName, clientId, clientName, productId, productName, + fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualIterestRate, interestCompoundingPeriodType, + interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, + lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, depositType, + minBalanceForInterestCalculation, withHoldTax, taxGroupData); + } + } + + private static class FixedDepositAccountTemplateMapper extends DepositAccountTemplateMapper { + + public static final String PRE_CLOSURE_PENAL_APPLICABLE = "preClosurePenalApplicable"; + public static final String PRE_CLOSURE_PENAL_INTEREST = "preClosurePenalInterest"; + public static final String PRE_CLOSURE_PENAL_INTEREST_ON_ID = "preClosurePenalInterestOnId"; + public static final String MIN_DEPOSIT_TERM = "minDepositTerm"; + public static final String MAX_DEPOSIT_TERM = "maxDepositTerm"; + public static final String MIN_DEPOSIT_TERM_TYPE_ID = "minDepositTermTypeId"; + public static final String MAX_DEPOSIT_TERM_TYPE_ID = "maxDepositTermTypeId"; + public static final String IN_MULTIPLES_OF_DEPOSIT_TERM = "inMultiplesOfDepositTerm"; + public static final String IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID = "inMultiplesOfDepositTermTypeId"; + private final String schemaSql; + + FixedDepositAccountTemplateMapper(final ClientData client, final GroupGeneralData group) { + super(client, group); + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append(super.selectFieldsSql()); + + sqlBuilder.append(", dptp.pre_closure_penal_applicable as preClosurePenalApplicable, "); + sqlBuilder.append("dptp.pre_closure_penal_interest as preClosurePenalInterest, "); + sqlBuilder.append("dptp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, "); + sqlBuilder.append("sa.account_type as accountType, sa.customer_segment as customerSegment, sa.fd_type as fdType, "); + sqlBuilder.append("sa.product_code as productCode, "); + sqlBuilder.append("sa.effective_date as effectiveDate, sa.effective_to as effectiveTo, sa.end_date as endDate, "); + sqlBuilder.append("sa.introducer_required as introducerRequired, sa.share_linking_enabled as shareLinkingEnabled, "); + sqlBuilder.append("sa.nomination_facility as nominationFacility, sa.dormancy_alert as dormancyAlert, "); + sqlBuilder.append("sa.is_dormancy_tracking_active as isDormancyTrackingActive, "); + sqlBuilder.append("sa.days_to_inactive as daysToInactive, sa.days_to_dormancy as daysToDormancy, "); + sqlBuilder.append("sa.days_to_escheat as daysToEscheat, "); + sqlBuilder.append("sa.interest_calculate_selector as interestCalculateSelector, "); + sqlBuilder.append("icv.code_value as interestCalculationTypeName, "); + sqlBuilder.append("sa.average_period as averagePeriod, "); + sqlBuilder.append("sa.auto_post as autoPost, sa.frequency_fix_day as frequencyFixDay, "); + sqlBuilder.append("sa.last_interest_date as lastInterestDate, sa.enable_lockin_period as enableLockinPeriod, "); + sqlBuilder.append("sa.balance_frequency as balanceFrequency, "); + sqlBuilder.append("sa.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, "); + sqlBuilder.append("sa.balance_swap_option as balanceSwapOption, sa.msme as msme, "); + sqlBuilder.append("sa.auto_debit_facility as autoDebitFacility, sa.loan_facility as loanFacility, "); + sqlBuilder.append("sa.premature_withdrawal_option as prematureWithdrawalOption, sa.tenure_flexibility as tenureFlexibility, "); + sqlBuilder.append("sa.lien_marked as lienMarked, sa.maturity_instructions as maturityInstructions, "); + sqlBuilder.append("sa.credit_maturity_proceeds as creditMaturityProceeds, "); + sqlBuilder.append("sa.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, "); + sqlBuilder.append("sa.panel_request_applicable as panelRequestApplicable, "); + sqlBuilder.append("sa.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, "); + sqlBuilder.append("sa.deceased_settlement as deceasedSettlement, "); + sqlBuilder.append("sa.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate, "); + sqlBuilder.append("dptp.min_deposit_term as minDepositTerm, "); + sqlBuilder.append("dptp.max_deposit_term as maxDepositTerm, "); + sqlBuilder.append("dptp.min_deposit_term_type_enum as minDepositTermTypeId, "); + sqlBuilder.append("dptp.max_deposit_term_type_enum as maxDepositTermTypeId, "); + sqlBuilder.append("dptp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, "); + sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId "); + + sqlBuilder.append(super.selectTablesSql()); + sqlBuilder.append("left join m_code_value icv on icv.id = sa.interest_calculate_selector "); + + this.schemaSql = sqlBuilder.toString(); + } + + @Override + public String schema() { + return this.schemaSql; + } + + @Override + public FixedDepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final DepositAccountData depositAccountData = super.mapRow(rs); + + final boolean preClosurePenalApplicable = rs.getBoolean(PRE_CLOSURE_PENAL_APPLICABLE); + final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, PRE_CLOSURE_PENAL_INTEREST); + final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, PRE_CLOSURE_PENAL_INTEREST_ON_ID); + final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null + : SavingsEnumerations.preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId); + + final Integer minDepositTerm = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM); + final Integer maxDepositTerm = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM); + final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM_TYPE_ID); + final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null + : SavingsEnumerations.depositTermFrequencyType(minDepositTermTypeId); + final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM_TYPE_ID); + final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null + : SavingsEnumerations.depositTermFrequencyType(maxDepositTermTypeId); + final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM); + final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID); + final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null + : SavingsEnumerations.depositTermFrequencyType(inMultiplesOfDepositTermTypeId); + + final BigDecimal depositAmount = null; + final BigDecimal maturityAmount = null; + final LocalDate maturityDate = null; + final Integer depositPeriod = null; + final EnumOptionData depositPeriodFrequencyType = null; + final EnumOptionData onAccountClosureType = null; + final Boolean transferInterestToSavings = false; + + final FixedDepositAccountData result = FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, + preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, + maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, + maturityDate, depositPeriod, depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, null); + result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("fdType"), rs.getString("productCode"), + JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"), + JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"), + rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"), + JdbcSupport.getLong(rs, "daysToInactive"), JdbcSupport.getLong(rs, "daysToDormancy"), + JdbcSupport.getLong(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"), + rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"), + JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"), + rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"), + rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"), + rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"), + rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"), + rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"), + rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"), + rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate")); + return result; + } + } + + private static class RecurringDepositAccountTemplateMapper extends DepositAccountTemplateMapper { + + public static final String PRE_CLOSURE_PENAL_APPLICABLE = "preClosurePenalApplicable"; + public static final String PRE_CLOSURE_PENAL_INTEREST = "preClosurePenalInterest"; + public static final String PRE_CLOSURE_PENAL_INTEREST_ON_ID = "preClosurePenalInterestOnId"; + public static final String MIN_DEPOSIT_TERM = "minDepositTerm"; + public static final String MAX_DEPOSIT_TERM = "maxDepositTerm"; + public static final String MIN_DEPOSIT_TERM_TYPE_ID = "minDepositTermTypeId"; + public static final String MAX_DEPOSIT_TERM_TYPE_ID = "maxDepositTermTypeId"; + public static final String IN_MULTIPLES_OF_DEPOSIT_TERM = "inMultiplesOfDepositTerm"; + public static final String IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID = "inMultiplesOfDepositTermTypeId"; + public static final String IS_MANDATORY_DEPOSIT = "isMandatoryDeposit"; + public static final String ALLOW_WITHDRAWAL = "allowWithdrawal"; + public static final String ADJUST_ADVANCE_TOWARDS_FUTURE_PAYMENTS = "adjustAdvanceTowardsFuturePayments"; + private final String schemaSql; + + RecurringDepositAccountTemplateMapper(final ClientData client, final GroupGeneralData group) { + super(client, group); + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append(this.selectFieldsSql()); + + sqlBuilder.append(", dptp.pre_closure_penal_applicable as preClosurePenalApplicable, "); + sqlBuilder.append("dptp.pre_closure_penal_interest as preClosurePenalInterest, "); + sqlBuilder.append("dptp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, "); + sqlBuilder.append("dprd.is_mandatory as isMandatoryDeposit, "); + sqlBuilder.append("dprd.allow_withdrawal as allowWithdrawal, "); + sqlBuilder.append("dprd.adjust_advance_towards_future_payments as adjustAdvanceTowardsFuturePayments, "); + sqlBuilder.append("dptp.min_deposit_term as minDepositTerm, "); + sqlBuilder.append("dptp.max_deposit_term as maxDepositTerm, "); + sqlBuilder.append("dptp.min_deposit_term_type_enum as minDepositTermTypeId, "); + sqlBuilder.append("dptp.max_deposit_term_type_enum as maxDepositTermTypeId, "); + sqlBuilder.append("dptp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, "); + sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, "); + sqlBuilder.append("sa.account_type as accountType, sa.customer_segment as customerSegment, "); + sqlBuilder.append("sa.product_code as productCode, "); + sqlBuilder.append("sa.effective_date as effectiveDate, sa.effective_to as effectiveTo, sa.end_date as endDate, "); + sqlBuilder.append("sa.introducer_required as introducerRequired, sa.share_linking_enabled as shareLinkingEnabled, "); + sqlBuilder.append("sa.nomination_facility as nominationFacility, sa.dormancy_alert as dormancyAlert, "); + sqlBuilder.append("sa.is_dormancy_tracking_active as isDormancyTrackingActive, "); + sqlBuilder.append("sa.days_to_inactive as daysToInactive, "); + sqlBuilder.append("sa.days_to_dormancy as daysToDormancy, "); + sqlBuilder.append("sa.days_to_escheat as daysToEscheat, "); + sqlBuilder.append("sa.interest_calculate_selector as interestCalculateSelector, "); + sqlBuilder.append("icv.code_value as interestCalculationTypeName, "); + sqlBuilder.append("sa.average_period as averagePeriod, "); + sqlBuilder.append("sa.auto_post as autoPost, sa.frequency_fix_day as frequencyFixDay, "); + sqlBuilder.append("sa.last_interest_date as lastInterestDate, sa.enable_lockin_period as enableLockinPeriod, "); + sqlBuilder.append("sa.balance_frequency as balanceFrequency, "); + sqlBuilder.append("sa.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, "); + sqlBuilder.append("sa.balance_swap_option as balanceSwapOption, sa.msme as msme, "); + sqlBuilder.append("sa.auto_debit_facility as autoDebitFacility, sa.loan_facility as loanFacility, "); + sqlBuilder.append("sa.premature_withdrawal_option as prematureWithdrawalOption, sa.tenure_flexibility as tenureFlexibility, "); + sqlBuilder.append("sa.lien_marked as lienMarked, sa.maturity_instructions as maturityInstructions, "); + sqlBuilder.append("sa.credit_maturity_proceeds as creditMaturityProceeds, "); + sqlBuilder.append("sa.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, "); + sqlBuilder.append("sa.panel_request_applicable as panelRequestApplicable, "); + sqlBuilder.append("sa.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, "); + sqlBuilder.append("sa.deceased_settlement as deceasedSettlement, "); + sqlBuilder.append("sa.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate "); + + sqlBuilder.append(this.selectTablesSql()); + sqlBuilder.append("left join m_deposit_product_recurring_detail dprd on sa.id = dprd.savings_product_id "); + sqlBuilder.append("left join m_code_value icv on icv.id = sa.interest_calculate_selector "); + + this.schemaSql = sqlBuilder.toString(); + } + + @Override + public String schema() { + return this.schemaSql; + } + + @Override + public RecurringDepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final DepositAccountData depositAccountData = super.mapRow(rs); + + final boolean preClosurePenalApplicable = rs.getBoolean(PRE_CLOSURE_PENAL_APPLICABLE); + final BigDecimal preClosurePenalInterest = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, PRE_CLOSURE_PENAL_INTEREST); + final Integer preClosurePenalInterestOnTypeId = JdbcSupport.getInteger(rs, PRE_CLOSURE_PENAL_INTEREST_ON_ID); + final EnumOptionData preClosurePenalInterestOnType = (preClosurePenalInterestOnTypeId == null) ? null + : SavingsEnumerations.preClosurePenaltyInterestOnType(preClosurePenalInterestOnTypeId); + + final Integer minDepositTerm = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM); + final Integer maxDepositTerm = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM); + final Integer minDepositTermTypeId = JdbcSupport.getInteger(rs, MIN_DEPOSIT_TERM_TYPE_ID); + final EnumOptionData minDepositTermType = (minDepositTermTypeId == null) ? null + : SavingsEnumerations.depositTermFrequencyType(minDepositTermTypeId); + final Integer maxDepositTermTypeId = JdbcSupport.getInteger(rs, MAX_DEPOSIT_TERM_TYPE_ID); + final EnumOptionData maxDepositTermType = (maxDepositTermTypeId == null) ? null + : SavingsEnumerations.depositTermFrequencyType(maxDepositTermTypeId); + final Integer inMultiplesOfDepositTerm = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM); + final Integer inMultiplesOfDepositTermTypeId = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF_DEPOSIT_TERM_TYPE_ID); + final EnumOptionData inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null + : SavingsEnumerations.depositTermFrequencyType(inMultiplesOfDepositTermTypeId); + final boolean isMandatoryDeposit = rs.getBoolean(IS_MANDATORY_DEPOSIT); + final boolean allowWithdrawal = rs.getBoolean(ALLOW_WITHDRAWAL); + final boolean adjustAdvanceTowardsFuturePayments = rs.getBoolean(ADJUST_ADVANCE_TOWARDS_FUTURE_PAYMENTS); + final boolean isCalendarInherited = false; + + final BigDecimal depositAmount = null; + final BigDecimal maturityAmount = null; + final LocalDate maturityDate = null; + final Integer depositPeriod = null; + final EnumOptionData depositPeriodFrequencyType = null; + final LocalDate expectedFirstDepositOnDate = null; + final BigDecimal mandatoryRecommendedDepositAmount = null; + final EnumOptionData onAccountClosureType = null; + final BigDecimal totalOverdueAmount = null; + final Integer noOfOverdueInstallments = null; + + final RecurringDepositAccountData result = RecurringDepositAccountData.instance(depositAccountData, + preClosurePenalApplicable, preClosurePenalInterest, + preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, + inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod, + depositPeriodFrequencyType, mandatoryRecommendedDepositAmount, onAccountClosureType, expectedFirstDepositOnDate, + totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal, adjustAdvanceTowardsFuturePayments, + isCalendarInherited); + + // CBS product fields + result.setCbsProductFields( + rs.getString("accountType"), + rs.getString("customerSegment"), + rs.getString("productCode"), + JdbcSupport.getLocalDate(rs, "effectiveDate"), + JdbcSupport.getLocalDate(rs, "effectiveTo"), + JdbcSupport.getLocalDate(rs, "endDate"), + rs.getBoolean("introducerRequired"), + rs.getBoolean("shareLinkingEnabled"), + rs.getBoolean("nominationFacility"), + rs.getBoolean("dormancyAlert"), + rs.getBoolean("isDormancyTrackingActive"), + JdbcSupport.getInteger(rs, "daysToInactive"), + JdbcSupport.getInteger(rs, "daysToDormancy"), + JdbcSupport.getInteger(rs, "daysToEscheat"), + JdbcSupport.getInteger(rs, "interestCalculateSelector"), + rs.getString("interestCalculationTypeName"), + rs.getString("averagePeriod"), + rs.getBoolean("autoPost"), + JdbcSupport.getInteger(rs, "frequencyFixDay"), + JdbcSupport.getLocalDate(rs, "lastInterestDate"), + rs.getBoolean("enableLockinPeriod"), + rs.getString("balanceFrequency"), + rs.getBoolean("accountClosedZeroBalanceInDayClosing"), + rs.getBoolean("balanceSwapOption"), + rs.getBoolean("msme"), + rs.getBoolean("autoDebitFacility"), + rs.getBoolean("loanFacility"), + rs.getBoolean("prematureWithdrawalOption"), + rs.getBoolean("tenureFlexibility"), + rs.getBoolean("lienMarked"), + rs.getBoolean("maturityInstructions"), + rs.getBoolean("creditMaturityProceeds"), + rs.getBoolean("penaltyGracePeriodMissedInstallments"), + rs.getBoolean("panelRequestApplicable"), + rs.getBoolean("noPenaltyNomineeHeirClosure"), + rs.getBoolean("deceasedSettlement"), + rs.getBoolean("allowDebitTxnAfterDueDate") + ); + + return result; + } + } + + private static final class DepositAccountForMaturityMapper implements RowMapper { + + public static final String ID = "id"; + public static final String ACCOUNT_NUMBER = "accountNumber"; + public static final String DEPOSIT_TYPE_ID = "depositTypeId"; + + public String schema() { + LocalDate today = DateUtils.getBusinessLocalDate(); + String formattedToday = DATE_TIME_FORMATTER.format(today); + final StringBuilder sqlBuilder = new StringBuilder(200); + sqlBuilder.append("da.id as id, "); + sqlBuilder.append("da.account_no as accountNumber, "); + sqlBuilder.append("da.deposit_type_enum as depositTypeId "); + sqlBuilder.append("FROM m_savings_account da "); + sqlBuilder.append("inner join m_deposit_account_term_and_preclosure dat on dat.savings_account_id = da.id "); + sqlBuilder.append("and dat.maturity_date is not null and dat.maturity_date <= '" + formattedToday + "' "); + + return sqlBuilder.toString(); + } + + @Override + public DepositAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = rs.getLong(ID); + final String name = rs.getString(ACCOUNT_NUMBER); + final Integer depositTypeId = JdbcSupport.getInteger(rs, DEPOSIT_TYPE_ID); + final EnumOptionData depositType = (depositTypeId == null) ? null : SavingsEnumerations.depositType(depositTypeId); + + return DepositAccountData.lookup(id, name, depositType); + } + } + + private static final class RecurringAccountDepositTransactionTemplateMapper implements RowMapper { + + public static final String ID = "id"; + public static final String ACCOUNT_NO = "accountNo"; + public static final String DUEAMOUNT = "dueamount"; + public static final String OUTSTANDING_CHARGE_AMOUNT = "outstandingChargeAmount"; + public static final String DUEDATE = "duedate"; + public static final String CURRENCY_CODE = "currencyCode"; + public static final String CURRENCY_NAME = "currencyName"; + public static final String CURRENCY_NAME_CODE = "currencyNameCode"; + public static final String CURRENCY_DISPLAY_SYMBOL = "currencyDisplaySymbol"; + public static final String CURRENCY_DIGITS = "currencyDigits"; + public static final String IN_MULTIPLES_OF = "inMultiplesOf"; + public static final String RUNNING_BALANCE = "runningBalance"; + private final String schemaSql; + + RecurringAccountDepositTransactionTemplateMapper() { + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("sa.id as id, sa.account_no as accountNo, "); + sqlBuilder.append( + "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, 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("sa.account_balance_derived as runningBalance, "); + sqlBuilder.append( + "mss.duedate as duedate, (mss.deposit_amount - coalesce(mss.deposit_amount_completed_derived,0)) as dueamount, "); + sqlBuilder.append("coalesce(sac.amount_outstanding_derived,0.0) AS outstandingChargeAmount "); + sqlBuilder.append("from m_savings_account sa "); + sqlBuilder.append("join m_mandatory_savings_schedule mss on mss.savings_account_id=sa.id and mss.completed_derived = false "); + sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); + sqlBuilder.append("LEFT JOIN(SELECT s.savings_account_id AS savings_account_id "); + sqlBuilder.append(",SUM(COALESCE(s.amount_outstanding_derived,0.0)) AS amount_outstanding_derived "); + sqlBuilder.append("FROM m_savings_account_charge s "); + sqlBuilder.append("JOIN m_charge c ON c.id = s.charge_id AND c.charge_time_enum = 3 "); + sqlBuilder.append("WHERE s.savings_account_id = ? "); + sqlBuilder.append("AND s.is_active = TRUE GROUP BY s.savings_account_id)sac ON sac.savings_account_id = sa.id "); + + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long savingsId = rs.getLong(ID); + final String accountNo = rs.getString(ACCOUNT_NO); + final BigDecimal dueamount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, DUEAMOUNT); + final BigDecimal outstandingChargeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, OUTSTANDING_CHARGE_AMOUNT); + final LocalDate duedate = JdbcSupport.getLocalDate(rs, DUEDATE); + final String currencyCode = rs.getString(CURRENCY_CODE); + final String currencyName = rs.getString(CURRENCY_NAME); + final String currencyNameCode = rs.getString(CURRENCY_NAME_CODE); + final String currencyDisplaySymbol = rs.getString(CURRENCY_DISPLAY_SYMBOL); + final Integer currencyDigits = JdbcSupport.getInteger(rs, CURRENCY_DIGITS); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, IN_MULTIPLES_OF); + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, + currencyNameCode); + final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations + .transactionType(SavingsAccountTransactionType.DEPOSIT.getValue()); + final PaymentDetailData paymentDetailData = null; + final AccountTransferData transfer = null; + final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, RUNNING_BALANCE); + final boolean postInterestAsOn = false; + final String submittedByUsername = null; + final String note = null; + final LocalDate submittedOnDate = DateUtils.getBusinessLocalDate(); + return SavingsAccountTransactionData.create(savingsId, transactionType, paymentDetailData, savingsId, accountNo, duedate, + currency, dueamount, outstandingChargeAmount, runningBalance, false, transfer, postInterestAsOn, submittedByUsername, + note, submittedOnDate); + } + } + + private static class AccountTransferMapper implements RowMapper { + + public static final String FROM_ACC = "fromAcc"; + public static final String TO_ACC = "toAcc"; + public static final String AMOUNT = "amount"; + public static final String TRANSACTION_DATE = "transactionDate"; + public static final String TRANSFER_INTEREST_TO_SAVINGS = "transfer interest to savings"; + private final String schemaSql; + + AccountTransferMapper() { + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append( + "sa.id as fromAcc ,aa.linked_savings_account_id as toAcc,st.amount as amount, st.transaction_date as transactionDate ") + .append(" from m_deposit_account_term_and_preclosure da ") + .append(" inner join m_savings_account sa on da.savings_account_id = sa.id") + .append(" inner join m_savings_account_transaction st on st.savings_account_id = sa.id") + .append(" inner join m_portfolio_account_associations aa on aa.savings_account_id=sa.id"); + schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public AccountTransferDTO mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { + final Long fromAccountId = rs.getLong(FROM_ACC); + final Long toAccountId = rs.getLong(TO_ACC); + final BigDecimal transactionAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, AMOUNT); + final boolean isRegularTransaction = false; + final boolean isExceptionForBalanceCheck = false; + final LocalDate transactionDate = JdbcSupport.getLocalDate(rs, TRANSACTION_DATE); + return new AccountTransferDTO(transactionDate, transactionAmount, PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, + fromAccountId, toAccountId, TRANSFER_INTEREST_TO_SAVINGS, null, null, null, null, null, null, null, + AccountTransferType.INTEREST_TRANSFER.getValue(), null, null, ExternalId.empty(), null, null, null, + isRegularTransaction, isExceptionForBalanceCheck); + } + + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 3080f530a..f4453fbf2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -1,1533 +1,1545 @@ -/** - * 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 static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY_DB_FIELD; -import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_DATE_DB_FIELD; - -import java.math.BigDecimal; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import org.apache.commons.lang3.StringUtils; -import org.apache.fineract.accounting.common.AccountingRuleType; -import org.apache.fineract.accounting.glaccount.data.GLAccountData; -import org.apache.fineract.infrastructure.core.data.EnumOptionData; -import org.apache.fineract.infrastructure.core.domain.ExternalId; -import org.apache.fineract.infrastructure.core.domain.JdbcSupport; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.core.service.Page; -import org.apache.fineract.infrastructure.core.service.PaginationHelper; -import org.apache.fineract.infrastructure.core.service.SearchParameters; -import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.infrastructure.security.utils.ColumnValidator; -import org.apache.fineract.organisation.monetary.data.CurrencyData; -import org.apache.fineract.portfolio.account.data.AccountTransferData; -import org.apache.fineract.portfolio.client.data.ClientData; -import org.apache.fineract.portfolio.group.data.GroupGeneralData; -import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData; -import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; -import org.apache.fineract.portfolio.savings.DepositAccountType; -import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType; -import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType; -import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; -import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; -import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; -import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountSubStatusEnumData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; -import org.apache.fineract.portfolio.savings.data.SavingsAccrualData; -import org.apache.fineract.portfolio.savings.data.SavingsProductData; -import org.apache.fineract.portfolio.savings.domain.SavingsAccount; -import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; -import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargesPaidByData; -import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper; -import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; -import org.apache.fineract.portfolio.savings.domain.SavingsAccountSubStatusEnum; -import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException; -import org.apache.fineract.portfolio.tax.data.TaxComponentData; -import org.apache.fineract.portfolio.tax.data.TaxDetailsData; -import org.apache.fineract.portfolio.tax.data.TaxGroupData; -import org.apache.fineract.useradministration.domain.AppUser; -import org.springframework.dao.DataAccessException; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.ResultSetExtractor; -import org.springframework.jdbc.core.RowMapper; - -public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountReadPlatformService { - - private final PlatformSecurityContext context; - private final JdbcTemplate jdbcTemplate; - private final DatabaseSpecificSQLGenerator sqlGenerator; - - // mappers - private final SavingsAccountTransactionTemplateMapper transactionTemplateMapper; - protected SavingsAccountTransactionsMapper transactionsMapper; - private final SavingsAccountTransactionsForBatchMapper savingsAccountTransactionsForBatchMapper; - private final SavingAccountMapper savingAccountMapper; - private final SavingAccountMapperForInterestPosting savingAccountMapperForInterestPosting; - // private final SavingsAccountAnnualFeeMapper annualFeeMapper; - - // pagination - private final PaginationHelper paginationHelper; - - private final ColumnValidator columnValidator; - private final SavingsAccountAssembler savingAccountAssembler; - - private final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper; - - public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate, - final SavingsAccountAssembler savingAccountAssembler, PaginationHelper paginationHelper, ColumnValidator columnValidator, - DatabaseSpecificSQLGenerator sqlGenerator, SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper) { - this.context = context; - this.jdbcTemplate = jdbcTemplate; - this.sqlGenerator = sqlGenerator; - this.savingsAccountRepositoryWrapper = savingsAccountRepositoryWrapper; - this.transactionTemplateMapper = new SavingsAccountTransactionTemplateMapper(); - this.transactionsMapper = new SavingsAccountTransactionsMapper(); - this.savingsAccountTransactionsForBatchMapper = new SavingsAccountTransactionsForBatchMapper(); - this.savingAccountMapper = new SavingAccountMapper(); - this.columnValidator = columnValidator; - this.paginationHelper = paginationHelper; - this.savingAccountMapperForInterestPosting = new SavingAccountMapperForInterestPosting(); - this.savingAccountAssembler = savingAccountAssembler; - } - - @Override - public Collection retrieveAllForLookup(final Long clientId) { - - final StringBuilder sqlBuilder = new StringBuilder("select " + this.savingAccountMapper.schema()); - sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300 "); - - final Object[] queryParameters = new Object[] { clientId }; - return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters); - } - - @Override - public Collection retrieveActiveForLookup(final Long clientId, DepositAccountType depositAccountType) { - - final StringBuilder sqlBuilder = new StringBuilder("select " + this.savingAccountMapper.schema()); - sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300 and sa.deposit_type_enum = ? "); - - final Object[] queryParameters = new Object[] { clientId, depositAccountType.getValue() }; - return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters); - } - - @Override - public Collection retrieveActiveForLookup(final Long clientId, DepositAccountType depositAccountType, - String currencyCode) { - final StringBuilder sqlBuilder = new StringBuilder("select " + this.savingAccountMapper.schema()); - sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300 and sa.deposit_type_enum = ? and sa.currency_code = ? "); - - final Object[] queryParameters = new Object[] { clientId, depositAccountType.getValue(), currencyCode }; - return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters); - } - - @Override - public Page retrieveAll(final SearchParameters searchParameters) { - - final AppUser currentUser = this.context.authenticatedUser(); - final String hierarchy = currentUser.getOffice().getHierarchy(); - final String hierarchySearchString = hierarchy + "%"; - - final StringBuilder sqlBuilder = new StringBuilder(200); - sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); - sqlBuilder.append(this.savingAccountMapper.schema()); - - sqlBuilder.append(" join m_office o on o.id = c.office_id"); - sqlBuilder.append(" where o.hierarchy like ?"); - - final Object[] objectArray = new Object[3]; - objectArray[0] = hierarchySearchString; - int arrayPos = 1; - if (searchParameters != null) { - - if (StringUtils.isNotBlank(searchParameters.getStatus())) { - sqlBuilder.append(" and sa.status_enum = ?"); - objectArray[arrayPos] = Integer.parseInt(searchParameters.getStatus()); - arrayPos = arrayPos + 1; - } - - if (StringUtils.isNotBlank(searchParameters.getExternalId())) { - sqlBuilder.append(" and sa.external_id = ?"); - objectArray[arrayPos] = searchParameters.getExternalId(); - arrayPos = arrayPos + 1; - } - if (searchParameters.getOfficeId() != null) { - sqlBuilder.append(" and c.office_id = ?"); - objectArray[arrayPos++] = searchParameters.getOfficeId(); - } - if (searchParameters.hasOrderBy()) { - sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); - this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); - - if (searchParameters.hasSortOrder()) { - sqlBuilder.append(' ').append(searchParameters.getSortOrder()); - this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); - } - } - - if (searchParameters.hasLimit()) { - sqlBuilder.append(" "); - if (searchParameters.hasOffset()) { - sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); - } else { - sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); - } - } - } - final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos); - return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), finalObjectArray, this.savingAccountMapper); - } - - @Override - public SavingsAccountData retrieveOne(final Long accountId) { - - try { - final String sql = "select " + this.savingAccountMapper.schema() + " where sa.id = ?"; - - final SavingsAccountData accountData = this.jdbcTemplate.queryForObject(sql, this.savingAccountMapper, - new Object[] { accountId }); // NOSONAR - - // Load joint holders - if (accountData != null) { - final String jointSql = "SELECT jh.id as id, jh.client_id as clientId, jh.cif_number as cifNumber, " - + "jh.display_name as displayName, jh.share_percentage as sharePercentage, " - + "jh.signing_authority as signingAuthority, jh.role as role, jh.is_primary as isPrimary " - + "FROM m_savings_account_joint_holders jh WHERE jh.savings_account_id = ?"; - final List> jointHolders = this.jdbcTemplate.queryForList(jointSql, accountId); - if (!jointHolders.isEmpty()) { - accountData.setJointHolders(jointHolders); - } - } - - return accountData; - } catch (final EmptyResultDataAccessException e) { - throw new SavingsAccountNotFoundException(accountId, e); - } - } - - @Override - public List retrieveAllTransactionData(final List refNo) throws DataAccessException { - String inSql = String.join(",", Collections.nCopies(refNo.size(), "?")); - String sql = "select " + this.savingsAccountTransactionsForBatchMapper.schema() + " where tr.ref_no in (%s)"; - Object[] params = new Object[refNo.size()]; - int i = 0; - for (String element : refNo) { - params[i] = element; - i++; - } - return this.jdbcTemplate.query(String.format(sql, inSql), this.savingsAccountTransactionsForBatchMapper, params); - } - - @Override - public List retrieveAllSavingsDataForInterestPosting(final boolean backdatedTxnsAllowedTill, final int pageSize, - final Integer status, final Long maxSavingsId) { - LocalDate yesterday = DateUtils.getBusinessLocalDate().minusDays(1); - String sql = "select " + this.savingAccountMapperForInterestPosting.schema() - + "join (select a.id from m_savings_account a where a.id > ? and a.status_enum = ? limit ?) b on b.id = sa.id "; - if (backdatedTxnsAllowedTill) { - sql = sql - + "where (CASE WHEN sa.interest_posted_till_date is not null THEN tr.transaction_date >= sa.interest_posted_till_date ELSE tr.transaction_date >= sa.activatedon_date END) "; - } - - sql = sql + " and (sa.interest_posted_till_date is null or sa.interest_posted_till_date <= ? ) "; - // #audit backward compatibility - sql = sql + " order by sa.id, tr.transaction_date, tr." + CREATED_DATE_DB_FIELD + ", tr.created_date, tr.id"; - - List savingsAccountDataList = this.jdbcTemplate.query(sql, this.savingAccountMapperForInterestPosting, // NOSONAR - new Object[] { maxSavingsId, status, pageSize, yesterday }); - for (SavingsAccountData savingsAccountData : savingsAccountDataList) { - this.savingAccountAssembler.assembleSavings(savingsAccountData); - } - return savingsAccountDataList; - } - - private static final class SavingAccountMapperForInterestPosting implements ResultSetExtractor> { - - private final String schemaSql; - - SavingAccountMapperForInterestPosting() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, "); - sqlBuilder.append("sa.deposit_type_enum as depositType, "); - sqlBuilder.append("c.id as clientId, c.office_id as clientOfficeId, "); - sqlBuilder.append("g.id as groupId, g.office_id as groupOfficeId, "); - sqlBuilder.append("sa.status_enum as statusEnum, "); - sqlBuilder.append("sa.sub_status_enum as subStatusEnum, "); - sqlBuilder.append("sa.submittedon_date as submittedOnDate,"); - sqlBuilder.append("sa.rejectedon_date as rejectedOnDate,"); - sqlBuilder.append("sa.withdrawnon_date as withdrawnOnDate,"); - sqlBuilder.append("sa.approvedon_date as approvedOnDate,"); - sqlBuilder.append("sa.activatedon_date as activatedOnDate,"); - sqlBuilder.append("sa.closedon_date as closedOnDate,"); - sqlBuilder.append( - "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); - sqlBuilder.append("sa.nominal_annual_interest_rate as nominalAnnualInterestRate, "); - sqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, "); - sqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, "); - sqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, "); - sqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, "); - sqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, "); - sqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,"); - sqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); - sqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); - sqlBuilder.append("sa.allow_overdraft as allowOverdraft, "); - sqlBuilder.append("sa.overdraft_limit as overdraftLimit, "); - sqlBuilder.append("sa.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, "); - sqlBuilder.append("sa.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, "); - sqlBuilder.append("sa.total_deposits_derived as totalDeposits, "); - sqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, "); - sqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, "); - sqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, "); - sqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, "); - sqlBuilder.append("sa.total_interest_posted_derived as totalInterestPosted, "); - sqlBuilder.append("sa.total_overdraft_interest_derived as totalOverdraftInterestDerived, "); - sqlBuilder.append("sa.account_balance_derived as accountBalance, "); - sqlBuilder.append("sa.total_fees_charge_derived as totalFeeCharge, "); - sqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, "); - sqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation,"); - sqlBuilder.append("sa.min_required_balance as minRequiredBalance, "); - sqlBuilder.append("sa.enforce_min_required_balance as enforceMinRequiredBalance, "); - sqlBuilder.append("sa.max_allowed_lien_limit as maxAllowedLienLimit, "); - sqlBuilder.append("sa.is_lien_allowed as lienAllowed, "); - sqlBuilder.append("sa.on_hold_funds_derived as onHoldFunds, "); - sqlBuilder.append("sa.withhold_tax as withHoldTax, "); - sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax, "); - sqlBuilder.append("sa.last_interest_calculation_date as lastInterestCalculationDate, "); - sqlBuilder.append("sa.total_savings_amount_on_hold as onHoldAmount, "); - sqlBuilder.append("sa.interest_posted_till_date as interestPostedTillDate, "); - sqlBuilder.append("tg.id as taxGroupId, "); - sqlBuilder.append("(select COALESCE(max(sat.transaction_date),sa.activatedon_date) "); - sqlBuilder.append("from m_savings_account_transaction as sat "); - sqlBuilder.append("where sat.is_reversed = false and sat.is_reversal = false "); - sqlBuilder.append("and sat.transaction_type_enum in (1,2) "); - sqlBuilder.append("and sat.savings_account_id = sa.id) as lastActiveTransactionDate, "); - sqlBuilder.append("sp.id as productId, "); - sqlBuilder.append("sp.is_dormancy_tracking_active as isDormancyTrackingActive, "); - sqlBuilder.append("sp.days_to_inactive as daysToInactive, "); - sqlBuilder.append("sp.days_to_dormancy as daysToDormancy, "); - sqlBuilder.append("sp.days_to_escheat as daysToEscheat, "); - sqlBuilder.append("sp.accounting_type as accountingType, "); - sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, "); - sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,"); - sqlBuilder.append("tr.submitted_on_date as transSubmittedOnDate,tr.cumulative_balance_derived as cumulativeBalance,"); - sqlBuilder.append("tr.running_balance_derived as runningBalance, tr.is_reversed as reversed,"); - sqlBuilder.append("tr.balance_end_date_derived as balanceEndDate, tr.overdraft_amount_derived as overdraftAmount,"); - sqlBuilder.append("tr.is_manual as manualTransaction,tr.office_id as officeId, "); - sqlBuilder.append("pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, "); - sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, "); - sqlBuilder.append("pt.value as paymentTypeName, "); - sqlBuilder.append("msacpb.amount as paidByAmount, msacpb.id as chargesPaidById, "); - sqlBuilder.append( - "msac.id as chargeId, msac.amount as chargeAmount, msac.charge_time_enum as chargeTimeType, msac.is_penalty as isPenaltyCharge, "); - sqlBuilder.append("txd.id as taxDetailsId, txd.amount as taxAmount, "); - sqlBuilder.append("apm.gl_account_id as glAccountIdForInterestOnSavings, apm1.gl_account_id as glAccountIdForSavingsControl, "); - sqlBuilder.append( - "apm2.gl_account_id as glAccountIdForInterestReceivable,apm3.gl_account_id as glAccountIdForOverdraftPorfolio, "); - sqlBuilder.append("apm4.gl_account_id as glAccountIdForInterestPayable, "); - sqlBuilder.append( - "mtc.id as taxComponentId, mtc.debit_account_id as debitAccountId, mtc.credit_account_id as creditAccountId, mtc.percentage as taxPercentage "); - 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("join m_savings_account_transaction tr on sa.id = tr.savings_account_id "); - sqlBuilder.append("left join m_payment_detail pd on pd.id = tr.payment_detail_id "); - sqlBuilder.append("left join m_payment_type pt on pt.id = pd.payment_type_id "); - sqlBuilder.append("left join m_savings_account_charge_paid_by msacpb on msacpb.savings_account_transaction_id = tr.id "); - sqlBuilder.append("left join m_savings_account_charge msac on msac.id = msacpb.savings_account_charge_id "); - 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_tax_group tg on tg.id = sa.tax_group_id "); - sqlBuilder.append("left join m_savings_account_transaction_tax_details txd on txd.savings_transaction_id = tr.id "); - sqlBuilder.append("left join m_tax_component mtc on mtc.id = txd.tax_component_id "); - sqlBuilder.append( - "left join acc_product_mapping apm on apm.product_type = 2 and apm.product_id = sp.id and apm.financial_account_type=3 "); - sqlBuilder.append( - "left join acc_product_mapping apm1 on apm1.product_type = 2 and apm1.product_id = sp.id and apm1.financial_account_type=2 "); - sqlBuilder.append("left join acc_product_mapping apm2 on apm2.product_id = sp.id and apm2.financial_account_type=18 "); - sqlBuilder.append("left join acc_product_mapping apm3 on apm3.product_id = sp.id and apm3.financial_account_type = 11 "); - sqlBuilder.append("left join acc_product_mapping apm4 on apm4.product_id = sp.id and apm4.financial_account_type = 17 "); - - this.schemaSql = sqlBuilder.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public List extractData(final ResultSet rs) throws SQLException { - - List savingsAccountDataList = new ArrayList<>(); - HashMap savingsMap = new HashMap<>(); - String currencyCode = null; - Integer currencyDigits = null; - Integer inMultiplesOf = null; - CurrencyData currency = null; - HashMap transMap = new HashMap<>(); - HashMap taxDetails = new HashMap<>(); - HashMap chargeDetails = new HashMap<>(); - SavingsAccountTransactionData savingsAccountTransactionData = null; - SavingsAccountData savingsAccountData = null; - int count = 0; - - while (rs.next()) { - final Long id = rs.getLong("id"); - final Long transactionId = rs.getLong("transactionId"); - final Long taxDetailId = JdbcSupport.getLongDefaultToNullIfZero(rs, "taxDetailsId"); - final Long taxComponentId = JdbcSupport.getLongDefaultToNullIfZero(rs, "taxComponentId"); - final String accountNo = rs.getString("accountNo"); - final Long chargeId = rs.getLong("chargeId"); - - if (!savingsMap.containsValue(id)) { - if (count > 0) { - savingsAccountDataList.add(savingsAccountData); - } - count++; - savingsMap.put("id", id); - - final String externalId = rs.getString("externalId"); - final Integer depositTypeId = rs.getInt("depositType"); - final EnumOptionData depositType = SavingsEnumerations.depositType(depositTypeId); - final Long groupId = JdbcSupport.getLong(rs, "groupId"); - final Long groupOfficeId = JdbcSupport.getLong(rs, "groupOfficeId"); - final GroupGeneralData groupGeneralData = new GroupGeneralData(groupId, groupOfficeId); - - final Long clientId = JdbcSupport.getLong(rs, "clientId"); - final Long clientOfficeId = JdbcSupport.getLong(rs, "clientOfficeId"); - final ClientData clientData = ClientData.createClientForInterestPosting(clientId, clientOfficeId); - - final Long glAccountIdForInterestOnSavings = rs.getLong("glAccountIdForInterestOnSavings"); - final Long glAccountIdForSavingsControl = rs.getLong("glAccountIdForSavingsControl"); - - final Long glAccountIdForOverdraftPorfolio = rs.getLong("glAccountIdForOverdraftPorfolio"); - final Long glAccountIdForInterestReceivable = rs.getLong("glAccountIdForInterestReceivable"); - - final Long glAccountIdForInterestPayable = rs.getLong("glAccountIdForInterestPayable"); - - final Long productId = rs.getLong("productId"); - final Integer accountType = rs.getInt("accountingType"); - final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountType); - final EnumOptionData enumOptionDataForAccounting = new EnumOptionData(accountType.longValue(), - accountingRuleType.getCode(), accountingRuleType.getValue().toString()); - final SavingsProductData savingsProductData = SavingsProductData.createForInterestPosting(productId, - enumOptionDataForAccounting); - - final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum"); - final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum); - final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum"); - final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum); - final LocalDate lastActiveTransactionDate = JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate"); - final boolean isDormancyTrackingActive = rs.getBoolean("isDormancyTrackingActive"); - final Integer numDaysToInactive = JdbcSupport.getInteger(rs, "daysToInactive"); - final Integer numDaysToDormancy = JdbcSupport.getInteger(rs, "daysToDormancy"); - final Integer numDaysToEscheat = JdbcSupport.getInteger(rs, "daysToEscheat"); - Integer daysToInactive = null; - Integer daysToDormancy = null; - Integer daysToEscheat = null; - - LocalDate currentDate = DateUtils.getBusinessLocalDate(); - if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) { - if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) { - daysToEscheat = DateUtils.getExactDifferenceInDays(currentDate, - lastActiveTransactionDate.plusDays(numDaysToEscheat)); - } - if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) { - daysToDormancy = DateUtils.getExactDifferenceInDays(currentDate, - lastActiveTransactionDate.plusDays(numDaysToDormancy)); - } - if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) { - daysToInactive = DateUtils.getExactDifferenceInDays(currentDate, - lastActiveTransactionDate.plusDays(numDaysToInactive)); - } - } - final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); - final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate"); - final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); - final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate"); - final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); - final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, null, - null, null, null, null, null, null, withdrawnOnDate, null, null, null, approvedOnDate, null, null, null, - activatedOnDate, null, null, null, closedOnDate, null, null, null); - - currencyCode = rs.getString("currencyCode"); - currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - currency = new CurrencyData(currencyCode, currencyDigits, inMultiplesOf); - - final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits"); - final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals"); - final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawalFees"); - final BigDecimal totalAnnualFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalAnnualFees"); - - final BigDecimal totalInterestEarned = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestEarned"); - final BigDecimal totalInterestPosted = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalInterestPosted"); - final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accountBalance"); - final BigDecimal totalFeeCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalFeeCharge"); - final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge"); - final BigDecimal totalOverdraftInterestDerived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, - "totalOverdraftInterestDerived"); - final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax"); - final LocalDate interestPostedTillDate = JdbcSupport.getLocalDate(rs, "interestPostedTillDate"); - - final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, - "minBalanceForInterestCalculation"); - final BigDecimal onHoldFunds = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldFunds"); - - final BigDecimal onHoldAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldAmount"); - - BigDecimal availableBalance = accountBalance; - if (availableBalance != null && onHoldFunds != null) { - - availableBalance = availableBalance.subtract(onHoldFunds); - } - - if (availableBalance != null && onHoldAmount != null) { - - availableBalance = availableBalance.subtract(onHoldAmount); - } - - BigDecimal interestNotPosted = BigDecimal.ZERO; - LocalDate lastInterestCalculationDate = null; - if (totalInterestEarned != null) { - interestNotPosted = totalInterestEarned.subtract(totalInterestPosted).add(totalOverdraftInterestDerived); - lastInterestCalculationDate = JdbcSupport.getLocalDate(rs, "lastInterestCalculationDate"); - } - - final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, - totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge, - totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax, interestNotPosted, - lastInterestCalculationDate, availableBalance, interestPostedTillDate); - summary.setPrevInterestPostedTillDate(interestPostedTillDate); - - final boolean withHoldTax = rs.getBoolean("withHoldTax"); - final Long taxGroupId = JdbcSupport.getLongDefaultToNullIfZero(rs, "taxGroupId"); - TaxGroupData taxGroupData = null; - if (taxGroupId != null) { - taxGroupData = TaxGroupData.lookup(taxGroupId, null); - } - - final BigDecimal nominalAnnualInterestRate = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, - "nominalAnnualInterestRate"); - - final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations.compoundingInterestPeriodType( - SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, "interestCompoundingPeriodType"))); - - final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType( - SavingsPostingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, "interestPostingPeriodType"))); - - final EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType( - SavingsInterestCalculationType.fromInt(JdbcSupport.getInteger(rs, "interestCalculationType"))); - - final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations - .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType - .fromInt(JdbcSupport.getInteger(rs, "interestCalculationDaysInYearType"))); - - final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, - "minRequiredOpeningBalance"); - - final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency"); - EnumOptionData lockinPeriodFrequencyType = null; - final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType"); - if (lockinPeriodFrequencyTypeValue != null) { - final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType - .fromInt(lockinPeriodFrequencyTypeValue); - lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType); - } - - final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers"); - - final boolean allowOverdraft = rs.getBoolean("allowOverdraft"); - final BigDecimal overdraftLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "overdraftLimit"); - final BigDecimal nominalAnnualInterestRateOverdraft = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, - "nominalAnnualInterestRateOverdraft"); - final BigDecimal minOverdraftForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, - "minOverdraftForInterestCalculation"); - - final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance"); - final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance"); - final BigDecimal maxAllowedLienLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxAllowedLienLimit"); - final boolean lienAllowed = rs.getBoolean("lienAllowed"); - savingsAccountData = SavingsAccountData.instance(id, accountNo, depositType, externalId, null, null, null, null, - productId, null, null, null, status, subStatus, null, timeline, currency, nominalAnnualInterestRate, - interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, - interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance, - enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, onHoldFunds, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroupData, - lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, - onHoldAmount); - - savingsAccountData.setClientData(clientData); - savingsAccountData.setGroupGeneralData(groupGeneralData); - savingsAccountData.setSavingsProduct(savingsProductData); - - savingsAccountData.setGlAccountIdForInterestReceivable(glAccountIdForInterestReceivable); - savingsAccountData.setGlAccountIdForOverdraftPorfolio(glAccountIdForOverdraftPorfolio); - - savingsAccountData.setGlAccountIdForInterestPayable(glAccountIdForInterestPayable); - - savingsAccountData.setGlAccountIdForInterestOnSavings(glAccountIdForInterestOnSavings); - savingsAccountData.setGlAccountIdForSavingsControl(glAccountIdForSavingsControl); - } - - if (!transMap.containsValue(transactionId)) { - - final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType"); - final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(transactionTypeInt); - - final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate"); - final LocalDate balanceEndDate = JdbcSupport.getLocalDate(rs, "balanceEndDate"); - final LocalDate transSubmittedOnDate = JdbcSupport.getLocalDate(rs, "transSubmittedOnDate"); - final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount"); - final BigDecimal overdraftAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "overdraftAmount"); - final BigDecimal outstandingChargeAmount = null; - final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "runningBalance"); - final boolean reversed = rs.getBoolean("reversed"); - final Long officeId = rs.getLong("officeId"); - final BigDecimal cumulativeBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "cumulativeBalance"); - - final boolean postInterestAsOn = false; - - PaymentDetailData paymentDetailData = null; - if (transactionType.isDepositOrWithdrawal()) { - final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType"); - if (paymentTypeId != null) { - final String typeName = rs.getString("paymentTypeName"); - final PaymentTypeData paymentTypeData = new PaymentTypeData(paymentTypeId, typeName, null, false, null, null, - false); - paymentDetailData = new PaymentDetailData(id, paymentTypeData, null, null, null, null, null); - } - } - - savingsAccountTransactionData = SavingsAccountTransactionData.create(transactionId, transactionType, paymentDetailData, - id, accountNo, date, currency, amount, outstandingChargeAmount, runningBalance, reversed, transSubmittedOnDate, - postInterestAsOn, cumulativeBalance, balanceEndDate); - savingsAccountTransactionData.setOverdraftAmount(overdraftAmount); - - transMap.put("id", transactionId); - if (savingsAccountData.getOfficeId() == null) { - savingsAccountData.setOfficeId(officeId); - } - - savingsAccountData.setSavingsAccountTransactionData(savingsAccountTransactionData); - } - - if (chargeId != null && !chargeDetails.containsValue(chargeId)) { - final boolean isPenalty = rs.getBoolean("isPenaltyCharge"); - final BigDecimal chargeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "chargeAmount"); - final Integer chargesTimeType = rs.getInt("chargeTimeType"); - final EnumOptionData enumOptionDataForChargesTimeType = new EnumOptionData(chargesTimeType.longValue(), null, null); - final SavingsAccountChargeData savingsAccountChargeData = new SavingsAccountChargeData(chargeId, chargeAmount, - enumOptionDataForChargesTimeType, isPenalty); - - final Long chargesPaidById = rs.getLong("chargesPaidById"); - final BigDecimal chargesPaid = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "paidByAmount"); - final SavingsAccountChargesPaidByData savingsAccountChargesPaidByData = new SavingsAccountChargesPaidByData( - chargesPaidById, chargesPaid); - savingsAccountChargesPaidByData.setSavingsAccountChargeData(savingsAccountChargeData); - if (savingsAccountChargesPaidByData != null) { - savingsAccountTransactionData.setChargesPaidByData(savingsAccountChargesPaidByData); - } - - chargeDetails.put("id", chargeId); - } - - if (taxDetailId != null && !taxDetails.containsValue(taxDetailId)) { - final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "taxAmount"); - final BigDecimal percentage = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "taxPercentage"); - final Long debitId = rs.getLong("debitAccountId"); - final Long creditId = rs.getLong("creditAccountId"); - final GLAccountData debitAccount = GLAccountData.createFrom(debitId); - final GLAccountData creditAccount = GLAccountData.createFrom(creditId); - - if (taxComponentId != null) { - final TaxComponentData taxComponent = TaxComponentData.createTaxComponent(taxComponentId, percentage, debitAccount, - creditAccount); - savingsAccountTransactionData.setTaxDetails(new TaxDetailsData(taxComponent, amount)); - } - - taxDetails.put("id", taxDetailId); - } - - } - if (savingsAccountData != null) { - savingsAccountDataList.add(savingsAccountData); - } - return savingsAccountDataList; - - } - } - - private static final class SavingAccountMapper implements RowMapper { - - private final String schemaSql; - - SavingAccountMapper() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, "); - sqlBuilder.append("sa.deposit_type_enum as depositType, "); - 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 fieldOfficerId, s.display_name as fieldOfficerName, "); - sqlBuilder.append("sa.status_enum as statusEnum, "); - sqlBuilder.append("sa.sub_status_enum as subStatusEnum, "); - sqlBuilder.append("sa.reason_for_block as reasonForBlock, "); - sqlBuilder.append("sa.submittedon_date as submittedOnDate,"); - sqlBuilder.append("sbu.username as submittedByUsername,"); - sqlBuilder.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,"); - - sqlBuilder.append("sa.rejectedon_date as rejectedOnDate,"); - sqlBuilder.append("rbu.username as rejectedByUsername,"); - sqlBuilder.append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,"); - - sqlBuilder.append("sa.withdrawnon_date as withdrawnOnDate,"); - sqlBuilder.append("wbu.username as withdrawnByUsername,"); - sqlBuilder.append("wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,"); - - sqlBuilder.append("sa.approvedon_date as approvedOnDate,"); - sqlBuilder.append("abu.username as approvedByUsername,"); - sqlBuilder.append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,"); - - sqlBuilder.append("sa.activatedon_date as activatedOnDate,"); - sqlBuilder.append("avbu.username as activatedByUsername,"); - sqlBuilder.append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname,"); - - sqlBuilder.append("sa.closedon_date as closedOnDate,"); - sqlBuilder.append("cbu.username as closedByUsername,"); - sqlBuilder.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,"); - - sqlBuilder.append( - "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, 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("sa.nominal_annual_interest_rate as nominalAnnualInterestRate, "); - sqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, "); - sqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, "); - sqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, "); - sqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, "); - sqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, "); - sqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,"); - sqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); - - sqlBuilder.append("sa.allow_overdraft as allowOverdraft, "); - sqlBuilder.append("sa.overdraft_limit as overdraftLimit, "); - sqlBuilder.append("sa.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, "); - sqlBuilder.append("sa.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, "); - - sqlBuilder.append("sa.total_deposits_derived as totalDeposits, "); - sqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, "); - sqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, "); - sqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, "); - sqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, "); - sqlBuilder.append("sa.total_interest_posted_derived as totalInterestPosted, "); - sqlBuilder.append("sa.total_overdraft_interest_derived as totalOverdraftInterestDerived, "); - sqlBuilder.append("sa.account_balance_derived as accountBalance, "); - sqlBuilder.append("sa.total_fees_charge_derived as totalFeeCharge, "); - sqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, "); - sqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation,"); - sqlBuilder.append("sa.min_required_balance as minRequiredBalance, "); - sqlBuilder.append("sa.enforce_min_required_balance as enforceMinRequiredBalance, "); - sqlBuilder.append("sa.max_allowed_lien_limit as maxAllowedLienLimit, "); - sqlBuilder.append("sa.is_lien_allowed as lienAllowed, "); - sqlBuilder.append("sa.on_hold_funds_derived as onHoldFunds, "); - sqlBuilder.append("sa.withhold_tax as withHoldTax, "); - sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax, "); - sqlBuilder.append("sa.last_interest_calculation_date as lastInterestCalculationDate, "); - sqlBuilder.append("sa.interest_posted_till_date as interestPostedTillDate, "); - sqlBuilder.append("sa.total_savings_amount_on_hold as onHoldAmount, "); - sqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); - sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, "); - sqlBuilder.append("(select COALESCE(max(sat.transaction_date),sa.activatedon_date) "); - sqlBuilder.append("from m_savings_account_transaction as sat "); - sqlBuilder.append("where sat.is_reversed = false and sat.is_reversal = false "); - sqlBuilder.append("and sat.transaction_type_enum in (1,2) "); - sqlBuilder.append("and sat.savings_account_id = sa.id) as lastActiveTransactionDate, "); - sqlBuilder.append("sp.is_dormancy_tracking_active as isDormancyTrackingActive, "); - sqlBuilder.append("sp.days_to_inactive as daysToInactive, "); - sqlBuilder.append("sp.days_to_dormancy as daysToDormancy, "); - sqlBuilder.append("sp.days_to_escheat as daysToEscheat, "); - // CBS extended fields - sqlBuilder.append("sa.cbs_account_type as cbsAccountType, "); - sqlBuilder.append("sa.customer_segment as customerSegment, "); - sqlBuilder.append("sa.account_status as accountStatus, "); - sqlBuilder.append("sa.mode_of_operation as modeOfOperation, "); - sqlBuilder.append("sa.special_category as specialCategory, "); - sqlBuilder.append("sa.weaker_section as weakerSection, "); - sqlBuilder.append("sa.guardian_name as guardianName, "); - sqlBuilder.append("sa.guardian_cif_number as guardianCifNumber, "); - sqlBuilder.append("sa.guardian_relationship as guardianRelationship, "); - sqlBuilder.append("sa.minor_date_of_birth as minorDateOfBirth, "); - sqlBuilder.append("sa.guardian_client_id as guardianClientId, "); - sqlBuilder.append("sa.introducer_client_id as introducerClientId, "); - sqlBuilder.append("sa.introducer_name as introducerName, "); - sqlBuilder.append("sa.introducer_cif_number as introducerCifNumber, "); - sqlBuilder.append("sa.nominee_data as nomineeData, "); - sqlBuilder.append("sa.linked_account_id as linkedAccountId, "); - sqlBuilder.append("sa.linked_account_type as linkedAccountType, "); - sqlBuilder.append("sa.linked_account_no as linkedAccountNo, "); - sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, "); - sqlBuilder.append("sp.msme as msme, "); - sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, "); - sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, "); - // Facility config fields - sqlBuilder.append("safc.atm_facility as atmFacility, "); - sqlBuilder.append("safc.atm_withdrawal_facility as atmWithdrawalFacility, "); - sqlBuilder.append("safc.upi_facility as upiFacility, "); - sqlBuilder.append("safc.pos_transactions_facility as posTransactionsFacility, "); - sqlBuilder.append("safc.cheque_book as chequeBook, "); - sqlBuilder.append("safc.imps_facility as impsFacility, "); - sqlBuilder.append("safc.sms_alerts as smsAlerts, "); - sqlBuilder.append("safc.bbps_facility as bbpsFacility, "); - sqlBuilder.append("safc.insurance_cover as insuranceCover, "); - sqlBuilder.append("safc.bepg_facility as bepgFacility, "); - sqlBuilder.append("safc.neft_facility as neftFacility, "); - sqlBuilder.append("safc.rtgs_facility as rtgsFacility, "); - sqlBuilder.append("safc.inter_bank_total_cap_facility as interBankTotalCapFacility, "); - sqlBuilder.append("safc.withdrawal_facility as withdrawalFacility, "); - sqlBuilder.append("safc.deposit_facility_kyc as depositFacilityKyc, "); - sqlBuilder.append("safc.virtual_debit_card as virtualDebitCard "); - 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 "); - sqlBuilder.append("left join m_appuser sbu on sbu.id = sa.submittedon_userid "); - sqlBuilder.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid "); - sqlBuilder.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid "); - sqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid "); - sqlBuilder.append("left join m_appuser avbu on avbu.id = sa.activatedon_userid "); - sqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid "); - sqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id "); - sqlBuilder.append("left join m_savings_account_facility_config safc on safc.savings_account_id = sa.id "); - - this.schemaSql = sqlBuilder.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public SavingsAccountData 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 Integer depositTypeId = rs.getInt("depositType"); - final EnumOptionData depositType = SavingsEnumerations.depositType(depositTypeId); - - 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 Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum"); - final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum); - - final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum"); - final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum); - final String reasonForBlock = rs.getString("reasonForBlock"); - - final LocalDate lastActiveTransactionDate = JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate"); - final boolean isDormancyTrackingActive = rs.getBoolean("isDormancyTrackingActive"); - final Integer numDaysToInactive = JdbcSupport.getInteger(rs, "daysToInactive"); - final Integer numDaysToDormancy = JdbcSupport.getInteger(rs, "daysToDormancy"); - final Integer numDaysToEscheat = JdbcSupport.getInteger(rs, "daysToEscheat"); - Integer daysToInactive = null; - Integer daysToDormancy = null; - Integer daysToEscheat = null; - - LocalDate currentDate = DateUtils.getBusinessLocalDate(); - if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) { - if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) { - daysToEscheat = DateUtils.getExactDifferenceInDays(currentDate, lastActiveTransactionDate.plusDays(numDaysToEscheat)); - } - if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) { - daysToDormancy = DateUtils.getExactDifferenceInDays(currentDate, lastActiveTransactionDate.plusDays(numDaysToDormancy)); - } - if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) { - daysToInactive = DateUtils.getExactDifferenceInDays(currentDate, lastActiveTransactionDate.plusDays(numDaysToInactive)); - } - } - - final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); - final String submittedByUsername = rs.getString("submittedByUsername"); - final String submittedByFirstname = rs.getString("submittedByFirstname"); - final String submittedByLastname = rs.getString("submittedByLastname"); - - final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate"); - final String rejectedByUsername = rs.getString("rejectedByUsername"); - final String rejectedByFirstname = rs.getString("rejectedByFirstname"); - final String rejectedByLastname = rs.getString("rejectedByLastname"); - - final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate"); - final String withdrawnByUsername = rs.getString("withdrawnByUsername"); - final String withdrawnByFirstname = rs.getString("withdrawnByFirstname"); - final String withdrawnByLastname = rs.getString("withdrawnByLastname"); - - final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); - final String approvedByUsername = rs.getString("approvedByUsername"); - final String approvedByFirstname = rs.getString("approvedByFirstname"); - final String approvedByLastname = rs.getString("approvedByLastname"); - - final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate"); - final String activatedByUsername = rs.getString("activatedByUsername"); - final String activatedByFirstname = rs.getString("activatedByFirstname"); - final String activatedByLastname = rs.getString("activatedByLastname"); - - final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); - final String closedByUsername = rs.getString("closedByUsername"); - final String closedByFirstname = rs.getString("closedByFirstname"); - final String closedByLastname = rs.getString("closedByLastname"); - - final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, - submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, - rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, - approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, activatedByUsername, activatedByFirstname, - activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname); - - 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); - - final BigDecimal nominalAnnualInterestRate = rs.getBigDecimal("nominalAnnualInterestRate"); - - final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations.compoundingInterestPeriodType( - SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, "interestCompoundingPeriodType"))); - - final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType( - SavingsPostingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, "interestPostingPeriodType"))); - - final EnumOptionData interestCalculationType = SavingsEnumerations - .interestCalculationType(SavingsInterestCalculationType.fromInt(JdbcSupport.getInteger(rs, "interestCalculationType"))); - - final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations.interestCalculationDaysInYearType( - SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs, "interestCalculationDaysInYearType"))); - - final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredOpeningBalance"); - - final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency"); - EnumOptionData lockinPeriodFrequencyType = null; - final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType"); - if (lockinPeriodFrequencyTypeValue != null) { - final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); - lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType); - } - - final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers"); - - final boolean allowOverdraft = rs.getBoolean("allowOverdraft"); - final BigDecimal overdraftLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "overdraftLimit"); - final BigDecimal nominalAnnualInterestRateOverdraft = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, - "nominalAnnualInterestRateOverdraft"); - final BigDecimal minOverdraftForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, - "minOverdraftForInterestCalculation"); - - final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance"); - final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance"); - - final BigDecimal maxAllowedLienLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxAllowedLienLimit"); - final boolean lienAllowed = rs.getBoolean("lienAllowed"); - - final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits"); - final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals"); - final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawalFees"); - final BigDecimal totalAnnualFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalAnnualFees"); - - final BigDecimal totalInterestEarned = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestEarned"); - final BigDecimal totalInterestPosted = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalInterestPosted"); - final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accountBalance"); - final BigDecimal totalFeeCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalFeeCharge"); - final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge"); - final BigDecimal totalOverdraftInterestDerived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, - "totalOverdraftInterestDerived"); - final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax"); - final LocalDate interestPostedTillDate = JdbcSupport.getLocalDate(rs, "interestPostedTillDate"); - - final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, - "minBalanceForInterestCalculation"); - final BigDecimal onHoldFunds = rs.getBigDecimal("onHoldFunds"); - - final BigDecimal onHoldAmount = rs.getBigDecimal("onHoldAmount"); - - BigDecimal availableBalance = accountBalance; - if (availableBalance != null && onHoldFunds != null) { - availableBalance = availableBalance.subtract(onHoldFunds); - } - - if (availableBalance != null && onHoldAmount != null) { - - availableBalance = availableBalance.subtract(onHoldAmount); - } - - BigDecimal interestNotPosted = BigDecimal.ZERO; - LocalDate lastInterestCalculationDate = null; - if (totalInterestEarned != null) { - interestNotPosted = totalInterestEarned.subtract(totalInterestPosted).add(totalOverdraftInterestDerived); - lastInterestCalculationDate = JdbcSupport.getLocalDate(rs, "lastInterestCalculationDate"); - } - - final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, - totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge, - totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax, interestNotPosted, lastInterestCalculationDate, - availableBalance, interestPostedTillDate); - - final boolean withHoldTax = rs.getBoolean("withHoldTax"); - final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId"); - final String taxGroupName = rs.getString("taxGroupName"); - TaxGroupData taxGroupData = null; - if (taxGroupId != null) { - taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); - } - - final SavingsAccountData accountData = SavingsAccountData.instance(id, accountNo, depositType, externalId, groupId, groupName, - clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, subStatus, reasonForBlock, - timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance, - enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, onHoldFunds, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroupData, - lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, onHoldAmount); - - // CBS extended fields - accountData.setCbsAccountType(rs.getString("cbsAccountType")); - accountData.setCustomerSegment(rs.getString("customerSegment")); - accountData.setAccountStatus(rs.getString("accountStatus")); - accountData.setModeOfOperation(rs.getString("modeOfOperation")); - accountData.setSpecialCategory(rs.getString("specialCategory")); - accountData.setWeakerSection(rs.getString("weakerSection")); - accountData.setGuardianName(rs.getString("guardianName")); - accountData.setGuardianCifNumber(rs.getString("guardianCifNumber")); - accountData.setGuardianRelationship(rs.getString("guardianRelationship")); - accountData.setMinorDateOfBirth(JdbcSupport.getLocalDate(rs, "minorDateOfBirth")); - accountData.setGuardianClientId(JdbcSupport.getLong(rs, "guardianClientId")); - accountData.setIntroducerClientId(JdbcSupport.getLong(rs, "introducerClientId")); - accountData.setIntroducerName(rs.getString("introducerName")); - accountData.setIntroducerCifNumber(rs.getString("introducerCifNumber")); - accountData.setLinkedAccountId(JdbcSupport.getLong(rs, "linkedAccountId")); - accountData.setLinkedAccountType(rs.getString("linkedAccountType")); - accountData.setLinkedAccountNo(rs.getString("linkedAccountNo")); - accountData.setBalanceSwapOption(rs.getObject("balanceSwapOption") != null ? rs.getBoolean("balanceSwapOption") : null); - accountData.setMsme(rs.getObject("msme") != null ? rs.getBoolean("msme") : null); - accountData.setNominationFacility(rs.getObject("nominationFacility") != null ? rs.getBoolean("nominationFacility") : null); - accountData.setAutoDebitFacility(rs.getObject("autoDebitFacility") != null ? rs.getBoolean("autoDebitFacility") : null); - - // Nominee as parsed map - final String nomineeDataStr = rs.getString("nomineeData"); - if (nomineeDataStr != null && !nomineeDataStr.isEmpty()) { - try { - com.google.gson.Gson gson = new com.google.gson.Gson(); - @SuppressWarnings("unchecked") - java.util.Map nomineeMap = gson.fromJson(nomineeDataStr, - new com.google.gson.reflect.TypeToken>() {}.getType()); - accountData.setNomineeData(nomineeMap); - } catch (Exception ignored) {} - } - - // Facility config - final boolean hasFacilityConfig = rs.getObject("atmFacility") != null; - if (hasFacilityConfig) { - final java.util.Map facilityConfigMap = new java.util.LinkedHashMap<>(); - facilityConfigMap.put("atmFacility", rs.getBoolean("atmFacility")); - facilityConfigMap.put("atmWithdrawalFacility", rs.getBoolean("atmWithdrawalFacility")); - facilityConfigMap.put("upiFacility", rs.getBoolean("upiFacility")); - facilityConfigMap.put("posTransactionsFacility", rs.getBoolean("posTransactionsFacility")); - facilityConfigMap.put("chequeBook", rs.getBoolean("chequeBook")); - facilityConfigMap.put("impsFacility", rs.getBoolean("impsFacility")); - facilityConfigMap.put("smsAlerts", rs.getBoolean("smsAlerts")); - facilityConfigMap.put("bbpsFacility", rs.getBoolean("bbpsFacility")); - facilityConfigMap.put("insuranceCover", rs.getBoolean("insuranceCover")); - facilityConfigMap.put("bepgFacility", rs.getBoolean("bepgFacility")); - facilityConfigMap.put("neftFacility", rs.getBoolean("neftFacility")); - facilityConfigMap.put("rtgsFacility", rs.getBoolean("rtgsFacility")); - facilityConfigMap.put("interBankTotalCapFacility", rs.getBoolean("interBankTotalCapFacility")); - facilityConfigMap.put("withdrawalFacility", rs.getBoolean("withdrawalFacility")); - facilityConfigMap.put("depositFacilityKyc", rs.getBoolean("depositFacilityKyc")); - facilityConfigMap.put("virtualDebitCard", rs.getBoolean("virtualDebitCard")); - accountData.setAccountFacilityConfig(facilityConfigMap); - } - - return accountData; - } - } - - private static final class SavingAccountMapperForLookup implements RowMapper { - - private final String schemaSql; - - SavingAccountMapperForLookup() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("sa.id as id, sa.account_no as accountNo, "); - sqlBuilder.append("sa.deposit_type_enum as depositType, "); - sqlBuilder.append("sp.id as productId, sp.name as productName, "); - sqlBuilder.append("sa.status_enum as statusEnum "); - - sqlBuilder.append("from m_savings_account sa "); - sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id "); - - this.schemaSql = sqlBuilder.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public SavingsAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong("id"); - final String accountNo = rs.getString("accountNo"); - final Integer depositTypeId = rs.getInt("depositType"); - final EnumOptionData depositType = SavingsEnumerations.depositType(depositTypeId); - - final Long productId = rs.getLong("productId"); - final String productName = rs.getString("productName"); - - final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum"); - final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum); - - return SavingsAccountData.lookupWithProductDetails(id, accountNo, depositType, productId, productName, status); - } - } - - @Override - public SavingsAccountTransactionData retrieveDepositTransactionTemplate(final Long savingsId, - final DepositAccountType depositAccountType) { - - try { - final String sql = "select " + this.transactionTemplateMapper.schema() + " where sa.id = ? and sa.deposit_type_enum = ?"; - - return this.jdbcTemplate.queryForObject(sql, this.transactionTemplateMapper, // NOSONAR - new Object[] { savingsId, depositAccountType.getValue() }); - } catch (final EmptyResultDataAccessException e) { - throw new SavingsAccountNotFoundException(savingsId, e); - } - } - - @Override - public Collection retrieveAllTransactions(final Long savingsId, DepositAccountType depositAccountType) { - // #audit backward compatibility - final String sql = "select " + this.transactionsMapper.schema() - + " where sa.id = ? and sa.deposit_type_enum = ? order by tr.transaction_date DESC," + " tr." + CREATED_DATE_DB_FIELD - + " DESC, tr.created_date DESC, tr.id DESC"; - return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { savingsId, depositAccountType.getValue() }); // NOSONAR - } - - @Override - public SavingsAccountTransactionData retrieveSavingsTransaction(final Long savingsId, final Long transactionId, - DepositAccountType depositAccountType) { - - final String sql = "select " + this.transactionsMapper.schema() + " where sa.id = ? and sa.deposit_type_enum = ? and tr.id= ?"; - - return this.jdbcTemplate.queryForObject(sql, this.transactionsMapper, // NOSONAR - new Object[] { savingsId, depositAccountType.getValue(), transactionId }); - } - - private static final class SavingsAccountTransactionsForBatchMapper implements RowMapper { - - private final String schemaSql; - - SavingsAccountTransactionsForBatchMapper() { - - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("tr.id as transactionId, tr.ref_no as refNo "); - sqlBuilder.append("from m_savings_account_transaction tr"); - - this.schemaSql = sqlBuilder.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - final Long id = rs.getLong("transactionId"); - SavingsAccountTransactionData savingsAccountTransactionData = SavingsAccountTransactionData.create(id); - final String refNo = rs.getString("refNo"); - savingsAccountTransactionData.setRefNo(refNo); - return savingsAccountTransactionData; - } - } - - /* - * @Override public Collection retrieveAccountsWithAnnualFeeDue() { final String sql = - * "select " + this.annualFeeMapper.schema() + - * " where sa.annual_fee_next_due_date is not null and sa.annual_fee_next_due_date <= NOW()" ; - * - * return this.jdbcTemplate.query(sql, this.annualFeeMapper, new Object[] {}); } - */ - - public static class SavingsAccountTransactionsMapper implements RowMapper { - - private static final String SELECT = buildSelect(); - private static final String FROM = buildFrom(); - private static final String SCHEMA = SELECT + FROM; - - public SavingsAccountTransactionsMapper() {} - - protected static String buildSelect() { - return "tr.id as transactionId, tr.transaction_type_enum as transactionType, " - + "tr.transaction_date as transactionDate, tr.amount as transactionAmount, " - + "tr.release_id_of_hold_amount as releaseTransactionId, tr.reason_for_block as reasonForBlock, " - + "tr.submitted_on_date as submittedOnDate, au.username as submittedByUsername, nt.note as transactionNote, " - + "tr.running_balance_derived as runningBalance, tr.is_reversed as reversed, " - + "tr.is_reversal as isReversal, tr.original_transaction_id as originalTransactionId, tr.is_lien_transaction as lienTransaction, " - + "fromtran.id as fromTransferId, fromtran.is_reversed as fromTransferReversed, " - + "fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount, " - + "fromtran.description as fromTransferDescription, " - + "totran.id as toTransferId, totran.is_reversed as toTransferReversed, " - + "totran.transaction_date as toTransferDate, totran.amount as toTransferAmount, " - + "totran.description as toTransferDescription, sa.id as savingsId, sa.account_no as accountNo, " - + "pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, " - + "pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, " - + "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, " - + "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, " - + "curr.display_symbol as currencyDisplaySymbol, pt.value as paymentTypeName, " + "tr.is_manual as postInterestAsOn "; - } - - protected static String buildFrom() { - return " FROM m_savings_account_transaction tr join m_savings_account sa on tr.savings_account_id = sa.id " - + "join m_currency curr on curr.code = sa.currency_code " - + "left join m_account_transfer_transaction fromtran on fromtran.from_savings_transaction_id = tr.id " - + "left join m_account_transfer_transaction totran on totran.to_savings_transaction_id = tr.id " - + "left join m_payment_detail pd on tr.payment_detail_id = pd.id " - + "left join m_payment_type pt on pd.payment_type_id = pt.id left join m_appuser au on au.id= tr." + CREATED_BY_DB_FIELD - + " left join m_note nt ON nt.savings_account_transaction_id=tr.id "; - } - - public String schema() { - return SCHEMA; - } - - public String select() { - return SELECT; - } - - public String from() { - return FROM; - } - - @Override - public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - final Long id = rs.getLong("transactionId"); - final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType"); - final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(transactionTypeInt); - - final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate"); - final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); - final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount"); - final Long releaseTransactionId = rs.getLong("releaseTransactionId"); - final String reasonForBlock = rs.getString("reasonForBlock"); - final BigDecimal outstandingChargeAmount = null; - final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "runningBalance"); - final boolean reversed = rs.getBoolean("reversed"); - final boolean isReversal = rs.getBoolean("isReversal"); - final Long originalTransactionId = rs.getLong("originalTransactionId"); - final Boolean lienTransaction = rs.getBoolean("lienTransaction"); - - final Long savingsId = rs.getLong("savingsId"); - final String accountNo = rs.getString("accountNo"); - final boolean postInterestAsOn = rs.getBoolean("postInterestAsOn"); - - PaymentDetailData paymentDetailData = null; - if (transactionType.isDepositOrWithdrawal()) { - final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType"); - if (paymentTypeId != null) { - final String typeName = rs.getString("paymentTypeName"); - final PaymentTypeData paymentType = PaymentTypeData.builder().id(paymentTypeId).name(typeName).build(); - final String accountNumber = rs.getString("accountNumber"); - final String checkNumber = rs.getString("checkNumber"); - final String routingCode = rs.getString("routingCode"); - final String receiptNumber = rs.getString("receiptNumber"); - final String bankNumber = rs.getString("bankNumber"); - paymentDetailData = new PaymentDetailData(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber, - bankNumber); - } - } - - 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); - - AccountTransferData transfer = null; - final Long fromTransferId = JdbcSupport.getLong(rs, "fromTransferId"); - final Long toTransferId = JdbcSupport.getLong(rs, "toTransferId"); - if (fromTransferId != null) { - final LocalDate fromTransferDate = JdbcSupport.getLocalDate(rs, "fromTransferDate"); - final BigDecimal fromTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fromTransferAmount"); - final boolean fromTransferReversed = rs.getBoolean("fromTransferReversed"); - final String fromTransferDescription = rs.getString("fromTransferDescription"); - - transfer = AccountTransferData.transferBasicDetails(fromTransferId, currency, fromTransferAmount, fromTransferDate, - fromTransferDescription, fromTransferReversed); - } else if (toTransferId != null) { - final LocalDate toTransferDate = JdbcSupport.getLocalDate(rs, "toTransferDate"); - final BigDecimal toTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "toTransferAmount"); - final boolean toTransferReversed = rs.getBoolean("toTransferReversed"); - final String toTransferDescription = rs.getString("toTransferDescription"); - - transfer = AccountTransferData.transferBasicDetails(toTransferId, currency, toTransferAmount, toTransferDate, - toTransferDescription, toTransferReversed); - } - final String submittedByUsername = rs.getString("submittedByUsername"); - final String note = rs.getString("transactionNote"); - return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency, - amount, outstandingChargeAmount, runningBalance, reversed, transfer, submittedOnDate, postInterestAsOn, - submittedByUsername, note, isReversal, originalTransactionId, lienTransaction, releaseTransactionId, reasonForBlock); - } - } - - private static final class SavingsAccountTransactionTemplateMapper implements RowMapper { - - private final String schemaSql; - - SavingsAccountTransactionTemplateMapper() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("sa.id as id, sa.account_no as accountNo, "); - sqlBuilder.append( - "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, 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("sa.min_required_opening_balance as minRequiredOpeningBalance "); - sqlBuilder.append("from m_savings_account sa "); - sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); - - this.schemaSql = sqlBuilder.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long savingsId = rs.getLong("id"); - final String accountNo = rs.getString("accountNo"); - - 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 SavingsAccountTransactionData.template(savingsId, accountNo, DateUtils.getBusinessLocalDate(), currency); - } - } - - @Override - public Collection retrieveForLookup(Long clientId, Boolean overdraft) { - - SavingAccountMapperForLookup accountMapperForLookup = new SavingAccountMapperForLookup(); - final StringBuilder sqlBuilder = new StringBuilder("select " + accountMapperForLookup.schema()); - sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300"); - Object[] queryParameters = null; - if (overdraft == null) { - queryParameters = new Object[] { clientId }; - } else { - sqlBuilder.append(" and sa.allow_overdraft = ?"); - queryParameters = new Object[] { clientId, overdraft }; - } - return this.jdbcTemplate.query(sqlBuilder.toString(), accountMapperForLookup, queryParameters); - - } - - @Override - public List retrieveSavingsIdsPendingInactive(LocalDate tenantLocalDate) { - List ret = null; - StringBuilder sql = new StringBuilder("select sa.id "); - sql.append(" from m_savings_account as sa "); - sql.append(" inner join m_savings_product as sp on (sa.product_id = sp.id and sp.is_dormancy_tracking_active = true) "); - sql.append(" where sa.status_enum = 300 "); - sql.append(" and sa.sub_status_enum = 0 "); - String compareDate = "(select COALESCE(max(sat.transaction_date), sa.activatedon_date) " - + "from m_savings_account_transaction as sat where sat.is_reversed = false and sat.is_reversal = false" - + " and sat.transaction_type_enum in (1,2) and sat.savings_account_id = sa.id)"; - sql.append(" and ").append(sqlGenerator.dateDiff("?", compareDate)).append(" >= sp.days_to_inactive "); - - try { - ret = this.jdbcTemplate.queryForList(sql.toString(), new Object[] { tenantLocalDate }, Long.class); - } catch (EmptyResultDataAccessException e) { - // ignore empty result scenario - } catch (DataAccessException e) { - throw e; - } - - return ret; - } - - @Override - public List retrieveSavingsIdsPendingDormant(LocalDate tenantLocalDate) { - List ret = null; - StringBuilder sql = new StringBuilder("select sa.id "); - sql.append(" from m_savings_account as sa "); - sql.append(" inner join m_savings_product as sp on (sa.product_id = sp.id and sp.is_dormancy_tracking_active = true) "); - sql.append(" where sa.status_enum = 300 "); - sql.append(" and sa.sub_status_enum = 100 "); - sql.append(" and " + sqlGenerator.dateDiff("?", - "(select COALESCE(max(sat.transaction_date),sa.activatedon_date) from m_savings_account_transaction as sat where sat.is_reversed = false and sat.is_reversal = false and sat.transaction_type_enum in (1,2) and sat.savings_account_id = sa.id)") - + " "); - sql.append(" >= sp.days_to_dormancy "); - - try { - ret = this.jdbcTemplate.queryForList(sql.toString(), new Object[] { tenantLocalDate }, Long.class); - } catch (EmptyResultDataAccessException e) { - // ignore empty result scenario - } catch (DataAccessException e) { - throw e; - } - - return ret; - } - - @Override - public List retrieveSavingsIdsPendingEscheat(LocalDate tenantLocalDate) { - List ret = null; - StringBuilder sql = new StringBuilder("select sa.id "); - sql.append(" from m_savings_account as sa "); - sql.append(" inner join m_savings_product as sp on (sa.product_id = sp.id and sp.is_dormancy_tracking_active = true) "); - sql.append(" where sa.status_enum = 300 "); - sql.append(" and sa.sub_status_enum = 200 "); - sql.append(" and " + sqlGenerator.dateDiff("?", - "(select COALESCE(max(sat.transaction_date),sa.activatedon_date) from m_savings_account_transaction as sat where sat.is_reversed = false and sat.is_reversal = false and sat.transaction_type_enum in (1,2) and sat.savings_account_id = sa.id)") - + " "); - sql.append(" >= sp.days_to_escheat "); - - try { - ret = this.jdbcTemplate.queryForList(sql.toString(), Long.class, new Object[] { tenantLocalDate }); - } catch (EmptyResultDataAccessException e) { - // ignore empty result scenario - } catch (DataAccessException e) { - throw e; - } - - return ret; - } - - @Override - public boolean isAccountBelongsToClient(final Long clientId, final Long accountId, final DepositAccountType depositAccountType, - final String currencyCode) { - try { - final StringBuilder buff = new StringBuilder("select count(*) from m_savings_account sa "); - buff.append( - " where sa.id = ? and sa.client_id = ? and sa.deposit_type_enum = ? and sa.currency_code = ? and sa.status_enum = 300"); - return this.jdbcTemplate.queryForObject(buff.toString(), Integer.class, accountId, clientId, depositAccountType.getValue(), - currencyCode) > 0; - } catch (final EmptyResultDataAccessException e) { - throw new SavingsAccountNotFoundException(accountId, e); - } - } - /* - * private static final class SavingsAccountAnnualFeeMapper implements RowMapper { - * - * private final String schemaSql; - * - * public SavingsAccountAnnualFeeMapper() { final StringBuilder sqlBuilder = new StringBuilder(200); - * sqlBuilder.append("sa.id as id, sa.account_no as accountNo, "); sqlBuilder - * .append("sa.annual_fee_next_due_date as annualFeeNextDueDate "); sqlBuilder.append("from m_savings_account sa "); - * - * this.schemaSql = sqlBuilder.toString(); } - * - * public String schema() { return this.schemaSql; } - * - * @Override public SavingsAccountAnnualFeeData mapRow(final ResultSet rs, - * - * @SuppressWarnings("unused") final int rowNum) throws SQLException { - * - * final Long id = rs.getLong("id"); final String accountNo = rs.getString("accountNo"); final LocalDate - * annualFeeNextDueDate = JdbcSupport.getLocalDate(rs, "annualFeeNextDueDate"); - * - * return SavingsAccountAnnualFeeData.instance(id, accountNo, annualFeeNextDueDate); } } - */ - - @Override - public String retrieveAccountNumberByAccountId(Long accountId) { - try { - final String sql = "select s.account_no from m_savings_account s where s.id = ?"; - return this.jdbcTemplate.queryForObject(sql, String.class, accountId); - } catch (final EmptyResultDataAccessException e) { - throw new SavingsAccountNotFoundException(accountId, e); - } - } - - @Override - public List getAccountsIdsByStatusPaged(Integer status, int pageSize, Long maxSavingsIdInList) { - String sql = new StringBuilder().append(" SELECT sa.id FROM m_savings_account sa ") - .append(" where sa.id > ? and sa.status_enum = ? ").append(" order by sa.id limit ?").toString(); - - try { - return this.jdbcTemplate.queryForList(sql, Long.class, new Object[] { maxSavingsIdInList, status, pageSize }); - } catch (EmptyResultDataAccessException e) { - return new ArrayList<>(); - } - } - - @Override - public Long retrieveAccountIdByExternalId(final ExternalId externalId) { - return savingsAccountRepositoryWrapper.findIdByExternalId(externalId); - } - - @Override - public List retrievePeriodicAccrualData(LocalDate tillDate, SavingsAccount savings) { - Long savingsId = (savings != null) ? savings.getId() : null; - Integer status = SavingsAccountStatusType.ACTIVE.getValue(); - Integer accountingRule = AccountingRuleType.ACCRUAL_PERIODIC.getValue(); - - return this.savingsAccountRepositoryWrapper.findAccrualData(tillDate, savingsId, status, accountingRule); - } -} +/** + * 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 static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY_DB_FIELD; +import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_DATE_DB_FIELD; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.accounting.common.AccountingRuleType; +import org.apache.fineract.accounting.glaccount.data.GLAccountData; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.domain.JdbcSupport; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.infrastructure.core.service.PaginationHelper; +import org.apache.fineract.infrastructure.core.service.SearchParameters; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.utils.ColumnValidator; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.portfolio.account.data.AccountTransferData; +import org.apache.fineract.portfolio.client.data.ClientData; +import org.apache.fineract.portfolio.group.data.GroupGeneralData; +import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData; +import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; +import org.apache.fineract.portfolio.savings.DepositAccountType; +import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType; +import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType; +import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; +import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; +import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; +import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountSubStatusEnumData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; +import org.apache.fineract.portfolio.savings.data.SavingsAccrualData; +import org.apache.fineract.portfolio.savings.data.SavingsProductData; +import org.apache.fineract.portfolio.savings.domain.SavingsAccount; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargesPaidByData; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountSubStatusEnum; +import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException; +import org.apache.fineract.portfolio.tax.data.TaxComponentData; +import org.apache.fineract.portfolio.tax.data.TaxDetailsData; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; +import org.apache.fineract.useradministration.domain.AppUser; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowMapper; + +public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountReadPlatformService { + + private final PlatformSecurityContext context; + private final JdbcTemplate jdbcTemplate; + private final DatabaseSpecificSQLGenerator sqlGenerator; + + // mappers + private final SavingsAccountTransactionTemplateMapper transactionTemplateMapper; + protected SavingsAccountTransactionsMapper transactionsMapper; + private final SavingsAccountTransactionsForBatchMapper savingsAccountTransactionsForBatchMapper; + private final SavingAccountMapper savingAccountMapper; + private final SavingAccountMapperForInterestPosting savingAccountMapperForInterestPosting; + // private final SavingsAccountAnnualFeeMapper annualFeeMapper; + + // pagination + private final PaginationHelper paginationHelper; + + private final ColumnValidator columnValidator; + private final SavingsAccountAssembler savingAccountAssembler; + + private final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper; + + public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate, + final SavingsAccountAssembler savingAccountAssembler, PaginationHelper paginationHelper, ColumnValidator columnValidator, + DatabaseSpecificSQLGenerator sqlGenerator, SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper) { + this.context = context; + this.jdbcTemplate = jdbcTemplate; + this.sqlGenerator = sqlGenerator; + this.savingsAccountRepositoryWrapper = savingsAccountRepositoryWrapper; + this.transactionTemplateMapper = new SavingsAccountTransactionTemplateMapper(); + this.transactionsMapper = new SavingsAccountTransactionsMapper(); + this.savingsAccountTransactionsForBatchMapper = new SavingsAccountTransactionsForBatchMapper(); + this.savingAccountMapper = new SavingAccountMapper(); + this.columnValidator = columnValidator; + this.paginationHelper = paginationHelper; + this.savingAccountMapperForInterestPosting = new SavingAccountMapperForInterestPosting(); + this.savingAccountAssembler = savingAccountAssembler; + } + + @Override + public Collection retrieveAllForLookup(final Long clientId) { + + final StringBuilder sqlBuilder = new StringBuilder("select " + this.savingAccountMapper.schema()); + sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300 "); + + final Object[] queryParameters = new Object[] { clientId }; + return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters); + } + + @Override + public Collection retrieveActiveForLookup(final Long clientId, DepositAccountType depositAccountType) { + + final StringBuilder sqlBuilder = new StringBuilder("select " + this.savingAccountMapper.schema()); + sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300 and sa.deposit_type_enum = ? "); + + final Object[] queryParameters = new Object[] { clientId, depositAccountType.getValue() }; + return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters); + } + + @Override + public Collection retrieveActiveForLookup(final Long clientId, DepositAccountType depositAccountType, + String currencyCode) { + final StringBuilder sqlBuilder = new StringBuilder("select " + this.savingAccountMapper.schema()); + sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300 and sa.deposit_type_enum = ? and sa.currency_code = ? "); + + final Object[] queryParameters = new Object[] { clientId, depositAccountType.getValue(), currencyCode }; + return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters); + } + + @Override + public Page retrieveAll(final SearchParameters searchParameters) { + + final AppUser currentUser = this.context.authenticatedUser(); + final String hierarchy = currentUser.getOffice().getHierarchy(); + final String hierarchySearchString = hierarchy + "%"; + + final StringBuilder sqlBuilder = new StringBuilder(200); + sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); + sqlBuilder.append(this.savingAccountMapper.schema()); + + sqlBuilder.append(" join m_office o on o.id = c.office_id"); + sqlBuilder.append(" where o.hierarchy like ?"); + + final Object[] objectArray = new Object[10]; + objectArray[0] = hierarchySearchString; + int arrayPos = 1; + if (searchParameters != null) { + + if (StringUtils.isNotBlank(searchParameters.getStatus())) { + sqlBuilder.append(" and sa.status_enum = ?"); + objectArray[arrayPos] = Integer.parseInt(searchParameters.getStatus()); + arrayPos = arrayPos + 1; + } + + if (StringUtils.isNotBlank(searchParameters.getExternalId())) { + sqlBuilder.append(" and sa.external_id = ?"); + objectArray[arrayPos] = searchParameters.getExternalId(); + arrayPos = arrayPos + 1; + } + if (searchParameters.getOfficeId() != null) { + sqlBuilder.append(" and c.office_id = ?"); + objectArray[arrayPos++] = searchParameters.getOfficeId(); + } + if (StringUtils.isNotBlank(searchParameters.getAccountNo())) { + sqlBuilder.append(" and sa.account_no like ?"); + objectArray[arrayPos++] = "%" + searchParameters.getAccountNo() + "%"; + } + if (StringUtils.isNotBlank(searchParameters.getName())) { + sqlBuilder.append(" and c.display_name like ?"); + objectArray[arrayPos++] = "%" + searchParameters.getName() + "%"; + } + if (searchParameters.getDepositType() != null) { + sqlBuilder.append(" and sa.deposit_type_enum = ?"); + objectArray[arrayPos++] = searchParameters.getDepositType(); + } + if (searchParameters.hasOrderBy()) { + sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); + this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); + + if (searchParameters.hasSortOrder()) { + sqlBuilder.append(' ').append(searchParameters.getSortOrder()); + this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); + } + } + + if (searchParameters.hasLimit()) { + sqlBuilder.append(" "); + if (searchParameters.hasOffset()) { + sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); + } else { + sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); + } + } + } + final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos); + return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), finalObjectArray, this.savingAccountMapper); + } + + @Override + public SavingsAccountData retrieveOne(final Long accountId) { + + try { + final String sql = "select " + this.savingAccountMapper.schema() + " where sa.id = ?"; + + final SavingsAccountData accountData = this.jdbcTemplate.queryForObject(sql, this.savingAccountMapper, + new Object[] { accountId }); // NOSONAR + + // Load joint holders + if (accountData != null) { + final String jointSql = "SELECT jh.id as id, jh.client_id as clientId, jh.cif_number as cifNumber, " + + "jh.display_name as displayName, jh.share_percentage as sharePercentage, " + + "jh.signing_authority as signingAuthority, jh.role as role, jh.is_primary as isPrimary " + + "FROM m_savings_account_joint_holders jh WHERE jh.savings_account_id = ?"; + final List> jointHolders = this.jdbcTemplate.queryForList(jointSql, accountId); + if (!jointHolders.isEmpty()) { + accountData.setJointHolders(jointHolders); + } + } + + return accountData; + } catch (final EmptyResultDataAccessException e) { + throw new SavingsAccountNotFoundException(accountId, e); + } + } + + @Override + public List retrieveAllTransactionData(final List refNo) throws DataAccessException { + String inSql = String.join(",", Collections.nCopies(refNo.size(), "?")); + String sql = "select " + this.savingsAccountTransactionsForBatchMapper.schema() + " where tr.ref_no in (%s)"; + Object[] params = new Object[refNo.size()]; + int i = 0; + for (String element : refNo) { + params[i] = element; + i++; + } + return this.jdbcTemplate.query(String.format(sql, inSql), this.savingsAccountTransactionsForBatchMapper, params); + } + + @Override + public List retrieveAllSavingsDataForInterestPosting(final boolean backdatedTxnsAllowedTill, final int pageSize, + final Integer status, final Long maxSavingsId) { + LocalDate yesterday = DateUtils.getBusinessLocalDate().minusDays(1); + String sql = "select " + this.savingAccountMapperForInterestPosting.schema() + + "join (select a.id from m_savings_account a where a.id > ? and a.status_enum = ? limit ?) b on b.id = sa.id "; + if (backdatedTxnsAllowedTill) { + sql = sql + + "where (CASE WHEN sa.interest_posted_till_date is not null THEN tr.transaction_date >= sa.interest_posted_till_date ELSE tr.transaction_date >= sa.activatedon_date END) "; + } + + sql = sql + " and (sa.interest_posted_till_date is null or sa.interest_posted_till_date <= ? ) "; + // #audit backward compatibility + sql = sql + " order by sa.id, tr.transaction_date, tr." + CREATED_DATE_DB_FIELD + ", tr.created_date, tr.id"; + + List savingsAccountDataList = this.jdbcTemplate.query(sql, this.savingAccountMapperForInterestPosting, // NOSONAR + new Object[] { maxSavingsId, status, pageSize, yesterday }); + for (SavingsAccountData savingsAccountData : savingsAccountDataList) { + this.savingAccountAssembler.assembleSavings(savingsAccountData); + } + return savingsAccountDataList; + } + + private static final class SavingAccountMapperForInterestPosting implements ResultSetExtractor> { + + private final String schemaSql; + + SavingAccountMapperForInterestPosting() { + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, "); + sqlBuilder.append("sa.deposit_type_enum as depositType, "); + sqlBuilder.append("c.id as clientId, c.office_id as clientOfficeId, "); + sqlBuilder.append("g.id as groupId, g.office_id as groupOfficeId, "); + sqlBuilder.append("sa.status_enum as statusEnum, "); + sqlBuilder.append("sa.sub_status_enum as subStatusEnum, "); + sqlBuilder.append("sa.submittedon_date as submittedOnDate,"); + sqlBuilder.append("sa.rejectedon_date as rejectedOnDate,"); + sqlBuilder.append("sa.withdrawnon_date as withdrawnOnDate,"); + sqlBuilder.append("sa.approvedon_date as approvedOnDate,"); + sqlBuilder.append("sa.activatedon_date as activatedOnDate,"); + sqlBuilder.append("sa.closedon_date as closedOnDate,"); + sqlBuilder.append( + "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); + sqlBuilder.append("sa.nominal_annual_interest_rate as nominalAnnualInterestRate, "); + sqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, "); + sqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, "); + sqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, "); + sqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, "); + sqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, "); + sqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,"); + sqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); + sqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); + sqlBuilder.append("sa.allow_overdraft as allowOverdraft, "); + sqlBuilder.append("sa.overdraft_limit as overdraftLimit, "); + sqlBuilder.append("sa.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, "); + sqlBuilder.append("sa.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, "); + sqlBuilder.append("sa.total_deposits_derived as totalDeposits, "); + sqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, "); + sqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, "); + sqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, "); + sqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, "); + sqlBuilder.append("sa.total_interest_posted_derived as totalInterestPosted, "); + sqlBuilder.append("sa.total_overdraft_interest_derived as totalOverdraftInterestDerived, "); + sqlBuilder.append("sa.account_balance_derived as accountBalance, "); + sqlBuilder.append("sa.total_fees_charge_derived as totalFeeCharge, "); + sqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, "); + sqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation,"); + sqlBuilder.append("sa.min_required_balance as minRequiredBalance, "); + sqlBuilder.append("sa.enforce_min_required_balance as enforceMinRequiredBalance, "); + sqlBuilder.append("sa.max_allowed_lien_limit as maxAllowedLienLimit, "); + sqlBuilder.append("sa.is_lien_allowed as lienAllowed, "); + sqlBuilder.append("sa.on_hold_funds_derived as onHoldFunds, "); + sqlBuilder.append("sa.withhold_tax as withHoldTax, "); + sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax, "); + sqlBuilder.append("sa.last_interest_calculation_date as lastInterestCalculationDate, "); + sqlBuilder.append("sa.total_savings_amount_on_hold as onHoldAmount, "); + sqlBuilder.append("sa.interest_posted_till_date as interestPostedTillDate, "); + sqlBuilder.append("tg.id as taxGroupId, "); + sqlBuilder.append("(select COALESCE(max(sat.transaction_date),sa.activatedon_date) "); + sqlBuilder.append("from m_savings_account_transaction as sat "); + sqlBuilder.append("where sat.is_reversed = false and sat.is_reversal = false "); + sqlBuilder.append("and sat.transaction_type_enum in (1,2) "); + sqlBuilder.append("and sat.savings_account_id = sa.id) as lastActiveTransactionDate, "); + sqlBuilder.append("sp.id as productId, "); + sqlBuilder.append("sp.is_dormancy_tracking_active as isDormancyTrackingActive, "); + sqlBuilder.append("sp.days_to_inactive as daysToInactive, "); + sqlBuilder.append("sp.days_to_dormancy as daysToDormancy, "); + sqlBuilder.append("sp.days_to_escheat as daysToEscheat, "); + sqlBuilder.append("sp.accounting_type as accountingType, "); + sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, "); + sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,"); + sqlBuilder.append("tr.submitted_on_date as transSubmittedOnDate,tr.cumulative_balance_derived as cumulativeBalance,"); + sqlBuilder.append("tr.running_balance_derived as runningBalance, tr.is_reversed as reversed,"); + sqlBuilder.append("tr.balance_end_date_derived as balanceEndDate, tr.overdraft_amount_derived as overdraftAmount,"); + sqlBuilder.append("tr.is_manual as manualTransaction,tr.office_id as officeId, "); + sqlBuilder.append("pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, "); + sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, "); + sqlBuilder.append("pt.value as paymentTypeName, "); + sqlBuilder.append("msacpb.amount as paidByAmount, msacpb.id as chargesPaidById, "); + sqlBuilder.append( + "msac.id as chargeId, msac.amount as chargeAmount, msac.charge_time_enum as chargeTimeType, msac.is_penalty as isPenaltyCharge, "); + sqlBuilder.append("txd.id as taxDetailsId, txd.amount as taxAmount, "); + sqlBuilder.append("apm.gl_account_id as glAccountIdForInterestOnSavings, apm1.gl_account_id as glAccountIdForSavingsControl, "); + sqlBuilder.append( + "apm2.gl_account_id as glAccountIdForInterestReceivable,apm3.gl_account_id as glAccountIdForOverdraftPorfolio, "); + sqlBuilder.append("apm4.gl_account_id as glAccountIdForInterestPayable, "); + sqlBuilder.append( + "mtc.id as taxComponentId, mtc.debit_account_id as debitAccountId, mtc.credit_account_id as creditAccountId, mtc.percentage as taxPercentage "); + 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("join m_savings_account_transaction tr on sa.id = tr.savings_account_id "); + sqlBuilder.append("left join m_payment_detail pd on pd.id = tr.payment_detail_id "); + sqlBuilder.append("left join m_payment_type pt on pt.id = pd.payment_type_id "); + sqlBuilder.append("left join m_savings_account_charge_paid_by msacpb on msacpb.savings_account_transaction_id = tr.id "); + sqlBuilder.append("left join m_savings_account_charge msac on msac.id = msacpb.savings_account_charge_id "); + 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_tax_group tg on tg.id = sa.tax_group_id "); + sqlBuilder.append("left join m_savings_account_transaction_tax_details txd on txd.savings_transaction_id = tr.id "); + sqlBuilder.append("left join m_tax_component mtc on mtc.id = txd.tax_component_id "); + sqlBuilder.append( + "left join acc_product_mapping apm on apm.product_type = 2 and apm.product_id = sp.id and apm.financial_account_type=3 "); + sqlBuilder.append( + "left join acc_product_mapping apm1 on apm1.product_type = 2 and apm1.product_id = sp.id and apm1.financial_account_type=2 "); + sqlBuilder.append("left join acc_product_mapping apm2 on apm2.product_id = sp.id and apm2.financial_account_type=18 "); + sqlBuilder.append("left join acc_product_mapping apm3 on apm3.product_id = sp.id and apm3.financial_account_type = 11 "); + sqlBuilder.append("left join acc_product_mapping apm4 on apm4.product_id = sp.id and apm4.financial_account_type = 17 "); + + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public List extractData(final ResultSet rs) throws SQLException { + + List savingsAccountDataList = new ArrayList<>(); + HashMap savingsMap = new HashMap<>(); + String currencyCode = null; + Integer currencyDigits = null; + Integer inMultiplesOf = null; + CurrencyData currency = null; + HashMap transMap = new HashMap<>(); + HashMap taxDetails = new HashMap<>(); + HashMap chargeDetails = new HashMap<>(); + SavingsAccountTransactionData savingsAccountTransactionData = null; + SavingsAccountData savingsAccountData = null; + int count = 0; + + while (rs.next()) { + final Long id = rs.getLong("id"); + final Long transactionId = rs.getLong("transactionId"); + final Long taxDetailId = JdbcSupport.getLongDefaultToNullIfZero(rs, "taxDetailsId"); + final Long taxComponentId = JdbcSupport.getLongDefaultToNullIfZero(rs, "taxComponentId"); + final String accountNo = rs.getString("accountNo"); + final Long chargeId = rs.getLong("chargeId"); + + if (!savingsMap.containsValue(id)) { + if (count > 0) { + savingsAccountDataList.add(savingsAccountData); + } + count++; + savingsMap.put("id", id); + + final String externalId = rs.getString("externalId"); + final Integer depositTypeId = rs.getInt("depositType"); + final EnumOptionData depositType = SavingsEnumerations.depositType(depositTypeId); + final Long groupId = JdbcSupport.getLong(rs, "groupId"); + final Long groupOfficeId = JdbcSupport.getLong(rs, "groupOfficeId"); + final GroupGeneralData groupGeneralData = new GroupGeneralData(groupId, groupOfficeId); + + final Long clientId = JdbcSupport.getLong(rs, "clientId"); + final Long clientOfficeId = JdbcSupport.getLong(rs, "clientOfficeId"); + final ClientData clientData = ClientData.createClientForInterestPosting(clientId, clientOfficeId); + + final Long glAccountIdForInterestOnSavings = rs.getLong("glAccountIdForInterestOnSavings"); + final Long glAccountIdForSavingsControl = rs.getLong("glAccountIdForSavingsControl"); + + final Long glAccountIdForOverdraftPorfolio = rs.getLong("glAccountIdForOverdraftPorfolio"); + final Long glAccountIdForInterestReceivable = rs.getLong("glAccountIdForInterestReceivable"); + + final Long glAccountIdForInterestPayable = rs.getLong("glAccountIdForInterestPayable"); + + final Long productId = rs.getLong("productId"); + final Integer accountType = rs.getInt("accountingType"); + final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountType); + final EnumOptionData enumOptionDataForAccounting = new EnumOptionData(accountType.longValue(), + accountingRuleType.getCode(), accountingRuleType.getValue().toString()); + final SavingsProductData savingsProductData = SavingsProductData.createForInterestPosting(productId, + enumOptionDataForAccounting); + + final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum"); + final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum); + final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum"); + final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum); + final LocalDate lastActiveTransactionDate = JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate"); + final boolean isDormancyTrackingActive = rs.getBoolean("isDormancyTrackingActive"); + final Integer numDaysToInactive = JdbcSupport.getInteger(rs, "daysToInactive"); + final Integer numDaysToDormancy = JdbcSupport.getInteger(rs, "daysToDormancy"); + final Integer numDaysToEscheat = JdbcSupport.getInteger(rs, "daysToEscheat"); + Integer daysToInactive = null; + Integer daysToDormancy = null; + Integer daysToEscheat = null; + + LocalDate currentDate = DateUtils.getBusinessLocalDate(); + if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) { + if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) { + daysToEscheat = DateUtils.getExactDifferenceInDays(currentDate, + lastActiveTransactionDate.plusDays(numDaysToEscheat)); + } + if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) { + daysToDormancy = DateUtils.getExactDifferenceInDays(currentDate, + lastActiveTransactionDate.plusDays(numDaysToDormancy)); + } + if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) { + daysToInactive = DateUtils.getExactDifferenceInDays(currentDate, + lastActiveTransactionDate.plusDays(numDaysToInactive)); + } + } + final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); + final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate"); + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); + final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate"); + final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); + final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, null, + null, null, null, null, null, null, withdrawnOnDate, null, null, null, approvedOnDate, null, null, null, + activatedOnDate, null, null, null, closedOnDate, null, null, null); + + currencyCode = rs.getString("currencyCode"); + currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + currency = new CurrencyData(currencyCode, currencyDigits, inMultiplesOf); + + final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits"); + final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals"); + final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawalFees"); + final BigDecimal totalAnnualFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalAnnualFees"); + + final BigDecimal totalInterestEarned = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestEarned"); + final BigDecimal totalInterestPosted = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalInterestPosted"); + final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accountBalance"); + final BigDecimal totalFeeCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalFeeCharge"); + final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge"); + final BigDecimal totalOverdraftInterestDerived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, + "totalOverdraftInterestDerived"); + final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax"); + final LocalDate interestPostedTillDate = JdbcSupport.getLocalDate(rs, "interestPostedTillDate"); + + final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "minBalanceForInterestCalculation"); + final BigDecimal onHoldFunds = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldFunds"); + + final BigDecimal onHoldAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldAmount"); + + BigDecimal availableBalance = accountBalance; + if (availableBalance != null && onHoldFunds != null) { + + availableBalance = availableBalance.subtract(onHoldFunds); + } + + if (availableBalance != null && onHoldAmount != null) { + + availableBalance = availableBalance.subtract(onHoldAmount); + } + + BigDecimal interestNotPosted = BigDecimal.ZERO; + LocalDate lastInterestCalculationDate = null; + if (totalInterestEarned != null) { + interestNotPosted = totalInterestEarned.subtract(totalInterestPosted).add(totalOverdraftInterestDerived); + lastInterestCalculationDate = JdbcSupport.getLocalDate(rs, "lastInterestCalculationDate"); + } + + final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, + totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge, + totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax, interestNotPosted, + lastInterestCalculationDate, availableBalance, interestPostedTillDate); + summary.setPrevInterestPostedTillDate(interestPostedTillDate); + + final boolean withHoldTax = rs.getBoolean("withHoldTax"); + final Long taxGroupId = JdbcSupport.getLongDefaultToNullIfZero(rs, "taxGroupId"); + TaxGroupData taxGroupData = null; + if (taxGroupId != null) { + taxGroupData = TaxGroupData.lookup(taxGroupId, null); + } + + final BigDecimal nominalAnnualInterestRate = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "nominalAnnualInterestRate"); + + final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations.compoundingInterestPeriodType( + SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, "interestCompoundingPeriodType"))); + + final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType( + SavingsPostingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, "interestPostingPeriodType"))); + + final EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType( + SavingsInterestCalculationType.fromInt(JdbcSupport.getInteger(rs, "interestCalculationType"))); + + final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations + .interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType + .fromInt(JdbcSupport.getInteger(rs, "interestCalculationDaysInYearType"))); + + final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "minRequiredOpeningBalance"); + + final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency"); + EnumOptionData lockinPeriodFrequencyType = null; + final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType"); + if (lockinPeriodFrequencyTypeValue != null) { + final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType + .fromInt(lockinPeriodFrequencyTypeValue); + lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType); + } + + final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers"); + + final boolean allowOverdraft = rs.getBoolean("allowOverdraft"); + final BigDecimal overdraftLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "overdraftLimit"); + final BigDecimal nominalAnnualInterestRateOverdraft = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "nominalAnnualInterestRateOverdraft"); + final BigDecimal minOverdraftForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "minOverdraftForInterestCalculation"); + + final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance"); + final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance"); + final BigDecimal maxAllowedLienLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxAllowedLienLimit"); + final boolean lienAllowed = rs.getBoolean("lienAllowed"); + savingsAccountData = SavingsAccountData.instance(id, accountNo, depositType, externalId, null, null, null, null, + productId, null, null, null, status, subStatus, null, timeline, currency, nominalAnnualInterestRate, + interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, + interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, + withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance, + enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, onHoldFunds, + nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroupData, + lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, + onHoldAmount); + + savingsAccountData.setClientData(clientData); + savingsAccountData.setGroupGeneralData(groupGeneralData); + savingsAccountData.setSavingsProduct(savingsProductData); + + savingsAccountData.setGlAccountIdForInterestReceivable(glAccountIdForInterestReceivable); + savingsAccountData.setGlAccountIdForOverdraftPorfolio(glAccountIdForOverdraftPorfolio); + + savingsAccountData.setGlAccountIdForInterestPayable(glAccountIdForInterestPayable); + + savingsAccountData.setGlAccountIdForInterestOnSavings(glAccountIdForInterestOnSavings); + savingsAccountData.setGlAccountIdForSavingsControl(glAccountIdForSavingsControl); + } + + if (!transMap.containsValue(transactionId)) { + + final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType"); + final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(transactionTypeInt); + + final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate"); + final LocalDate balanceEndDate = JdbcSupport.getLocalDate(rs, "balanceEndDate"); + final LocalDate transSubmittedOnDate = JdbcSupport.getLocalDate(rs, "transSubmittedOnDate"); + final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount"); + final BigDecimal overdraftAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "overdraftAmount"); + final BigDecimal outstandingChargeAmount = null; + final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "runningBalance"); + final boolean reversed = rs.getBoolean("reversed"); + final Long officeId = rs.getLong("officeId"); + final BigDecimal cumulativeBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "cumulativeBalance"); + + final boolean postInterestAsOn = false; + + PaymentDetailData paymentDetailData = null; + if (transactionType.isDepositOrWithdrawal()) { + final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType"); + if (paymentTypeId != null) { + final String typeName = rs.getString("paymentTypeName"); + final PaymentTypeData paymentTypeData = new PaymentTypeData(paymentTypeId, typeName, null, false, null, null, + false); + paymentDetailData = new PaymentDetailData(id, paymentTypeData, null, null, null, null, null); + } + } + + savingsAccountTransactionData = SavingsAccountTransactionData.create(transactionId, transactionType, paymentDetailData, + id, accountNo, date, currency, amount, outstandingChargeAmount, runningBalance, reversed, transSubmittedOnDate, + postInterestAsOn, cumulativeBalance, balanceEndDate); + savingsAccountTransactionData.setOverdraftAmount(overdraftAmount); + + transMap.put("id", transactionId); + if (savingsAccountData.getOfficeId() == null) { + savingsAccountData.setOfficeId(officeId); + } + + savingsAccountData.setSavingsAccountTransactionData(savingsAccountTransactionData); + } + + if (chargeId != null && !chargeDetails.containsValue(chargeId)) { + final boolean isPenalty = rs.getBoolean("isPenaltyCharge"); + final BigDecimal chargeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "chargeAmount"); + final Integer chargesTimeType = rs.getInt("chargeTimeType"); + final EnumOptionData enumOptionDataForChargesTimeType = new EnumOptionData(chargesTimeType.longValue(), null, null); + final SavingsAccountChargeData savingsAccountChargeData = new SavingsAccountChargeData(chargeId, chargeAmount, + enumOptionDataForChargesTimeType, isPenalty); + + final Long chargesPaidById = rs.getLong("chargesPaidById"); + final BigDecimal chargesPaid = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "paidByAmount"); + final SavingsAccountChargesPaidByData savingsAccountChargesPaidByData = new SavingsAccountChargesPaidByData( + chargesPaidById, chargesPaid); + savingsAccountChargesPaidByData.setSavingsAccountChargeData(savingsAccountChargeData); + if (savingsAccountChargesPaidByData != null) { + savingsAccountTransactionData.setChargesPaidByData(savingsAccountChargesPaidByData); + } + + chargeDetails.put("id", chargeId); + } + + if (taxDetailId != null && !taxDetails.containsValue(taxDetailId)) { + final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "taxAmount"); + final BigDecimal percentage = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "taxPercentage"); + final Long debitId = rs.getLong("debitAccountId"); + final Long creditId = rs.getLong("creditAccountId"); + final GLAccountData debitAccount = GLAccountData.createFrom(debitId); + final GLAccountData creditAccount = GLAccountData.createFrom(creditId); + + if (taxComponentId != null) { + final TaxComponentData taxComponent = TaxComponentData.createTaxComponent(taxComponentId, percentage, debitAccount, + creditAccount); + savingsAccountTransactionData.setTaxDetails(new TaxDetailsData(taxComponent, amount)); + } + + taxDetails.put("id", taxDetailId); + } + + } + if (savingsAccountData != null) { + savingsAccountDataList.add(savingsAccountData); + } + return savingsAccountDataList; + + } + } + + private static final class SavingAccountMapper implements RowMapper { + + private final String schemaSql; + + SavingAccountMapper() { + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, "); + sqlBuilder.append("sa.deposit_type_enum as depositType, "); + 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 fieldOfficerId, s.display_name as fieldOfficerName, "); + sqlBuilder.append("sa.status_enum as statusEnum, "); + sqlBuilder.append("sa.sub_status_enum as subStatusEnum, "); + sqlBuilder.append("sa.reason_for_block as reasonForBlock, "); + sqlBuilder.append("sa.submittedon_date as submittedOnDate,"); + sqlBuilder.append("sbu.username as submittedByUsername,"); + sqlBuilder.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,"); + + sqlBuilder.append("sa.rejectedon_date as rejectedOnDate,"); + sqlBuilder.append("rbu.username as rejectedByUsername,"); + sqlBuilder.append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname,"); + + sqlBuilder.append("sa.withdrawnon_date as withdrawnOnDate,"); + sqlBuilder.append("wbu.username as withdrawnByUsername,"); + sqlBuilder.append("wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname,"); + + sqlBuilder.append("sa.approvedon_date as approvedOnDate,"); + sqlBuilder.append("abu.username as approvedByUsername,"); + sqlBuilder.append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname,"); + + sqlBuilder.append("sa.activatedon_date as activatedOnDate,"); + sqlBuilder.append("avbu.username as activatedByUsername,"); + sqlBuilder.append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname,"); + + sqlBuilder.append("sa.closedon_date as closedOnDate,"); + sqlBuilder.append("cbu.username as closedByUsername,"); + sqlBuilder.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,"); + + sqlBuilder.append( + "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, 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("sa.nominal_annual_interest_rate as nominalAnnualInterestRate, "); + sqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, "); + sqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, "); + sqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, "); + sqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, "); + sqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, "); + sqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,"); + sqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); + + sqlBuilder.append("sa.allow_overdraft as allowOverdraft, "); + sqlBuilder.append("sa.overdraft_limit as overdraftLimit, "); + sqlBuilder.append("sa.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, "); + sqlBuilder.append("sa.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, "); + + sqlBuilder.append("sa.total_deposits_derived as totalDeposits, "); + sqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, "); + sqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, "); + sqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, "); + sqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, "); + sqlBuilder.append("sa.total_interest_posted_derived as totalInterestPosted, "); + sqlBuilder.append("sa.total_overdraft_interest_derived as totalOverdraftInterestDerived, "); + sqlBuilder.append("sa.account_balance_derived as accountBalance, "); + sqlBuilder.append("sa.total_fees_charge_derived as totalFeeCharge, "); + sqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, "); + sqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation,"); + sqlBuilder.append("sa.min_required_balance as minRequiredBalance, "); + sqlBuilder.append("sa.enforce_min_required_balance as enforceMinRequiredBalance, "); + sqlBuilder.append("sa.max_allowed_lien_limit as maxAllowedLienLimit, "); + sqlBuilder.append("sa.is_lien_allowed as lienAllowed, "); + sqlBuilder.append("sa.on_hold_funds_derived as onHoldFunds, "); + sqlBuilder.append("sa.withhold_tax as withHoldTax, "); + sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax, "); + sqlBuilder.append("sa.last_interest_calculation_date as lastInterestCalculationDate, "); + sqlBuilder.append("sa.interest_posted_till_date as interestPostedTillDate, "); + sqlBuilder.append("sa.total_savings_amount_on_hold as onHoldAmount, "); + sqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); + sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, "); + sqlBuilder.append("(select COALESCE(max(sat.transaction_date),sa.activatedon_date) "); + sqlBuilder.append("from m_savings_account_transaction as sat "); + sqlBuilder.append("where sat.is_reversed = false and sat.is_reversal = false "); + sqlBuilder.append("and sat.transaction_type_enum in (1,2) "); + sqlBuilder.append("and sat.savings_account_id = sa.id) as lastActiveTransactionDate, "); + sqlBuilder.append("sp.is_dormancy_tracking_active as isDormancyTrackingActive, "); + sqlBuilder.append("sp.days_to_inactive as daysToInactive, "); + sqlBuilder.append("sp.days_to_dormancy as daysToDormancy, "); + sqlBuilder.append("sp.days_to_escheat as daysToEscheat, "); + // CBS extended fields + sqlBuilder.append("sa.cbs_account_type as cbsAccountType, "); + sqlBuilder.append("sa.customer_segment as customerSegment, "); + sqlBuilder.append("sa.account_status as accountStatus, "); + sqlBuilder.append("sa.mode_of_operation as modeOfOperation, "); + sqlBuilder.append("sa.special_category as specialCategory, "); + sqlBuilder.append("sa.weaker_section as weakerSection, "); + sqlBuilder.append("sa.guardian_name as guardianName, "); + sqlBuilder.append("sa.guardian_cif_number as guardianCifNumber, "); + sqlBuilder.append("sa.guardian_relationship as guardianRelationship, "); + sqlBuilder.append("sa.minor_date_of_birth as minorDateOfBirth, "); + sqlBuilder.append("sa.guardian_client_id as guardianClientId, "); + sqlBuilder.append("sa.introducer_client_id as introducerClientId, "); + sqlBuilder.append("sa.introducer_name as introducerName, "); + sqlBuilder.append("sa.introducer_cif_number as introducerCifNumber, "); + sqlBuilder.append("sa.nominee_data as nomineeData, "); + sqlBuilder.append("sa.linked_account_id as linkedAccountId, "); + sqlBuilder.append("sa.linked_account_type as linkedAccountType, "); + sqlBuilder.append("sa.linked_account_no as linkedAccountNo, "); + sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, "); + sqlBuilder.append("sp.msme as msme, "); + sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, "); + sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, "); + // Facility config fields + sqlBuilder.append("safc.atm_facility as atmFacility, "); + sqlBuilder.append("safc.atm_withdrawal_facility as atmWithdrawalFacility, "); + sqlBuilder.append("safc.upi_facility as upiFacility, "); + sqlBuilder.append("safc.pos_transactions_facility as posTransactionsFacility, "); + sqlBuilder.append("safc.cheque_book as chequeBook, "); + sqlBuilder.append("safc.imps_facility as impsFacility, "); + sqlBuilder.append("safc.sms_alerts as smsAlerts, "); + sqlBuilder.append("safc.bbps_facility as bbpsFacility, "); + sqlBuilder.append("safc.insurance_cover as insuranceCover, "); + sqlBuilder.append("safc.bepg_facility as bepgFacility, "); + sqlBuilder.append("safc.neft_facility as neftFacility, "); + sqlBuilder.append("safc.rtgs_facility as rtgsFacility, "); + sqlBuilder.append("safc.inter_bank_total_cap_facility as interBankTotalCapFacility, "); + sqlBuilder.append("safc.withdrawal_facility as withdrawalFacility, "); + sqlBuilder.append("safc.deposit_facility_kyc as depositFacilityKyc, "); + sqlBuilder.append("safc.virtual_debit_card as virtualDebitCard "); + 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 "); + sqlBuilder.append("left join m_appuser sbu on sbu.id = sa.submittedon_userid "); + sqlBuilder.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid "); + sqlBuilder.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid "); + sqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid "); + sqlBuilder.append("left join m_appuser avbu on avbu.id = sa.activatedon_userid "); + sqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid "); + sqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id "); + sqlBuilder.append("left join m_savings_account_facility_config safc on safc.savings_account_id = sa.id "); + + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public SavingsAccountData 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 Integer depositTypeId = rs.getInt("depositType"); + final EnumOptionData depositType = SavingsEnumerations.depositType(depositTypeId); + + 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 Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum"); + final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum); + + final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum"); + final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum); + final String reasonForBlock = rs.getString("reasonForBlock"); + + final LocalDate lastActiveTransactionDate = JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate"); + final boolean isDormancyTrackingActive = rs.getBoolean("isDormancyTrackingActive"); + final Integer numDaysToInactive = JdbcSupport.getInteger(rs, "daysToInactive"); + final Integer numDaysToDormancy = JdbcSupport.getInteger(rs, "daysToDormancy"); + final Integer numDaysToEscheat = JdbcSupport.getInteger(rs, "daysToEscheat"); + Integer daysToInactive = null; + Integer daysToDormancy = null; + Integer daysToEscheat = null; + + LocalDate currentDate = DateUtils.getBusinessLocalDate(); + if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) { + if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) { + daysToEscheat = DateUtils.getExactDifferenceInDays(currentDate, lastActiveTransactionDate.plusDays(numDaysToEscheat)); + } + if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) { + daysToDormancy = DateUtils.getExactDifferenceInDays(currentDate, lastActiveTransactionDate.plusDays(numDaysToDormancy)); + } + if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) { + daysToInactive = DateUtils.getExactDifferenceInDays(currentDate, lastActiveTransactionDate.plusDays(numDaysToInactive)); + } + } + + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); + final String submittedByUsername = rs.getString("submittedByUsername"); + final String submittedByFirstname = rs.getString("submittedByFirstname"); + final String submittedByLastname = rs.getString("submittedByLastname"); + + final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate"); + final String rejectedByUsername = rs.getString("rejectedByUsername"); + final String rejectedByFirstname = rs.getString("rejectedByFirstname"); + final String rejectedByLastname = rs.getString("rejectedByLastname"); + + final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate"); + final String withdrawnByUsername = rs.getString("withdrawnByUsername"); + final String withdrawnByFirstname = rs.getString("withdrawnByFirstname"); + final String withdrawnByLastname = rs.getString("withdrawnByLastname"); + + final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); + final String approvedByUsername = rs.getString("approvedByUsername"); + final String approvedByFirstname = rs.getString("approvedByFirstname"); + final String approvedByLastname = rs.getString("approvedByLastname"); + + final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate"); + final String activatedByUsername = rs.getString("activatedByUsername"); + final String activatedByFirstname = rs.getString("activatedByFirstname"); + final String activatedByLastname = rs.getString("activatedByLastname"); + + final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); + final String closedByUsername = rs.getString("closedByUsername"); + final String closedByFirstname = rs.getString("closedByFirstname"); + final String closedByLastname = rs.getString("closedByLastname"); + + final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, + submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, + rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, + approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, activatedByUsername, activatedByFirstname, + activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname); + + 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); + + final BigDecimal nominalAnnualInterestRate = rs.getBigDecimal("nominalAnnualInterestRate"); + + final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations.compoundingInterestPeriodType( + SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, "interestCompoundingPeriodType"))); + + final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType( + SavingsPostingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, "interestPostingPeriodType"))); + + final EnumOptionData interestCalculationType = SavingsEnumerations + .interestCalculationType(SavingsInterestCalculationType.fromInt(JdbcSupport.getInteger(rs, "interestCalculationType"))); + + final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations.interestCalculationDaysInYearType( + SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs, "interestCalculationDaysInYearType"))); + + final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredOpeningBalance"); + + final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency"); + EnumOptionData lockinPeriodFrequencyType = null; + final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType"); + if (lockinPeriodFrequencyTypeValue != null) { + final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); + lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType); + } + + final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers"); + + final boolean allowOverdraft = rs.getBoolean("allowOverdraft"); + final BigDecimal overdraftLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "overdraftLimit"); + final BigDecimal nominalAnnualInterestRateOverdraft = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "nominalAnnualInterestRateOverdraft"); + final BigDecimal minOverdraftForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "minOverdraftForInterestCalculation"); + + final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance"); + final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance"); + + final BigDecimal maxAllowedLienLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxAllowedLienLimit"); + final boolean lienAllowed = rs.getBoolean("lienAllowed"); + + final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits"); + final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals"); + final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawalFees"); + final BigDecimal totalAnnualFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalAnnualFees"); + + final BigDecimal totalInterestEarned = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestEarned"); + final BigDecimal totalInterestPosted = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalInterestPosted"); + final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accountBalance"); + final BigDecimal totalFeeCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalFeeCharge"); + final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge"); + final BigDecimal totalOverdraftInterestDerived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, + "totalOverdraftInterestDerived"); + final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax"); + final LocalDate interestPostedTillDate = JdbcSupport.getLocalDate(rs, "interestPostedTillDate"); + + final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "minBalanceForInterestCalculation"); + final BigDecimal onHoldFunds = rs.getBigDecimal("onHoldFunds"); + + final BigDecimal onHoldAmount = rs.getBigDecimal("onHoldAmount"); + + BigDecimal availableBalance = accountBalance; + if (availableBalance != null && onHoldFunds != null) { + availableBalance = availableBalance.subtract(onHoldFunds); + } + + if (availableBalance != null && onHoldAmount != null) { + + availableBalance = availableBalance.subtract(onHoldAmount); + } + + BigDecimal interestNotPosted = BigDecimal.ZERO; + LocalDate lastInterestCalculationDate = null; + if (totalInterestEarned != null) { + interestNotPosted = totalInterestEarned.subtract(totalInterestPosted).add(totalOverdraftInterestDerived); + lastInterestCalculationDate = JdbcSupport.getLocalDate(rs, "lastInterestCalculationDate"); + } + + final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, + totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge, + totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax, interestNotPosted, lastInterestCalculationDate, + availableBalance, interestPostedTillDate); + + final boolean withHoldTax = rs.getBoolean("withHoldTax"); + final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId"); + final String taxGroupName = rs.getString("taxGroupName"); + TaxGroupData taxGroupData = null; + if (taxGroupId != null) { + taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); + } + + final SavingsAccountData accountData = SavingsAccountData.instance(id, accountNo, depositType, externalId, groupId, groupName, + clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, subStatus, reasonForBlock, + timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance, + enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, onHoldFunds, + nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroupData, + lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, onHoldAmount); + + // CBS extended fields + accountData.setCbsAccountType(rs.getString("cbsAccountType")); + accountData.setCustomerSegment(rs.getString("customerSegment")); + accountData.setAccountStatus(rs.getString("accountStatus")); + accountData.setModeOfOperation(rs.getString("modeOfOperation")); + accountData.setSpecialCategory(rs.getString("specialCategory")); + accountData.setWeakerSection(rs.getString("weakerSection")); + accountData.setGuardianName(rs.getString("guardianName")); + accountData.setGuardianCifNumber(rs.getString("guardianCifNumber")); + accountData.setGuardianRelationship(rs.getString("guardianRelationship")); + accountData.setMinorDateOfBirth(JdbcSupport.getLocalDate(rs, "minorDateOfBirth")); + accountData.setGuardianClientId(JdbcSupport.getLong(rs, "guardianClientId")); + accountData.setIntroducerClientId(JdbcSupport.getLong(rs, "introducerClientId")); + accountData.setIntroducerName(rs.getString("introducerName")); + accountData.setIntroducerCifNumber(rs.getString("introducerCifNumber")); + accountData.setLinkedAccountId(JdbcSupport.getLong(rs, "linkedAccountId")); + accountData.setLinkedAccountType(rs.getString("linkedAccountType")); + accountData.setLinkedAccountNo(rs.getString("linkedAccountNo")); + accountData.setBalanceSwapOption(rs.getObject("balanceSwapOption") != null ? rs.getBoolean("balanceSwapOption") : null); + accountData.setMsme(rs.getObject("msme") != null ? rs.getBoolean("msme") : null); + accountData.setNominationFacility(rs.getObject("nominationFacility") != null ? rs.getBoolean("nominationFacility") : null); + accountData.setAutoDebitFacility(rs.getObject("autoDebitFacility") != null ? rs.getBoolean("autoDebitFacility") : null); + + // Nominee as parsed map + final String nomineeDataStr = rs.getString("nomineeData"); + if (nomineeDataStr != null && !nomineeDataStr.isEmpty()) { + try { + com.google.gson.Gson gson = new com.google.gson.Gson(); + @SuppressWarnings("unchecked") + java.util.Map nomineeMap = gson.fromJson(nomineeDataStr, + new com.google.gson.reflect.TypeToken>() {}.getType()); + accountData.setNomineeData(nomineeMap); + } catch (Exception ignored) {} + } + + // Facility config + final boolean hasFacilityConfig = rs.getObject("atmFacility") != null; + if (hasFacilityConfig) { + final java.util.Map facilityConfigMap = new java.util.LinkedHashMap<>(); + facilityConfigMap.put("atmFacility", rs.getBoolean("atmFacility")); + facilityConfigMap.put("atmWithdrawalFacility", rs.getBoolean("atmWithdrawalFacility")); + facilityConfigMap.put("upiFacility", rs.getBoolean("upiFacility")); + facilityConfigMap.put("posTransactionsFacility", rs.getBoolean("posTransactionsFacility")); + facilityConfigMap.put("chequeBook", rs.getBoolean("chequeBook")); + facilityConfigMap.put("impsFacility", rs.getBoolean("impsFacility")); + facilityConfigMap.put("smsAlerts", rs.getBoolean("smsAlerts")); + facilityConfigMap.put("bbpsFacility", rs.getBoolean("bbpsFacility")); + facilityConfigMap.put("insuranceCover", rs.getBoolean("insuranceCover")); + facilityConfigMap.put("bepgFacility", rs.getBoolean("bepgFacility")); + facilityConfigMap.put("neftFacility", rs.getBoolean("neftFacility")); + facilityConfigMap.put("rtgsFacility", rs.getBoolean("rtgsFacility")); + facilityConfigMap.put("interBankTotalCapFacility", rs.getBoolean("interBankTotalCapFacility")); + facilityConfigMap.put("withdrawalFacility", rs.getBoolean("withdrawalFacility")); + facilityConfigMap.put("depositFacilityKyc", rs.getBoolean("depositFacilityKyc")); + facilityConfigMap.put("virtualDebitCard", rs.getBoolean("virtualDebitCard")); + accountData.setAccountFacilityConfig(facilityConfigMap); + } + + return accountData; + } + } + + private static final class SavingAccountMapperForLookup implements RowMapper { + + private final String schemaSql; + + SavingAccountMapperForLookup() { + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("sa.id as id, sa.account_no as accountNo, "); + sqlBuilder.append("sa.deposit_type_enum as depositType, "); + sqlBuilder.append("sp.id as productId, sp.name as productName, "); + sqlBuilder.append("sa.status_enum as statusEnum "); + + sqlBuilder.append("from m_savings_account sa "); + sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id "); + + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public SavingsAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = rs.getLong("id"); + final String accountNo = rs.getString("accountNo"); + final Integer depositTypeId = rs.getInt("depositType"); + final EnumOptionData depositType = SavingsEnumerations.depositType(depositTypeId); + + final Long productId = rs.getLong("productId"); + final String productName = rs.getString("productName"); + + final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum"); + final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum); + + return SavingsAccountData.lookupWithProductDetails(id, accountNo, depositType, productId, productName, status); + } + } + + @Override + public SavingsAccountTransactionData retrieveDepositTransactionTemplate(final Long savingsId, + final DepositAccountType depositAccountType) { + + try { + final String sql = "select " + this.transactionTemplateMapper.schema() + " where sa.id = ? and sa.deposit_type_enum = ?"; + + return this.jdbcTemplate.queryForObject(sql, this.transactionTemplateMapper, // NOSONAR + new Object[] { savingsId, depositAccountType.getValue() }); + } catch (final EmptyResultDataAccessException e) { + throw new SavingsAccountNotFoundException(savingsId, e); + } + } + + @Override + public Collection retrieveAllTransactions(final Long savingsId, DepositAccountType depositAccountType) { + // #audit backward compatibility + final String sql = "select " + this.transactionsMapper.schema() + + " where sa.id = ? and sa.deposit_type_enum = ? order by tr.transaction_date DESC," + " tr." + CREATED_DATE_DB_FIELD + + " DESC, tr.created_date DESC, tr.id DESC"; + return this.jdbcTemplate.query(sql, this.transactionsMapper, new Object[] { savingsId, depositAccountType.getValue() }); // NOSONAR + } + + @Override + public SavingsAccountTransactionData retrieveSavingsTransaction(final Long savingsId, final Long transactionId, + DepositAccountType depositAccountType) { + + final String sql = "select " + this.transactionsMapper.schema() + " where sa.id = ? and sa.deposit_type_enum = ? and tr.id= ?"; + + return this.jdbcTemplate.queryForObject(sql, this.transactionsMapper, // NOSONAR + new Object[] { savingsId, depositAccountType.getValue(), transactionId }); + } + + private static final class SavingsAccountTransactionsForBatchMapper implements RowMapper { + + private final String schemaSql; + + SavingsAccountTransactionsForBatchMapper() { + + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("tr.id as transactionId, tr.ref_no as refNo "); + sqlBuilder.append("from m_savings_account_transaction tr"); + + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + final Long id = rs.getLong("transactionId"); + SavingsAccountTransactionData savingsAccountTransactionData = SavingsAccountTransactionData.create(id); + final String refNo = rs.getString("refNo"); + savingsAccountTransactionData.setRefNo(refNo); + return savingsAccountTransactionData; + } + } + + /* + * @Override public Collection retrieveAccountsWithAnnualFeeDue() { final String sql = + * "select " + this.annualFeeMapper.schema() + + * " where sa.annual_fee_next_due_date is not null and sa.annual_fee_next_due_date <= NOW()" ; + * + * return this.jdbcTemplate.query(sql, this.annualFeeMapper, new Object[] {}); } + */ + + public static class SavingsAccountTransactionsMapper implements RowMapper { + + private static final String SELECT = buildSelect(); + private static final String FROM = buildFrom(); + private static final String SCHEMA = SELECT + FROM; + + public SavingsAccountTransactionsMapper() {} + + protected static String buildSelect() { + return "tr.id as transactionId, tr.transaction_type_enum as transactionType, " + + "tr.transaction_date as transactionDate, tr.amount as transactionAmount, " + + "tr.release_id_of_hold_amount as releaseTransactionId, tr.reason_for_block as reasonForBlock, " + + "tr.submitted_on_date as submittedOnDate, au.username as submittedByUsername, nt.note as transactionNote, " + + "tr.running_balance_derived as runningBalance, tr.is_reversed as reversed, " + + "tr.is_reversal as isReversal, tr.original_transaction_id as originalTransactionId, tr.is_lien_transaction as lienTransaction, " + + "fromtran.id as fromTransferId, fromtran.is_reversed as fromTransferReversed, " + + "fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount, " + + "fromtran.description as fromTransferDescription, " + + "totran.id as toTransferId, totran.is_reversed as toTransferReversed, " + + "totran.transaction_date as toTransferDate, totran.amount as toTransferAmount, " + + "totran.description as toTransferDescription, sa.id as savingsId, sa.account_no as accountNo, " + + "pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, " + + "pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, " + + "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, " + + "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, " + + "curr.display_symbol as currencyDisplaySymbol, pt.value as paymentTypeName, " + "tr.is_manual as postInterestAsOn "; + } + + protected static String buildFrom() { + return " FROM m_savings_account_transaction tr join m_savings_account sa on tr.savings_account_id = sa.id " + + "join m_currency curr on curr.code = sa.currency_code " + + "left join m_account_transfer_transaction fromtran on fromtran.from_savings_transaction_id = tr.id " + + "left join m_account_transfer_transaction totran on totran.to_savings_transaction_id = tr.id " + + "left join m_payment_detail pd on tr.payment_detail_id = pd.id " + + "left join m_payment_type pt on pd.payment_type_id = pt.id left join m_appuser au on au.id= tr." + CREATED_BY_DB_FIELD + + " left join m_note nt ON nt.savings_account_transaction_id=tr.id "; + } + + public String schema() { + return SCHEMA; + } + + public String select() { + return SELECT; + } + + public String from() { + return FROM; + } + + @Override + public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + final Long id = rs.getLong("transactionId"); + final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType"); + final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(transactionTypeInt); + + final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate"); + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); + final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount"); + final Long releaseTransactionId = rs.getLong("releaseTransactionId"); + final String reasonForBlock = rs.getString("reasonForBlock"); + final BigDecimal outstandingChargeAmount = null; + final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "runningBalance"); + final boolean reversed = rs.getBoolean("reversed"); + final boolean isReversal = rs.getBoolean("isReversal"); + final Long originalTransactionId = rs.getLong("originalTransactionId"); + final Boolean lienTransaction = rs.getBoolean("lienTransaction"); + + final Long savingsId = rs.getLong("savingsId"); + final String accountNo = rs.getString("accountNo"); + final boolean postInterestAsOn = rs.getBoolean("postInterestAsOn"); + + PaymentDetailData paymentDetailData = null; + if (transactionType.isDepositOrWithdrawal()) { + final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType"); + if (paymentTypeId != null) { + final String typeName = rs.getString("paymentTypeName"); + final PaymentTypeData paymentType = PaymentTypeData.builder().id(paymentTypeId).name(typeName).build(); + final String accountNumber = rs.getString("accountNumber"); + final String checkNumber = rs.getString("checkNumber"); + final String routingCode = rs.getString("routingCode"); + final String receiptNumber = rs.getString("receiptNumber"); + final String bankNumber = rs.getString("bankNumber"); + paymentDetailData = new PaymentDetailData(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber, + bankNumber); + } + } + + 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); + + AccountTransferData transfer = null; + final Long fromTransferId = JdbcSupport.getLong(rs, "fromTransferId"); + final Long toTransferId = JdbcSupport.getLong(rs, "toTransferId"); + if (fromTransferId != null) { + final LocalDate fromTransferDate = JdbcSupport.getLocalDate(rs, "fromTransferDate"); + final BigDecimal fromTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "fromTransferAmount"); + final boolean fromTransferReversed = rs.getBoolean("fromTransferReversed"); + final String fromTransferDescription = rs.getString("fromTransferDescription"); + + transfer = AccountTransferData.transferBasicDetails(fromTransferId, currency, fromTransferAmount, fromTransferDate, + fromTransferDescription, fromTransferReversed); + } else if (toTransferId != null) { + final LocalDate toTransferDate = JdbcSupport.getLocalDate(rs, "toTransferDate"); + final BigDecimal toTransferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "toTransferAmount"); + final boolean toTransferReversed = rs.getBoolean("toTransferReversed"); + final String toTransferDescription = rs.getString("toTransferDescription"); + + transfer = AccountTransferData.transferBasicDetails(toTransferId, currency, toTransferAmount, toTransferDate, + toTransferDescription, toTransferReversed); + } + final String submittedByUsername = rs.getString("submittedByUsername"); + final String note = rs.getString("transactionNote"); + return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency, + amount, outstandingChargeAmount, runningBalance, reversed, transfer, submittedOnDate, postInterestAsOn, + submittedByUsername, note, isReversal, originalTransactionId, lienTransaction, releaseTransactionId, reasonForBlock); + } + } + + private static final class SavingsAccountTransactionTemplateMapper implements RowMapper { + + private final String schemaSql; + + SavingsAccountTransactionTemplateMapper() { + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("sa.id as id, sa.account_no as accountNo, "); + sqlBuilder.append( + "sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, 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("sa.min_required_opening_balance as minRequiredOpeningBalance "); + sqlBuilder.append("from m_savings_account sa "); + sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); + + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long savingsId = rs.getLong("id"); + final String accountNo = rs.getString("accountNo"); + + 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 SavingsAccountTransactionData.template(savingsId, accountNo, DateUtils.getBusinessLocalDate(), currency); + } + } + + @Override + public Collection retrieveForLookup(Long clientId, Boolean overdraft) { + + SavingAccountMapperForLookup accountMapperForLookup = new SavingAccountMapperForLookup(); + final StringBuilder sqlBuilder = new StringBuilder("select " + accountMapperForLookup.schema()); + sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300"); + Object[] queryParameters = null; + if (overdraft == null) { + queryParameters = new Object[] { clientId }; + } else { + sqlBuilder.append(" and sa.allow_overdraft = ?"); + queryParameters = new Object[] { clientId, overdraft }; + } + return this.jdbcTemplate.query(sqlBuilder.toString(), accountMapperForLookup, queryParameters); + + } + + @Override + public List retrieveSavingsIdsPendingInactive(LocalDate tenantLocalDate) { + List ret = null; + StringBuilder sql = new StringBuilder("select sa.id "); + sql.append(" from m_savings_account as sa "); + sql.append(" inner join m_savings_product as sp on (sa.product_id = sp.id and sp.is_dormancy_tracking_active = true) "); + sql.append(" where sa.status_enum = 300 "); + sql.append(" and sa.sub_status_enum = 0 "); + String compareDate = "(select COALESCE(max(sat.transaction_date), sa.activatedon_date) " + + "from m_savings_account_transaction as sat where sat.is_reversed = false and sat.is_reversal = false" + + " and sat.transaction_type_enum in (1,2) and sat.savings_account_id = sa.id)"; + sql.append(" and ").append(sqlGenerator.dateDiff("?", compareDate)).append(" >= sp.days_to_inactive "); + + try { + ret = this.jdbcTemplate.queryForList(sql.toString(), new Object[] { tenantLocalDate }, Long.class); + } catch (EmptyResultDataAccessException e) { + // ignore empty result scenario + } catch (DataAccessException e) { + throw e; + } + + return ret; + } + + @Override + public List retrieveSavingsIdsPendingDormant(LocalDate tenantLocalDate) { + List ret = null; + StringBuilder sql = new StringBuilder("select sa.id "); + sql.append(" from m_savings_account as sa "); + sql.append(" inner join m_savings_product as sp on (sa.product_id = sp.id and sp.is_dormancy_tracking_active = true) "); + sql.append(" where sa.status_enum = 300 "); + sql.append(" and sa.sub_status_enum = 100 "); + sql.append(" and " + sqlGenerator.dateDiff("?", + "(select COALESCE(max(sat.transaction_date),sa.activatedon_date) from m_savings_account_transaction as sat where sat.is_reversed = false and sat.is_reversal = false and sat.transaction_type_enum in (1,2) and sat.savings_account_id = sa.id)") + + " "); + sql.append(" >= sp.days_to_dormancy "); + + try { + ret = this.jdbcTemplate.queryForList(sql.toString(), new Object[] { tenantLocalDate }, Long.class); + } catch (EmptyResultDataAccessException e) { + // ignore empty result scenario + } catch (DataAccessException e) { + throw e; + } + + return ret; + } + + @Override + public List retrieveSavingsIdsPendingEscheat(LocalDate tenantLocalDate) { + List ret = null; + StringBuilder sql = new StringBuilder("select sa.id "); + sql.append(" from m_savings_account as sa "); + sql.append(" inner join m_savings_product as sp on (sa.product_id = sp.id and sp.is_dormancy_tracking_active = true) "); + sql.append(" where sa.status_enum = 300 "); + sql.append(" and sa.sub_status_enum = 200 "); + sql.append(" and " + sqlGenerator.dateDiff("?", + "(select COALESCE(max(sat.transaction_date),sa.activatedon_date) from m_savings_account_transaction as sat where sat.is_reversed = false and sat.is_reversal = false and sat.transaction_type_enum in (1,2) and sat.savings_account_id = sa.id)") + + " "); + sql.append(" >= sp.days_to_escheat "); + + try { + ret = this.jdbcTemplate.queryForList(sql.toString(), Long.class, new Object[] { tenantLocalDate }); + } catch (EmptyResultDataAccessException e) { + // ignore empty result scenario + } catch (DataAccessException e) { + throw e; + } + + return ret; + } + + @Override + public boolean isAccountBelongsToClient(final Long clientId, final Long accountId, final DepositAccountType depositAccountType, + final String currencyCode) { + try { + final StringBuilder buff = new StringBuilder("select count(*) from m_savings_account sa "); + buff.append( + " where sa.id = ? and sa.client_id = ? and sa.deposit_type_enum = ? and sa.currency_code = ? and sa.status_enum = 300"); + return this.jdbcTemplate.queryForObject(buff.toString(), Integer.class, accountId, clientId, depositAccountType.getValue(), + currencyCode) > 0; + } catch (final EmptyResultDataAccessException e) { + throw new SavingsAccountNotFoundException(accountId, e); + } + } + /* + * private static final class SavingsAccountAnnualFeeMapper implements RowMapper { + * + * private final String schemaSql; + * + * public SavingsAccountAnnualFeeMapper() { final StringBuilder sqlBuilder = new StringBuilder(200); + * sqlBuilder.append("sa.id as id, sa.account_no as accountNo, "); sqlBuilder + * .append("sa.annual_fee_next_due_date as annualFeeNextDueDate "); sqlBuilder.append("from m_savings_account sa "); + * + * this.schemaSql = sqlBuilder.toString(); } + * + * public String schema() { return this.schemaSql; } + * + * @Override public SavingsAccountAnnualFeeData mapRow(final ResultSet rs, + * + * @SuppressWarnings("unused") final int rowNum) throws SQLException { + * + * final Long id = rs.getLong("id"); final String accountNo = rs.getString("accountNo"); final LocalDate + * annualFeeNextDueDate = JdbcSupport.getLocalDate(rs, "annualFeeNextDueDate"); + * + * return SavingsAccountAnnualFeeData.instance(id, accountNo, annualFeeNextDueDate); } } + */ + + @Override + public String retrieveAccountNumberByAccountId(Long accountId) { + try { + final String sql = "select s.account_no from m_savings_account s where s.id = ?"; + return this.jdbcTemplate.queryForObject(sql, String.class, accountId); + } catch (final EmptyResultDataAccessException e) { + throw new SavingsAccountNotFoundException(accountId, e); + } + } + + @Override + public List getAccountsIdsByStatusPaged(Integer status, int pageSize, Long maxSavingsIdInList) { + String sql = new StringBuilder().append(" SELECT sa.id FROM m_savings_account sa ") + .append(" where sa.id > ? and sa.status_enum = ? ").append(" order by sa.id limit ?").toString(); + + try { + return this.jdbcTemplate.queryForList(sql, Long.class, new Object[] { maxSavingsIdInList, status, pageSize }); + } catch (EmptyResultDataAccessException e) { + return new ArrayList<>(); + } + } + + @Override + public Long retrieveAccountIdByExternalId(final ExternalId externalId) { + return savingsAccountRepositoryWrapper.findIdByExternalId(externalId); + } + + @Override + public List retrievePeriodicAccrualData(LocalDate tillDate, SavingsAccount savings) { + Long savingsId = (savings != null) ? savings.getId() : null; + Integer status = SavingsAccountStatusType.ACTIVE.getValue(); + Integer accountingRule = AccountingRuleType.ACCRUAL_PERIODIC.getValue(); + + return this.savingsAccountRepositoryWrapper.findAccrualData(tillDate, savingsId, status, accountingRule); + } +}