diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java index ab7ca6828..ea26bb46b 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java @@ -1,688 +1,694 @@ -/** - * 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; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams; - -public final class DepositsApiConstants { - - private DepositsApiConstants() { - - } - - // Deposit products - public static final String FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME = "fixeddepositproduct"; - public static final String RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME = "recurringdepositproduct"; - - // Deposit accounts - public static final String FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME = "fixeddepositaccount"; - public static final String RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME = "recurringdepositaccount"; - - public static final String SAVINGS_ACCOUNT_RESOURCE_NAME = "savingsaccount"; - public static final String SAVINGS_ACCOUNT_TRANSACTION_RESOURCE_NAME = "savingsaccount.transaction"; - public static final String SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME = "savingsaccountcharge"; - - // Current accounts - public static final String CURRENT_ACCOUNT_RESOURCE_NAME = "currentaccount"; - public static final String CURRENT_PRODUCT_RESOURCE_NAME = "currentproduct"; - public static final String CURRENT_ACCOUNT_CHARGE_RESOURCE_NAME = "currentaccountcharge"; - - // deposit product actions - public static final String summitalAction = ".summital"; - public static final String approvalAction = ".approval"; - public static final String undoApprovalAction = ".undoApproval"; - public static final String rejectAction = ".reject"; - public static final String withdrawnByApplicantAction = ".withdrawnByApplicant"; - public static final String activateAction = ".activate"; - public static final String modifyApplicationAction = ".modify"; - public static final String deleteApplicationAction = ".delete"; - public static final String undoTransactionAction = ".undotransaction"; - public static final String applyAnnualFeeTransactionAction = ".applyannualfee"; - public static final String adjustTransactionAction = ".adjusttransaction"; - public static final String closeAction = ".close"; - public static final String preMatureCloseAction = ".preMatureClose"; - public static final String payChargeTransactionAction = ".paycharge"; - public static final String waiveChargeTransactionAction = ".waivecharge"; - - // command - public static final String COMMAND_UNDO_TRANSACTION = "undo"; - public static final String COMMAND_ADJUST_TRANSACTION = "modify"; - public static final String COMMAND_WAIVE_CHARGE = "waive"; - public static final String COMMAND_PAY_CHARGE = "paycharge"; - public static final String UPDATE_DEPOSIT_AMOUNT = "updateDepositAmount"; - - // general - public static final String localeParamName = "locale"; - public static final String dateFormatParamName = "dateFormat"; - public static final String monthDayFormatParamName = "monthDayFormat"; - - // deposit product and account parameters - public static final String idParamName = "id"; - public static final String accountNoParamName = "accountNo"; - public static final String externalIdParamName = "externalId"; - public static final String statusParamName = "status"; - public static final String clientIdParamName = "clientId"; - public static final String groupIdParamName = "groupId"; - public static final String productIdParamName = "productId"; - public static final String fieldOfficerIdParamName = "fieldOfficerId"; - - public static final String submittedOnDateParamName = "submittedOnDate"; - public static final String rejectedOnDateParamName = "rejectedOnDate"; - public static final String withdrawnOnDateParamName = "withdrawnOnDate"; - public static final String approvedOnDateParamName = "approvedOnDate"; - public static final String activatedOnDateParamName = "activatedOnDate"; - public static final String closedOnDateParamName = "closedOnDate"; - public static final String expectedFirstDepositOnDateParamName = "expectedFirstDepositOnDate"; - - public static final String activeParamName = "active"; - public static final String nameParamName = "name"; - public static final String shortNameParamName = "shortName"; - public static final String descriptionParamName = "description"; - public static final String currencyCodeParamName = "currencyCode"; - public static final String digitsAfterDecimalParamName = "digitsAfterDecimal"; - public static final String inMultiplesOfParamName = "inMultiplesOf"; - public static final String nominalAnnualInterestRateParamName = "nominalAnnualInterestRate"; - public static final String interestCompoundingPeriodTypeParamName = "interestCompoundingPeriodType"; - public static final String interestPostingPeriodTypeParamName = "interestPostingPeriodType"; - public static final String interestCalculationTypeParamName = "interestCalculationType"; - public static final String interestCalculationDaysInYearTypeParamName = "interestCalculationDaysInYearType"; - public static final String lockinPeriodFrequencyParamName = "lockinPeriodFrequency"; - public static final String lockinPeriodFrequencyTypeParamName = "lockinPeriodFrequencyType"; - public static final String feeAmountParamName = "feeAmount";// to be deleted - public static final String feeOnMonthDayParamName = "feeOnMonthDay"; - public static final String feeIntervalParamName = "feeInterval"; - public static final String accountingRuleParamName = "accountingRule"; - public static final String paymentTypeIdParamName = "paymentTypeId"; - public static final String transactionAccountNumberParamName = "accountNumber"; - public static final String checkNumberParamName = "checkNumber"; - public static final String routingCodeParamName = "routingCode"; - public static final String receiptNumberParamName = "receiptNumber"; - public static final String bankNumberParamName = "bankNumber"; - public static final String principalAmountParamName = "principalAmount"; - public static final String annualInterestRateParamName = "annualInterestRate"; - public static final String interestPostingPeriodInMonthsParamName = "interestPostingPeriodInMonths"; - public static final String tenureInMonthsParamName = "tenureInMonths"; - public static final String interestCompoundingPeriodInMonthsParamName = "interestCompoundingPeriodInMonths"; - - // Preclosure parameters - public static final String preClosurePenalApplicableParamName = "preClosurePenalApplicable"; - public static final String preClosurePenalInterestParamName = "preClosurePenalInterest"; - public static final String preClosurePenalInterestOnTypeIdParamName = "preClosurePenalInterestOnTypeId"; - public static final String interestFreePeriodFrequencyType = "interestFreePeriodFrequencyType"; - public static final String preClosurePenalInterestOnType = "preClosurePenalInterestOnType"; - - // term paramters - public static final String minDepositTermParamName = "minDepositTerm"; - public static final String maxDepositTermParamName = "maxDepositTerm"; - public static final String minDepositTermTypeIdParamName = "minDepositTermTypeId"; - public static final String maxDepositTermTypeIdParamName = "maxDepositTermTypeId"; - public static final String minDepositTermType = "minDepositTermType"; - public static final String maxDepositTermType = "maxDepositTermType"; - public static final String inMultiplesOfDepositTermParamName = "inMultiplesOfDepositTerm"; - public static final String inMultiplesOfDepositTermTypeIdParamName = "inMultiplesOfDepositTermTypeId"; - public static final String inMultiplesOfDepositTermType = "inMultiplesOfDepositTermType"; - - public static final String depositAmountParamName = "depositAmount"; - public static final String depositMinAmountParamName = "minDepositAmount"; - public static final String depositMaxAmountParamName = "maxDepositAmount"; - public static final String depositPeriodParamName = "depositPeriod"; - public static final String depositPeriodFrequencyIdParamName = "depositPeriodFrequencyId"; - - // recurring parameters - public static final String mandatoryRecommendedDepositAmountParamName = "mandatoryRecommendedDepositAmount"; - public static final String isMandatoryDepositParamName = "isMandatoryDeposit"; - public static final String allowWithdrawalParamName = "allowWithdrawal"; - public static final String adjustAdvanceTowardsFuturePaymentsParamName = "adjustAdvanceTowardsFuturePayments"; - - public static final String recurringFrequencyTypeParamName = "recurringFrequencyType"; - public static final String recurringFrequencyParamName = "recurringFrequency"; - public static final String isCalendarInheritedParamName = "isCalendarInherited"; - - // transaction parameters - public static final String transactionDateParamName = "transactionDate"; - public static final String transactionAmountParamName = "transactionAmount"; - public static final String paymentDetailDataParamName = "paymentDetailData"; - public static final String runningBalanceParamName = "runningBalance"; - public static final String reversedParamName = "reversed"; - public static final String dateParamName = "date"; - public static final String accountIdParamName = "accountId"; - - // recurring deposits update parameters - public static final String effectiveDateParamName = "effectiveDate"; - - // charges parameters - public static final String chargeIdParamName = "chargeId"; - public static final String chargesParamName = "charges"; - public static final String savingsAccountChargeIdParamName = "savingsAccountChargeId"; - public static final String chargeNameParamName = "name"; - public static final String penaltyParamName = "penalty"; - public static final String chargeTimeTypeParamName = "chargeTimeType"; - public static final String dueAsOfDateParamName = "dueDate"; - public static final String chargeCalculationTypeParamName = "chargeCalculationType"; - public static final String percentageParamName = "percentage"; - public static final String amountPercentageAppliedToParamName = "amountPercentageAppliedTo"; - public static final String currencyParamName = "currency"; - public static final String amountWaivedParamName = "amountWaived"; - public static final String amountWrittenOffParamName = "amountWrittenOff"; - public static final String amountOutstandingParamName = "amountOutstanding"; - public static final String amountOrPercentageParamName = "amountOrPercentage"; - public static final String amountParamName = "amount"; - public static final String isManualTransaction = "isManualTransaction"; - public static final String lienTransaction = "lienTransaction"; - public static final String chargesPaidByData = "chargesPaidByData"; - public static final String amountPaidParamName = "amountPaid"; - public static final String chargeOptionsParamName = "chargeOptions"; - public static final String chargePaymentModeParamName = "chargePaymentMode"; - - public static final String noteParamName = "note"; - public static final String chartsParamName = "charts"; - public static final String chartIdParamName = "chartId"; - - // deposit account associations - public static final String transactions = "transactions"; - public static final String charges = "charges"; - public static final String activeChart = "activeChart"; - - // account closure - public static final String onAccountClosureIdParamName = "onAccountClosureId"; - public static final String transferDescriptionParamName = "transferDescription"; - public static final String toSavingsAccountIdParamName = "toSavingsAccountId"; - public static final String savingsAccounts = "savingsAccounts"; - public static final String maturityInstructionIdParamName = "maturityInstructionId"; - public static final String transferToSavingsIdParamName = "transferToSavingsId"; - - public static final String preMatureCloseOnDateParamName = "preMatureCloseOnDate"; - - public static final String linkedAccountParamName = "linkAccountId"; - public static final String transferInterestToSavingsParamName = "transferInterestToSavings"; - - // template - public static final String chartTemplate = "chartTemplate"; - - /** - * Deposit Product Parameters - */ - private static final Set DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, - monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName, - digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, - interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, - lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, accountingRuleParamName, chargesParamName, - SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), - SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), - SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), - SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), - SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(), - SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), - SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), - SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), chartsParamName, SavingsApiConstants.withHoldTaxParamName, - SavingsApiConstants.taxGroupIdParamName)); - - private static final Set PRECLOSURE_REQUEST_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(preClosurePenalApplicableParamName, preClosurePenalInterestParamName, preClosurePenalInterestOnTypeIdParamName)); - - private static final Set PRECLOSURE_RESPONSE_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(preClosurePenalApplicableParamName, preClosurePenalInterestParamName, preClosurePenalInterestOnType)); - - private static final Set DEPOSIT_TERM_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(minDepositTermParamName, - maxDepositTermParamName, minDepositTermTypeIdParamName, maxDepositTermTypeIdParamName, inMultiplesOfDepositTermParamName, - inMultiplesOfDepositTermTypeIdParamName, depositAmountParamName, depositMinAmountParamName, depositMaxAmountParamName)); - - private static final Set DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(minDepositTermParamName, - maxDepositTermParamName, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTermParamName, - inMultiplesOfDepositTermType, depositAmountParamName, depositMinAmountParamName, depositMaxAmountParamName)); - - private static final Set RECURRING_DETAILS_REQUEST_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(mandatoryRecommendedDepositAmountParamName, isMandatoryDepositParamName, allowWithdrawalParamName, - adjustAdvanceTowardsFuturePaymentsParamName, recurringFrequencyTypeParamName, recurringFrequencyParamName, - isCalendarInheritedParamName)); - - private static final Set RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(mandatoryRecommendedDepositAmountParamName, isMandatoryDepositParamName, allowWithdrawalParamName, - adjustAdvanceTowardsFuturePaymentsParamName, recurringFrequencyTypeParamName, recurringFrequencyParamName, - isCalendarInheritedParamName)); - - private static final Set DEPOSIT_PRECLOSURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(preMatureCloseOnDateParamName)); - - public static final Set FIXED_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = fixedDepositProductRequestData(); - public static final Set FIXED_DEPOSIT_PRODUCT_RESPONSE_DATA_PARAMETERS = fixedDepositProductResponseData(); - - public static final Set RECURRING_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = recurringDepositProductRequestData(); - public static final Set RECURRING_DEPOSIT_PRODUCT_RESPONSE_DATA_PARAMETERS = recurringDepositProductResponseData(); - - private static Set fixedDepositProductRequestData() { - final Set fixedDepositRequestData = new HashSet<>(); - fixedDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS); - fixedDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS); - fixedDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS); - fixedDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName); - fixedDepositRequestData.add("accountType"); - fixedDepositRequestData.add("customerSegment"); - fixedDepositRequestData.add("productCode"); - fixedDepositRequestData.add("fdType"); - fixedDepositRequestData.add(SavingsApiConstants.glAccountIdParamName); - fixedDepositRequestData.add("effectiveDate"); - fixedDepositRequestData.add("effectiveTo"); - fixedDepositRequestData.add("endDate"); - fixedDepositRequestData.add("introducerRequired"); - fixedDepositRequestData.add("shareLinkingEnabled"); - fixedDepositRequestData.add("nominationFacility"); - fixedDepositRequestData.add("dormancyAlert"); - fixedDepositRequestData.add(SavingsApiConstants.isDormancyTrackingActiveParamName); - fixedDepositRequestData.add(SavingsApiConstants.daysToInactiveParamName); - fixedDepositRequestData.add(SavingsApiConstants.daysToDormancyParamName); - fixedDepositRequestData.add(SavingsApiConstants.daysToEscheatParamName); - fixedDepositRequestData.add("interestCalculateSelector"); - fixedDepositRequestData.add("averagePeriod"); - fixedDepositRequestData.add("autoPost"); - fixedDepositRequestData.add("frequencyFixDay"); - fixedDepositRequestData.add("lastInterestDate"); - fixedDepositRequestData.add("enableLockinPeriod"); - fixedDepositRequestData.add("balanceFrequency"); - fixedDepositRequestData.add("accountClosedZeroBalanceInDayClosing"); - fixedDepositRequestData.add("autoDebitFacility"); - fixedDepositRequestData.add("loanFacility"); - fixedDepositRequestData.add("prematureWithdrawalOption"); - fixedDepositRequestData.add("tenureFlexibility"); - fixedDepositRequestData.add("lienMarked"); - fixedDepositRequestData.add("withdrawalFeeForTransfers"); - fixedDepositRequestData.add("maturityInstructions"); - fixedDepositRequestData.add("creditMaturityProceeds"); - fixedDepositRequestData.add("penaltyGracePeriodMissedInstallments"); - fixedDepositRequestData.add("panelRequestApplicable"); - fixedDepositRequestData.add("noPenaltyNomineeHeirClosure"); - fixedDepositRequestData.add("deceasedSettlement"); - fixedDepositRequestData.add("allowDebitTxnAfterDueDate"); - fixedDepositRequestData.add("escheatLiabilityId"); - fixedDepositRequestData.add("fdSlabs"); - return fixedDepositRequestData; - } - - private static Set fixedDepositProductResponseData() { - final Set fixedDepositRequestData = new HashSet<>(); - fixedDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS); - fixedDepositRequestData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS); - fixedDepositRequestData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS); - fixedDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName); - fixedDepositRequestData.add("interestCalculationTypeOption"); - fixedDepositRequestData.add("periodFrequencyOptions"); - fixedDepositRequestData.add("yesNoOptions"); - fixedDepositRequestData.add("accountTypeOptions"); - fixedDepositRequestData.add("customerSegmentOptions"); - fixedDepositRequestData.add("accountType"); - fixedDepositRequestData.add("customerSegment"); - fixedDepositRequestData.add("productCode"); - fixedDepositRequestData.add("fdType"); - fixedDepositRequestData.add(SavingsApiConstants.glAccountIdParamName); - fixedDepositRequestData.add("glAccountName"); - fixedDepositRequestData.add("effectiveDate"); - fixedDepositRequestData.add("effectiveTo"); - fixedDepositRequestData.add("endDate"); - fixedDepositRequestData.add("introducerRequired"); - fixedDepositRequestData.add("shareLinkingEnabled"); - fixedDepositRequestData.add("nominationFacility"); - fixedDepositRequestData.add("dormancyAlert"); - fixedDepositRequestData.add(SavingsApiConstants.isDormancyTrackingActiveParamName); - fixedDepositRequestData.add(SavingsApiConstants.daysToInactiveParamName); - fixedDepositRequestData.add(SavingsApiConstants.daysToDormancyParamName); - fixedDepositRequestData.add(SavingsApiConstants.daysToEscheatParamName); - fixedDepositRequestData.add("interestCalculateSelector"); - fixedDepositRequestData.add("averagePeriod"); - fixedDepositRequestData.add("autoPost"); - fixedDepositRequestData.add("frequencyFixDay"); - fixedDepositRequestData.add("lastInterestDate"); - fixedDepositRequestData.add("enableLockinPeriod"); - fixedDepositRequestData.add("balanceFrequency"); - fixedDepositRequestData.add("accountClosedZeroBalanceInDayClosing"); - fixedDepositRequestData.add("autoDebitFacility"); - fixedDepositRequestData.add("loanFacility"); - fixedDepositRequestData.add("prematureWithdrawalOption"); - fixedDepositRequestData.add("tenureFlexibility"); - fixedDepositRequestData.add("lienMarked"); - fixedDepositRequestData.add("withdrawalFeeForTransfers"); - fixedDepositRequestData.add("maturityInstructions"); - fixedDepositRequestData.add("creditMaturityProceeds"); - fixedDepositRequestData.add("penaltyGracePeriodMissedInstallments"); - fixedDepositRequestData.add("panelRequestApplicable"); - fixedDepositRequestData.add("noPenaltyNomineeHeirClosure"); - fixedDepositRequestData.add("deceasedSettlement"); - fixedDepositRequestData.add("allowDebitTxnAfterDueDate"); - fixedDepositRequestData.add("accountingMappings"); - fixedDepositRequestData.add("paymentChannelToFundSourceMappings"); - fixedDepositRequestData.add("feeToIncomeAccountMappings"); - fixedDepositRequestData.add("penaltyToIncomeAccountMappings"); - return fixedDepositRequestData; - } - - private static Set recurringDepositProductRequestData() { - final Set recurringDepositRequestData = new HashSet<>(); - recurringDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS); - recurringDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS); - recurringDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS); - recurringDepositRequestData.addAll(RECURRING_DETAILS_REQUEST_DATA_PARAMETERS); - recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName); - // Custom CBS fields — Details step - recurringDepositRequestData.add("accountType"); - recurringDepositRequestData.add("customerSegment"); - recurringDepositRequestData.add("productCode"); - recurringDepositRequestData.add(SavingsApiConstants.glAccountIdParamName); - recurringDepositRequestData.add("effectiveTo"); - recurringDepositRequestData.add("endDate"); - recurringDepositRequestData.add("introducerRequired"); - recurringDepositRequestData.add("shareLinkingEnabled"); - // Custom CBS fields — Currency step - recurringDepositRequestData.add("dormancyAlert"); - recurringDepositRequestData.add(SavingsApiConstants.daysToInactiveParamName); - recurringDepositRequestData.add(SavingsApiConstants.daysToDormancyParamName); - recurringDepositRequestData.add(SavingsApiConstants.daysToEscheatParamName); - recurringDepositRequestData.add("balanceSwapOption"); - recurringDepositRequestData.add("msme"); - recurringDepositRequestData.add("nominationFacility"); - recurringDepositRequestData.add("autoDebitFacility"); - // Custom CBS fields — Terms step - recurringDepositRequestData.add("effectiveDate"); - recurringDepositRequestData.add("interestCalculateSelector"); - recurringDepositRequestData.add("averagePeriod"); - recurringDepositRequestData.add("autoPost"); - recurringDepositRequestData.add("frequencyFixDay"); - recurringDepositRequestData.add("lastInterestDate"); - // Custom CBS fields — Settings step - recurringDepositRequestData.add("loanFacility"); - recurringDepositRequestData.add("prematureWithdrawalOption"); - recurringDepositRequestData.add("tenureFlexibility"); - recurringDepositRequestData.add("lienMarked"); - recurringDepositRequestData.add("withdrawalFeeForTransfers"); - recurringDepositRequestData.add("enableLockinPeriod"); - recurringDepositRequestData.add("maturityInstructions"); - recurringDepositRequestData.add("creditMaturityProceeds"); - recurringDepositRequestData.add("penaltyGracePeriodMissedInstallments"); - recurringDepositRequestData.add("panelRequestApplicable"); - recurringDepositRequestData.add("noPenaltyNomineeHeirClosure"); - recurringDepositRequestData.add("deceasedSettlement"); - recurringDepositRequestData.add("allowDebitTxnAfterDueDate"); - recurringDepositRequestData.add("accountClosedZeroBalanceInDayClosing"); - recurringDepositRequestData.add("escheatLiabilityId"); - // Custom CBS fields — Interest rate slabs (sent inline but ignored server-side; saved separately) - recurringDepositRequestData.add("rdSlabs"); - return recurringDepositRequestData; - } - - private static Set recurringDepositProductResponseData() { - final Set recurringDepositRequestData = new HashSet<>(); - recurringDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS); - recurringDepositRequestData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS); - recurringDepositRequestData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS); - recurringDepositRequestData.addAll(RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS); - recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName); - // CBS extended fields — Options (for template=true) - recurringDepositRequestData.add("interestCalculationTypeOption"); - recurringDepositRequestData.add("periodFrequencyOptions"); - recurringDepositRequestData.add("yesNoOptions"); - recurringDepositRequestData.add("accountTypeOptions"); - recurringDepositRequestData.add("customerSegmentOptions"); - // CBS extended fields — Details step - recurringDepositRequestData.add("accountType"); - recurringDepositRequestData.add("customerSegment"); - recurringDepositRequestData.add("productCode"); - recurringDepositRequestData.add(SavingsApiConstants.glAccountIdParamName); - recurringDepositRequestData.add("glAccountName"); - recurringDepositRequestData.add("effectiveDate"); - recurringDepositRequestData.add("effectiveTo"); - recurringDepositRequestData.add("endDate"); - recurringDepositRequestData.add("introducerRequired"); - recurringDepositRequestData.add("shareLinkingEnabled"); - recurringDepositRequestData.add("nominationFacility"); - recurringDepositRequestData.add("dormancyAlert"); - // CBS extended fields — Terms step - recurringDepositRequestData.add("interestCalculateSelector"); - recurringDepositRequestData.add("averagePeriod"); - recurringDepositRequestData.add("autoPost"); - recurringDepositRequestData.add("frequencyFixDay"); - recurringDepositRequestData.add("lastInterestDate"); - // CBS extended fields — Settings step - recurringDepositRequestData.add("enableLockinPeriod"); - recurringDepositRequestData.add("balanceFrequency"); - recurringDepositRequestData.add("accountClosedZeroBalanceInDayClosing"); - recurringDepositRequestData.add("balanceSwapOption"); - recurringDepositRequestData.add("msme"); - recurringDepositRequestData.add("autoDebitFacility"); - recurringDepositRequestData.add("loanFacility"); - recurringDepositRequestData.add("prematureWithdrawalOption"); - recurringDepositRequestData.add("tenureFlexibility"); - recurringDepositRequestData.add("lienMarked"); - recurringDepositRequestData.add("maturityInstructions"); - recurringDepositRequestData.add("creditMaturityProceeds"); - recurringDepositRequestData.add("penaltyGracePeriodMissedInstallments"); - recurringDepositRequestData.add("panelRequestApplicable"); - recurringDepositRequestData.add("noPenaltyNomineeHeirClosure"); - recurringDepositRequestData.add("deceasedSettlement"); - recurringDepositRequestData.add("allowDebitTxnAfterDueDate"); - recurringDepositRequestData.add("escheatLiabilityId"); - recurringDepositRequestData.add("accountingMappings"); - recurringDepositRequestData.add("paymentChannelToFundSourceMappings"); - recurringDepositRequestData.add("feeToIncomeAccountMappings"); - recurringDepositRequestData.add("penaltyToIncomeAccountMappings"); - return recurringDepositRequestData; - } - - /** - * Depost Account parameters - */ - - private static final Set DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(localeParamName, dateFormatParamName, monthDayFormatParamName, accountNoParamName, externalIdParamName, - clientIdParamName, groupIdParamName, productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName, - nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, - interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, lockinPeriodFrequencyParamName, - lockinPeriodFrequencyTypeParamName, chargesParamName, chartsParamName, depositAmountParamName, depositPeriodParamName, - depositPeriodFrequencyIdParamName, savingsAccounts, expectedFirstDepositOnDateParamName, - SavingsApiConstants.withHoldTaxParamName, maturityInstructionIdParamName, transferToSavingsIdParamName)); - - public static final Set FIXED_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = fixedDepositAccountRequestData(); - public static final Set FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = fixedDepositAccountResponseData(); - public static final Set FIXED_DEPOSIT_ACCOUNT_INTEREST_CALCULATION_PARAMETERS = fixedDepositInterestCalculationData(); - public static final Set RECURRING_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = recurringDepositAccountRequestData(); - public static final Set RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = recurringDepositAccountResponseData(); - - private static Set fixedDepositInterestCalculationData() { - final Set fixedDepositInterestCalculationData = new HashSet<>(); - fixedDepositInterestCalculationData.add(principalAmountParamName); - fixedDepositInterestCalculationData.add(annualInterestRateParamName); - fixedDepositInterestCalculationData.add(tenureInMonthsParamName); - fixedDepositInterestCalculationData.add(interestPostingPeriodInMonthsParamName); - fixedDepositInterestCalculationData.add(interestCompoundingPeriodInMonthsParamName); - - return fixedDepositInterestCalculationData; - - } - - private static Set fixedDepositAccountRequestData() { - final Set fixedDepositRequestData = new HashSet<>(); - fixedDepositRequestData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS); - fixedDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS); - fixedDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS); - fixedDepositRequestData.add(linkedAccountParamName); - fixedDepositRequestData.add(transferInterestToSavingsParamName); - fixedDepositRequestData.add("accountType"); - fixedDepositRequestData.add("customerSegment"); - fixedDepositRequestData.add(SavingsApiConstants.accountStatusParamName); - fixedDepositRequestData.add(SavingsApiConstants.modeOfOperationParamName); - fixedDepositRequestData.add(SavingsApiConstants.specialCategoryParamName); - fixedDepositRequestData.add(SavingsApiConstants.weakerSectionParamName); - fixedDepositRequestData.add(SavingsApiConstants.guardianNameParamName); - fixedDepositRequestData.add(SavingsApiConstants.guardianCifNumberParamName); - fixedDepositRequestData.add(SavingsApiConstants.guardianRelationshipParamName); - fixedDepositRequestData.add(SavingsApiConstants.guardianClientIdParamName); - fixedDepositRequestData.add(SavingsApiConstants.minorDateOfBirthParamName); - fixedDepositRequestData.add(SavingsApiConstants.introducerIdParamName); - fixedDepositRequestData.add(SavingsApiConstants.introducerNameParamName); - fixedDepositRequestData.add(SavingsApiConstants.introducerCifNumberParamName); - fixedDepositRequestData.add(SavingsApiConstants.nomineeParamName); - fixedDepositRequestData.add("nominees"); - fixedDepositRequestData.add(SavingsApiConstants.linkedAccountIdParamName); - fixedDepositRequestData.add(SavingsApiConstants.linkedAccountTypeParamName); - fixedDepositRequestData.add(SavingsApiConstants.linkedAccountNoParamName); - fixedDepositRequestData.add(SavingsApiConstants.jointHoldersParamName); - fixedDepositRequestData.add(SavingsApiConstants.autoDebitFacilityParamName); - fixedDepositRequestData.add(SavingsApiConstants.nominationFacilityParamName); - return fixedDepositRequestData; - } - - private static Set fixedDepositAccountResponseData() { - final Set fixedDepositResponseData = new HashSet<>(); - fixedDepositResponseData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS); - fixedDepositResponseData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS); - fixedDepositResponseData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS); - fixedDepositResponseData.add(linkedAccountParamName); - fixedDepositResponseData.add(transferInterestToSavingsParamName); - fixedDepositResponseData.add("accountType"); - fixedDepositResponseData.add("customerSegment"); - fixedDepositResponseData.add("accountTypeOptions"); - fixedDepositResponseData.add("customerSegmentOptions"); - fixedDepositResponseData.add("productCode"); - fixedDepositResponseData.add("previewAccountNo"); - fixedDepositResponseData.add(SavingsApiConstants.accountStatusParamName); - fixedDepositResponseData.add(SavingsApiConstants.modeOfOperationParamName); - fixedDepositResponseData.add(SavingsApiConstants.specialCategoryParamName); - fixedDepositResponseData.add(SavingsApiConstants.weakerSectionParamName); - fixedDepositResponseData.add(SavingsApiConstants.guardianNameParamName); - fixedDepositResponseData.add(SavingsApiConstants.guardianCifNumberParamName); - fixedDepositResponseData.add(SavingsApiConstants.guardianRelationshipParamName); - fixedDepositResponseData.add(SavingsApiConstants.guardianClientIdParamName); - fixedDepositResponseData.add(SavingsApiConstants.minorDateOfBirthParamName); - fixedDepositResponseData.add(SavingsApiConstants.introducerIdParamName); - fixedDepositResponseData.add(SavingsApiConstants.introducerNameParamName); - fixedDepositResponseData.add(SavingsApiConstants.introducerCifNumberParamName); - fixedDepositResponseData.add(SavingsApiConstants.nomineeParamName); - fixedDepositResponseData.add(SavingsApiConstants.linkedAccountIdParamName); - fixedDepositResponseData.add(SavingsApiConstants.linkedAccountTypeParamName); - fixedDepositResponseData.add(SavingsApiConstants.linkedAccountNoParamName); - fixedDepositResponseData.add(SavingsApiConstants.jointHoldersParamName); - fixedDepositResponseData.add(SavingsApiConstants.nominationFacilityParamName); - fixedDepositResponseData.add(SavingsApiConstants.autoDebitFacilityParamName); - return fixedDepositResponseData; - } - - private static Set recurringDepositAccountRequestData() { - final Set recurringDepositRequestData = new HashSet<>(); - recurringDepositRequestData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS); - recurringDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS); - recurringDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS); - recurringDepositRequestData.addAll(RECURRING_DETAILS_REQUEST_DATA_PARAMETERS); - // CBS Account fields - recurringDepositRequestData.add("accountType"); - recurringDepositRequestData.add("customerSegment"); - recurringDepositRequestData.add(SavingsApiConstants.accountStatusParamName); - recurringDepositRequestData.add(SavingsApiConstants.modeOfOperationParamName); - recurringDepositRequestData.add(SavingsApiConstants.specialCategoryParamName); - recurringDepositRequestData.add(SavingsApiConstants.weakerSectionParamName); - recurringDepositRequestData.add(SavingsApiConstants.guardianNameParamName); - recurringDepositRequestData.add(SavingsApiConstants.guardianCifNumberParamName); - recurringDepositRequestData.add(SavingsApiConstants.guardianRelationshipParamName); - recurringDepositRequestData.add(SavingsApiConstants.guardianClientIdParamName); - recurringDepositRequestData.add(SavingsApiConstants.minorDateOfBirthParamName); - recurringDepositRequestData.add(SavingsApiConstants.introducerIdParamName); - recurringDepositRequestData.add(SavingsApiConstants.introducerNameParamName); - recurringDepositRequestData.add(SavingsApiConstants.introducerCifNumberParamName); - recurringDepositRequestData.add(SavingsApiConstants.nomineeParamName); - recurringDepositRequestData.add("nominees"); - recurringDepositRequestData.add(SavingsApiConstants.linkedAccountIdParamName); - recurringDepositRequestData.add(SavingsApiConstants.linkedAccountTypeParamName); - recurringDepositRequestData.add(SavingsApiConstants.linkedAccountNoParamName); - recurringDepositRequestData.add(SavingsApiConstants.jointHoldersParamName); - recurringDepositRequestData.add(SavingsApiConstants.balanceSwapOptionParamName); - recurringDepositRequestData.add(SavingsApiConstants.msmeParamName); - recurringDepositRequestData.add(SavingsApiConstants.autoDebitFacilityParamName); - return recurringDepositRequestData; - } - - private static Set recurringDepositAccountResponseData() { - final Set recurringDepositResponseData = new HashSet<>(); - recurringDepositResponseData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS); - recurringDepositResponseData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS); - recurringDepositResponseData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS); - recurringDepositResponseData.addAll(RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS); - // CBS product fields — included in template response when productId is provided - recurringDepositResponseData.add("accountType"); - recurringDepositResponseData.add("customerSegment"); - recurringDepositResponseData.add("accountTypeOptions"); - recurringDepositResponseData.add("customerSegmentOptions"); - recurringDepositResponseData.add("productCode"); - recurringDepositResponseData.add(SavingsApiConstants.glAccountIdParamName); - recurringDepositResponseData.add("glAccountName"); - recurringDepositResponseData.add("effectiveDate"); - recurringDepositResponseData.add("effectiveTo"); - recurringDepositResponseData.add("endDate"); - recurringDepositResponseData.add("introducerRequired"); - recurringDepositResponseData.add("shareLinkingEnabled"); - recurringDepositResponseData.add("nominationFacility"); - recurringDepositResponseData.add("dormancyAlert"); - recurringDepositResponseData.add("interestCalculateSelector"); - recurringDepositResponseData.add("interestCalculationTypeName"); - recurringDepositResponseData.add("averagePeriod"); - recurringDepositResponseData.add("autoPost"); - recurringDepositResponseData.add("frequencyFixDay"); - recurringDepositResponseData.add("lastInterestDate"); - recurringDepositResponseData.add("enableLockinPeriod"); - recurringDepositResponseData.add("balanceFrequency"); - recurringDepositResponseData.add("accountClosedZeroBalanceInDayClosing"); - recurringDepositResponseData.add("balanceSwapOption"); - recurringDepositResponseData.add("msme"); - recurringDepositResponseData.add("autoDebitFacility"); - recurringDepositResponseData.add("loanFacility"); - recurringDepositResponseData.add("prematureWithdrawalOption"); - recurringDepositResponseData.add("tenureFlexibility"); - recurringDepositResponseData.add("lienMarked"); - recurringDepositResponseData.add("maturityInstructions"); - recurringDepositResponseData.add("creditMaturityProceeds"); - recurringDepositResponseData.add("penaltyGracePeriodMissedInstallments"); - recurringDepositResponseData.add("panelRequestApplicable"); - recurringDepositResponseData.add("noPenaltyNomineeHeirClosure"); - recurringDepositResponseData.add("deceasedSettlement"); - recurringDepositResponseData.add("allowDebitTxnAfterDueDate"); - recurringDepositResponseData.add("accountingMappings"); - recurringDepositResponseData.add("paymentChannelToFundSourceMappings"); - recurringDepositResponseData.add("feeToIncomeAccountMappings"); - recurringDepositResponseData.add("penaltyToIncomeAccountMappings"); - return recurringDepositResponseData; - } - - private static final Set RECURRING_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName, - runningBalanceParamName, reversedParamName)); - - private static final Set SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(localeParamName, dateFormatParamName, activatedOnDateParamName)); - - private static final Set SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(chargeIdParamName, savingsAccountChargeIdParamName, chargeNameParamName, penaltyParamName, - chargeTimeTypeParamName, dueAsOfDateParamName, chargeCalculationTypeParamName, percentageParamName, - amountPercentageAppliedToParamName, currencyParamName, amountWaivedParamName, amountWrittenOffParamName, - amountOutstandingParamName, amountOrPercentageParamName, amountParamName, amountPaidParamName, chargeOptionsParamName)); - - private static final Set SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, - feeOnMonthDayParamName, monthDayFormatParamName, feeIntervalParamName)); - - private static final Set SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>( - Arrays.asList(amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName)); -} +/** + * 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; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams; + +public final class DepositsApiConstants { + + private DepositsApiConstants() { + + } + + // Deposit products + public static final String FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME = "fixeddepositproduct"; + public static final String RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME = "recurringdepositproduct"; + + // Deposit accounts + public static final String FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME = "fixeddepositaccount"; + public static final String RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME = "recurringdepositaccount"; + + public static final String SAVINGS_ACCOUNT_RESOURCE_NAME = "savingsaccount"; + public static final String SAVINGS_ACCOUNT_TRANSACTION_RESOURCE_NAME = "savingsaccount.transaction"; + public static final String SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME = "savingsaccountcharge"; + + // Current accounts + public static final String CURRENT_ACCOUNT_RESOURCE_NAME = "currentaccount"; + public static final String CURRENT_PRODUCT_RESOURCE_NAME = "currentproduct"; + public static final String CURRENT_ACCOUNT_CHARGE_RESOURCE_NAME = "currentaccountcharge"; + + // deposit product actions + public static final String summitalAction = ".summital"; + public static final String approvalAction = ".approval"; + public static final String undoApprovalAction = ".undoApproval"; + public static final String rejectAction = ".reject"; + public static final String withdrawnByApplicantAction = ".withdrawnByApplicant"; + public static final String activateAction = ".activate"; + public static final String modifyApplicationAction = ".modify"; + public static final String deleteApplicationAction = ".delete"; + public static final String undoTransactionAction = ".undotransaction"; + public static final String applyAnnualFeeTransactionAction = ".applyannualfee"; + public static final String adjustTransactionAction = ".adjusttransaction"; + public static final String closeAction = ".close"; + public static final String preMatureCloseAction = ".preMatureClose"; + public static final String payChargeTransactionAction = ".paycharge"; + public static final String waiveChargeTransactionAction = ".waivecharge"; + + // command + public static final String COMMAND_UNDO_TRANSACTION = "undo"; + public static final String COMMAND_ADJUST_TRANSACTION = "modify"; + public static final String COMMAND_WAIVE_CHARGE = "waive"; + public static final String COMMAND_PAY_CHARGE = "paycharge"; + public static final String UPDATE_DEPOSIT_AMOUNT = "updateDepositAmount"; + + // general + public static final String localeParamName = "locale"; + public static final String dateFormatParamName = "dateFormat"; + public static final String monthDayFormatParamName = "monthDayFormat"; + + // deposit product and account parameters + public static final String idParamName = "id"; + public static final String accountNoParamName = "accountNo"; + public static final String externalIdParamName = "externalId"; + public static final String statusParamName = "status"; + public static final String clientIdParamName = "clientId"; + public static final String groupIdParamName = "groupId"; + public static final String productIdParamName = "productId"; + public static final String fieldOfficerIdParamName = "fieldOfficerId"; + + public static final String submittedOnDateParamName = "submittedOnDate"; + public static final String rejectedOnDateParamName = "rejectedOnDate"; + public static final String withdrawnOnDateParamName = "withdrawnOnDate"; + public static final String approvedOnDateParamName = "approvedOnDate"; + public static final String activatedOnDateParamName = "activatedOnDate"; + public static final String closedOnDateParamName = "closedOnDate"; + public static final String expectedFirstDepositOnDateParamName = "expectedFirstDepositOnDate"; + + public static final String activeParamName = "active"; + public static final String nameParamName = "name"; + public static final String shortNameParamName = "shortName"; + public static final String descriptionParamName = "description"; + public static final String currencyCodeParamName = "currencyCode"; + public static final String digitsAfterDecimalParamName = "digitsAfterDecimal"; + public static final String inMultiplesOfParamName = "inMultiplesOf"; + public static final String nominalAnnualInterestRateParamName = "nominalAnnualInterestRate"; + public static final String interestCompoundingPeriodTypeParamName = "interestCompoundingPeriodType"; + public static final String interestPostingPeriodTypeParamName = "interestPostingPeriodType"; + public static final String interestCalculationTypeParamName = "interestCalculationType"; + public static final String interestCalculationDaysInYearTypeParamName = "interestCalculationDaysInYearType"; + public static final String lockinPeriodFrequencyParamName = "lockinPeriodFrequency"; + public static final String lockinPeriodFrequencyTypeParamName = "lockinPeriodFrequencyType"; + public static final String feeAmountParamName = "feeAmount";// to be deleted + public static final String feeOnMonthDayParamName = "feeOnMonthDay"; + public static final String feeIntervalParamName = "feeInterval"; + public static final String accountingRuleParamName = "accountingRule"; + public static final String paymentTypeIdParamName = "paymentTypeId"; + public static final String transactionAccountNumberParamName = "accountNumber"; + public static final String checkNumberParamName = "checkNumber"; + public static final String routingCodeParamName = "routingCode"; + public static final String receiptNumberParamName = "receiptNumber"; + public static final String bankNumberParamName = "bankNumber"; + public static final String principalAmountParamName = "principalAmount"; + public static final String annualInterestRateParamName = "annualInterestRate"; + public static final String interestPostingPeriodInMonthsParamName = "interestPostingPeriodInMonths"; + public static final String isSeniorCitizenParamName = "isSeniorCitizen"; + public static final String seniorCitizenInterestRateParamName = "seniorCitizenInterestRate"; + public static final String tenureInMonthsParamName = "tenureInMonths"; + public static final String interestCompoundingPeriodInMonthsParamName = "interestCompoundingPeriodInMonths"; + + // Preclosure parameters + public static final String preClosurePenalApplicableParamName = "preClosurePenalApplicable"; + public static final String preClosurePenalInterestParamName = "preClosurePenalInterest"; + public static final String preClosurePenalInterestOnTypeIdParamName = "preClosurePenalInterestOnTypeId"; + public static final String interestFreePeriodFrequencyType = "interestFreePeriodFrequencyType"; + public static final String preClosurePenalInterestOnType = "preClosurePenalInterestOnType"; + + // term paramters + public static final String minDepositTermParamName = "minDepositTerm"; + public static final String maxDepositTermParamName = "maxDepositTerm"; + public static final String minDepositTermTypeIdParamName = "minDepositTermTypeId"; + public static final String maxDepositTermTypeIdParamName = "maxDepositTermTypeId"; + public static final String minDepositTermType = "minDepositTermType"; + public static final String maxDepositTermType = "maxDepositTermType"; + public static final String inMultiplesOfDepositTermParamName = "inMultiplesOfDepositTerm"; + public static final String inMultiplesOfDepositTermTypeIdParamName = "inMultiplesOfDepositTermTypeId"; + public static final String inMultiplesOfDepositTermType = "inMultiplesOfDepositTermType"; + + public static final String depositAmountParamName = "depositAmount"; + public static final String depositMinAmountParamName = "minDepositAmount"; + public static final String depositMaxAmountParamName = "maxDepositAmount"; + public static final String depositPeriodParamName = "depositPeriod"; + public static final String depositPeriodFrequencyIdParamName = "depositPeriodFrequencyId"; + + // recurring parameters + public static final String mandatoryRecommendedDepositAmountParamName = "mandatoryRecommendedDepositAmount"; + public static final String isMandatoryDepositParamName = "isMandatoryDeposit"; + public static final String allowWithdrawalParamName = "allowWithdrawal"; + public static final String adjustAdvanceTowardsFuturePaymentsParamName = "adjustAdvanceTowardsFuturePayments"; + + public static final String recurringFrequencyTypeParamName = "recurringFrequencyType"; + public static final String recurringFrequencyParamName = "recurringFrequency"; + public static final String isCalendarInheritedParamName = "isCalendarInherited"; + + // transaction parameters + public static final String transactionDateParamName = "transactionDate"; + public static final String transactionAmountParamName = "transactionAmount"; + public static final String paymentDetailDataParamName = "paymentDetailData"; + public static final String runningBalanceParamName = "runningBalance"; + public static final String reversedParamName = "reversed"; + public static final String dateParamName = "date"; + public static final String accountIdParamName = "accountId"; + + // recurring deposits update parameters + public static final String effectiveDateParamName = "effectiveDate"; + + // charges parameters + public static final String chargeIdParamName = "chargeId"; + public static final String chargesParamName = "charges"; + public static final String savingsAccountChargeIdParamName = "savingsAccountChargeId"; + public static final String chargeNameParamName = "name"; + public static final String penaltyParamName = "penalty"; + public static final String chargeTimeTypeParamName = "chargeTimeType"; + public static final String dueAsOfDateParamName = "dueDate"; + public static final String chargeCalculationTypeParamName = "chargeCalculationType"; + public static final String percentageParamName = "percentage"; + public static final String amountPercentageAppliedToParamName = "amountPercentageAppliedTo"; + public static final String currencyParamName = "currency"; + public static final String amountWaivedParamName = "amountWaived"; + public static final String amountWrittenOffParamName = "amountWrittenOff"; + public static final String amountOutstandingParamName = "amountOutstanding"; + public static final String amountOrPercentageParamName = "amountOrPercentage"; + public static final String amountParamName = "amount"; + public static final String isManualTransaction = "isManualTransaction"; + public static final String lienTransaction = "lienTransaction"; + public static final String chargesPaidByData = "chargesPaidByData"; + public static final String amountPaidParamName = "amountPaid"; + public static final String chargeOptionsParamName = "chargeOptions"; + public static final String chargePaymentModeParamName = "chargePaymentMode"; + + public static final String noteParamName = "note"; + public static final String chartsParamName = "charts"; + public static final String chartIdParamName = "chartId"; + + // deposit account associations + public static final String transactions = "transactions"; + public static final String charges = "charges"; + public static final String activeChart = "activeChart"; + + // account closure + public static final String onAccountClosureIdParamName = "onAccountClosureId"; + public static final String transferDescriptionParamName = "transferDescription"; + public static final String toSavingsAccountIdParamName = "toSavingsAccountId"; + public static final String savingsAccounts = "savingsAccounts"; + public static final String maturityInstructionIdParamName = "maturityInstructionId"; + public static final String transferToSavingsIdParamName = "transferToSavingsId"; + + public static final String preMatureCloseOnDateParamName = "preMatureCloseOnDate"; + + public static final String linkedAccountParamName = "linkAccountId"; + public static final String transferInterestToSavingsParamName = "transferInterestToSavings"; + + // template + public static final String chartTemplate = "chartTemplate"; + + /** + * Deposit Product Parameters + */ + private static final Set DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, + monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName, + digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, + interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, + lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, accountingRuleParamName, chargesParamName, + SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), + SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), + SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), + SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), + SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(), + SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), + SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), + SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), chartsParamName, SavingsApiConstants.withHoldTaxParamName, + SavingsApiConstants.taxGroupIdParamName)); + + private static final Set PRECLOSURE_REQUEST_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(preClosurePenalApplicableParamName, preClosurePenalInterestParamName, preClosurePenalInterestOnTypeIdParamName)); + + private static final Set PRECLOSURE_RESPONSE_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(preClosurePenalApplicableParamName, preClosurePenalInterestParamName, preClosurePenalInterestOnType)); + + private static final Set DEPOSIT_TERM_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(minDepositTermParamName, + maxDepositTermParamName, minDepositTermTypeIdParamName, maxDepositTermTypeIdParamName, inMultiplesOfDepositTermParamName, + inMultiplesOfDepositTermTypeIdParamName, depositAmountParamName, depositMinAmountParamName, depositMaxAmountParamName)); + + private static final Set DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(minDepositTermParamName, + maxDepositTermParamName, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTermParamName, + inMultiplesOfDepositTermType, depositAmountParamName, depositMinAmountParamName, depositMaxAmountParamName)); + + private static final Set RECURRING_DETAILS_REQUEST_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(mandatoryRecommendedDepositAmountParamName, isMandatoryDepositParamName, allowWithdrawalParamName, + adjustAdvanceTowardsFuturePaymentsParamName, recurringFrequencyTypeParamName, recurringFrequencyParamName, + isCalendarInheritedParamName)); + + private static final Set RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(mandatoryRecommendedDepositAmountParamName, isMandatoryDepositParamName, allowWithdrawalParamName, + adjustAdvanceTowardsFuturePaymentsParamName, recurringFrequencyTypeParamName, recurringFrequencyParamName, + isCalendarInheritedParamName)); + + private static final Set DEPOSIT_PRECLOSURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(preMatureCloseOnDateParamName)); + + public static final Set FIXED_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = fixedDepositProductRequestData(); + public static final Set FIXED_DEPOSIT_PRODUCT_RESPONSE_DATA_PARAMETERS = fixedDepositProductResponseData(); + + public static final Set RECURRING_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = recurringDepositProductRequestData(); + public static final Set RECURRING_DEPOSIT_PRODUCT_RESPONSE_DATA_PARAMETERS = recurringDepositProductResponseData(); + + private static Set fixedDepositProductRequestData() { + final Set fixedDepositRequestData = new HashSet<>(); + fixedDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS); + fixedDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS); + fixedDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS); + fixedDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName); + fixedDepositRequestData.add("accountType"); + fixedDepositRequestData.add("customerSegment"); + fixedDepositRequestData.add("productCode"); + fixedDepositRequestData.add("fdType"); + fixedDepositRequestData.add(SavingsApiConstants.glAccountIdParamName); + fixedDepositRequestData.add("effectiveDate"); + fixedDepositRequestData.add("effectiveTo"); + fixedDepositRequestData.add("endDate"); + fixedDepositRequestData.add("introducerRequired"); + fixedDepositRequestData.add("shareLinkingEnabled"); + fixedDepositRequestData.add("nominationFacility"); + fixedDepositRequestData.add("dormancyAlert"); + fixedDepositRequestData.add(SavingsApiConstants.isDormancyTrackingActiveParamName); + fixedDepositRequestData.add(SavingsApiConstants.daysToInactiveParamName); + fixedDepositRequestData.add(SavingsApiConstants.daysToDormancyParamName); + fixedDepositRequestData.add(SavingsApiConstants.daysToEscheatParamName); + fixedDepositRequestData.add("interestCalculateSelector"); + fixedDepositRequestData.add("averagePeriod"); + fixedDepositRequestData.add("autoPost"); + fixedDepositRequestData.add("frequencyFixDay"); + fixedDepositRequestData.add("lastInterestDate"); + fixedDepositRequestData.add("enableLockinPeriod"); + fixedDepositRequestData.add("balanceFrequency"); + fixedDepositRequestData.add("accountClosedZeroBalanceInDayClosing"); + fixedDepositRequestData.add("autoDebitFacility"); + fixedDepositRequestData.add("loanFacility"); + fixedDepositRequestData.add("prematureWithdrawalOption"); + fixedDepositRequestData.add("tenureFlexibility"); + fixedDepositRequestData.add("lienMarked"); + fixedDepositRequestData.add("withdrawalFeeForTransfers"); + fixedDepositRequestData.add("maturityInstructions"); + fixedDepositRequestData.add("creditMaturityProceeds"); + fixedDepositRequestData.add("penaltyGracePeriodMissedInstallments"); + fixedDepositRequestData.add("panelRequestApplicable"); + fixedDepositRequestData.add("noPenaltyNomineeHeirClosure"); + fixedDepositRequestData.add("deceasedSettlement"); + fixedDepositRequestData.add("allowDebitTxnAfterDueDate"); + fixedDepositRequestData.add("escheatLiabilityId"); + fixedDepositRequestData.add("fdSlabs"); + return fixedDepositRequestData; + } + + private static Set fixedDepositProductResponseData() { + final Set fixedDepositRequestData = new HashSet<>(); + fixedDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS); + fixedDepositRequestData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS); + fixedDepositRequestData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS); + fixedDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName); + fixedDepositRequestData.add("interestCalculationTypeOption"); + fixedDepositRequestData.add("periodFrequencyOptions"); + fixedDepositRequestData.add("yesNoOptions"); + fixedDepositRequestData.add("accountTypeOptions"); + fixedDepositRequestData.add("customerSegmentOptions"); + fixedDepositRequestData.add("accountType"); + fixedDepositRequestData.add("customerSegment"); + fixedDepositRequestData.add("productCode"); + fixedDepositRequestData.add("fdType"); + fixedDepositRequestData.add(SavingsApiConstants.glAccountIdParamName); + fixedDepositRequestData.add("glAccountName"); + fixedDepositRequestData.add("effectiveDate"); + fixedDepositRequestData.add("effectiveTo"); + fixedDepositRequestData.add("endDate"); + fixedDepositRequestData.add("introducerRequired"); + fixedDepositRequestData.add("shareLinkingEnabled"); + fixedDepositRequestData.add("nominationFacility"); + fixedDepositRequestData.add("dormancyAlert"); + fixedDepositRequestData.add(SavingsApiConstants.isDormancyTrackingActiveParamName); + fixedDepositRequestData.add(SavingsApiConstants.daysToInactiveParamName); + fixedDepositRequestData.add(SavingsApiConstants.daysToDormancyParamName); + fixedDepositRequestData.add(SavingsApiConstants.daysToEscheatParamName); + fixedDepositRequestData.add("interestCalculateSelector"); + fixedDepositRequestData.add("averagePeriod"); + fixedDepositRequestData.add("autoPost"); + fixedDepositRequestData.add("frequencyFixDay"); + fixedDepositRequestData.add("lastInterestDate"); + fixedDepositRequestData.add("enableLockinPeriod"); + fixedDepositRequestData.add("balanceFrequency"); + fixedDepositRequestData.add("accountClosedZeroBalanceInDayClosing"); + fixedDepositRequestData.add("autoDebitFacility"); + fixedDepositRequestData.add("loanFacility"); + fixedDepositRequestData.add("prematureWithdrawalOption"); + fixedDepositRequestData.add("tenureFlexibility"); + fixedDepositRequestData.add("lienMarked"); + fixedDepositRequestData.add("withdrawalFeeForTransfers"); + fixedDepositRequestData.add("maturityInstructions"); + fixedDepositRequestData.add("creditMaturityProceeds"); + fixedDepositRequestData.add("penaltyGracePeriodMissedInstallments"); + fixedDepositRequestData.add("panelRequestApplicable"); + fixedDepositRequestData.add("noPenaltyNomineeHeirClosure"); + fixedDepositRequestData.add("deceasedSettlement"); + fixedDepositRequestData.add("allowDebitTxnAfterDueDate"); + fixedDepositRequestData.add("accountingMappings"); + fixedDepositRequestData.add("paymentChannelToFundSourceMappings"); + fixedDepositRequestData.add("feeToIncomeAccountMappings"); + fixedDepositRequestData.add("penaltyToIncomeAccountMappings"); + return fixedDepositRequestData; + } + + private static Set recurringDepositProductRequestData() { + final Set recurringDepositRequestData = new HashSet<>(); + recurringDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS); + recurringDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS); + recurringDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS); + recurringDepositRequestData.addAll(RECURRING_DETAILS_REQUEST_DATA_PARAMETERS); + recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName); + // Custom CBS fields — Details step + recurringDepositRequestData.add("accountType"); + recurringDepositRequestData.add("customerSegment"); + recurringDepositRequestData.add("productCode"); + recurringDepositRequestData.add(SavingsApiConstants.glAccountIdParamName); + recurringDepositRequestData.add("effectiveTo"); + recurringDepositRequestData.add("endDate"); + recurringDepositRequestData.add("introducerRequired"); + recurringDepositRequestData.add("shareLinkingEnabled"); + // Custom CBS fields — Currency step + recurringDepositRequestData.add("dormancyAlert"); + recurringDepositRequestData.add(SavingsApiConstants.daysToInactiveParamName); + recurringDepositRequestData.add(SavingsApiConstants.daysToDormancyParamName); + recurringDepositRequestData.add(SavingsApiConstants.daysToEscheatParamName); + recurringDepositRequestData.add("balanceSwapOption"); + recurringDepositRequestData.add("msme"); + recurringDepositRequestData.add("nominationFacility"); + recurringDepositRequestData.add("autoDebitFacility"); + // Custom CBS fields — Terms step + recurringDepositRequestData.add("effectiveDate"); + recurringDepositRequestData.add("interestCalculateSelector"); + recurringDepositRequestData.add("averagePeriod"); + recurringDepositRequestData.add("autoPost"); + recurringDepositRequestData.add("frequencyFixDay"); + recurringDepositRequestData.add("lastInterestDate"); + // Custom CBS fields — Settings step + recurringDepositRequestData.add("loanFacility"); + recurringDepositRequestData.add("prematureWithdrawalOption"); + recurringDepositRequestData.add("tenureFlexibility"); + recurringDepositRequestData.add("lienMarked"); + recurringDepositRequestData.add("withdrawalFeeForTransfers"); + recurringDepositRequestData.add("enableLockinPeriod"); + recurringDepositRequestData.add("maturityInstructions"); + recurringDepositRequestData.add("creditMaturityProceeds"); + recurringDepositRequestData.add("penaltyGracePeriodMissedInstallments"); + recurringDepositRequestData.add("panelRequestApplicable"); + recurringDepositRequestData.add("noPenaltyNomineeHeirClosure"); + recurringDepositRequestData.add("deceasedSettlement"); + recurringDepositRequestData.add("allowDebitTxnAfterDueDate"); + recurringDepositRequestData.add("accountClosedZeroBalanceInDayClosing"); + recurringDepositRequestData.add("escheatLiabilityId"); + // Custom CBS fields — Interest rate slabs (sent inline but ignored server-side; saved separately) + recurringDepositRequestData.add("rdSlabs"); + return recurringDepositRequestData; + } + + private static Set recurringDepositProductResponseData() { + final Set recurringDepositRequestData = new HashSet<>(); + recurringDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS); + recurringDepositRequestData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS); + recurringDepositRequestData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS); + recurringDepositRequestData.addAll(RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS); + recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName); + // CBS extended fields — Options (for template=true) + recurringDepositRequestData.add("interestCalculationTypeOption"); + recurringDepositRequestData.add("periodFrequencyOptions"); + recurringDepositRequestData.add("yesNoOptions"); + recurringDepositRequestData.add("accountTypeOptions"); + recurringDepositRequestData.add("customerSegmentOptions"); + // CBS extended fields — Details step + recurringDepositRequestData.add("accountType"); + recurringDepositRequestData.add("customerSegment"); + recurringDepositRequestData.add("productCode"); + recurringDepositRequestData.add(SavingsApiConstants.glAccountIdParamName); + recurringDepositRequestData.add("glAccountName"); + recurringDepositRequestData.add("effectiveDate"); + recurringDepositRequestData.add("effectiveTo"); + recurringDepositRequestData.add("endDate"); + recurringDepositRequestData.add("introducerRequired"); + recurringDepositRequestData.add("shareLinkingEnabled"); + recurringDepositRequestData.add("nominationFacility"); + recurringDepositRequestData.add("dormancyAlert"); + // CBS extended fields — Terms step + recurringDepositRequestData.add("interestCalculateSelector"); + recurringDepositRequestData.add("averagePeriod"); + recurringDepositRequestData.add("autoPost"); + recurringDepositRequestData.add("frequencyFixDay"); + recurringDepositRequestData.add("lastInterestDate"); + // CBS extended fields — Settings step + recurringDepositRequestData.add("enableLockinPeriod"); + recurringDepositRequestData.add("balanceFrequency"); + recurringDepositRequestData.add("accountClosedZeroBalanceInDayClosing"); + recurringDepositRequestData.add("balanceSwapOption"); + recurringDepositRequestData.add("msme"); + recurringDepositRequestData.add("autoDebitFacility"); + recurringDepositRequestData.add("loanFacility"); + recurringDepositRequestData.add("prematureWithdrawalOption"); + recurringDepositRequestData.add("tenureFlexibility"); + recurringDepositRequestData.add("lienMarked"); + recurringDepositRequestData.add("maturityInstructions"); + recurringDepositRequestData.add("creditMaturityProceeds"); + recurringDepositRequestData.add("penaltyGracePeriodMissedInstallments"); + recurringDepositRequestData.add("panelRequestApplicable"); + recurringDepositRequestData.add("noPenaltyNomineeHeirClosure"); + recurringDepositRequestData.add("deceasedSettlement"); + recurringDepositRequestData.add("allowDebitTxnAfterDueDate"); + recurringDepositRequestData.add("escheatLiabilityId"); + recurringDepositRequestData.add("accountingMappings"); + recurringDepositRequestData.add("paymentChannelToFundSourceMappings"); + recurringDepositRequestData.add("feeToIncomeAccountMappings"); + recurringDepositRequestData.add("penaltyToIncomeAccountMappings"); + return recurringDepositRequestData; + } + + /** + * Depost Account parameters + */ + + private static final Set DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(localeParamName, dateFormatParamName, monthDayFormatParamName, accountNoParamName, externalIdParamName, + clientIdParamName, groupIdParamName, productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName, + nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, + interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, lockinPeriodFrequencyParamName, + lockinPeriodFrequencyTypeParamName, chargesParamName, chartsParamName, depositAmountParamName, depositPeriodParamName, + depositPeriodFrequencyIdParamName, savingsAccounts, expectedFirstDepositOnDateParamName, + SavingsApiConstants.withHoldTaxParamName, maturityInstructionIdParamName, transferToSavingsIdParamName)); + + public static final Set FIXED_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = fixedDepositAccountRequestData(); + public static final Set FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = fixedDepositAccountResponseData(); + public static final Set FIXED_DEPOSIT_ACCOUNT_INTEREST_CALCULATION_PARAMETERS = fixedDepositInterestCalculationData(); + public static final Set RECURRING_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = recurringDepositAccountRequestData(); + public static final Set RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = recurringDepositAccountResponseData(); + + private static Set fixedDepositInterestCalculationData() { + final Set fixedDepositInterestCalculationData = new HashSet<>(); + fixedDepositInterestCalculationData.add(principalAmountParamName); + fixedDepositInterestCalculationData.add(annualInterestRateParamName); + fixedDepositInterestCalculationData.add(tenureInMonthsParamName); + fixedDepositInterestCalculationData.add(interestPostingPeriodInMonthsParamName); + fixedDepositInterestCalculationData.add(interestCompoundingPeriodInMonthsParamName); + + return fixedDepositInterestCalculationData; + + } + + private static Set fixedDepositAccountRequestData() { + final Set fixedDepositRequestData = new HashSet<>(); + fixedDepositRequestData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS); + fixedDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS); + fixedDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS); + fixedDepositRequestData.add(linkedAccountParamName); + fixedDepositRequestData.add(transferInterestToSavingsParamName); + fixedDepositRequestData.add("accountType"); + fixedDepositRequestData.add("customerSegment"); + fixedDepositRequestData.add(SavingsApiConstants.accountStatusParamName); + fixedDepositRequestData.add(SavingsApiConstants.modeOfOperationParamName); + fixedDepositRequestData.add(SavingsApiConstants.specialCategoryParamName); + fixedDepositRequestData.add(SavingsApiConstants.weakerSectionParamName); + fixedDepositRequestData.add(SavingsApiConstants.guardianNameParamName); + fixedDepositRequestData.add(SavingsApiConstants.guardianCifNumberParamName); + fixedDepositRequestData.add(SavingsApiConstants.guardianRelationshipParamName); + fixedDepositRequestData.add(SavingsApiConstants.guardianClientIdParamName); + fixedDepositRequestData.add(SavingsApiConstants.minorDateOfBirthParamName); + fixedDepositRequestData.add(SavingsApiConstants.introducerIdParamName); + fixedDepositRequestData.add(SavingsApiConstants.introducerNameParamName); + fixedDepositRequestData.add(SavingsApiConstants.introducerCifNumberParamName); + fixedDepositRequestData.add(SavingsApiConstants.nomineeParamName); + fixedDepositRequestData.add("nominees"); + fixedDepositRequestData.add(SavingsApiConstants.linkedAccountIdParamName); + fixedDepositRequestData.add(SavingsApiConstants.linkedAccountTypeParamName); + fixedDepositRequestData.add(SavingsApiConstants.linkedAccountNoParamName); + fixedDepositRequestData.add(SavingsApiConstants.jointHoldersParamName); + fixedDepositRequestData.add(SavingsApiConstants.autoDebitFacilityParamName); + fixedDepositRequestData.add(SavingsApiConstants.nominationFacilityParamName); + fixedDepositRequestData.add(isSeniorCitizenParamName); + fixedDepositRequestData.add(seniorCitizenInterestRateParamName); + return fixedDepositRequestData; + } + + private static Set fixedDepositAccountResponseData() { + final Set fixedDepositResponseData = new HashSet<>(); + fixedDepositResponseData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS); + fixedDepositResponseData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS); + fixedDepositResponseData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS); + fixedDepositResponseData.add(linkedAccountParamName); + fixedDepositResponseData.add(transferInterestToSavingsParamName); + fixedDepositResponseData.add("accountType"); + fixedDepositResponseData.add("customerSegment"); + fixedDepositResponseData.add("accountTypeOptions"); + fixedDepositResponseData.add("customerSegmentOptions"); + fixedDepositResponseData.add("productCode"); + fixedDepositResponseData.add("previewAccountNo"); + fixedDepositResponseData.add(SavingsApiConstants.accountStatusParamName); + fixedDepositResponseData.add(SavingsApiConstants.modeOfOperationParamName); + fixedDepositResponseData.add(SavingsApiConstants.specialCategoryParamName); + fixedDepositResponseData.add(SavingsApiConstants.weakerSectionParamName); + fixedDepositResponseData.add(SavingsApiConstants.guardianNameParamName); + fixedDepositResponseData.add(SavingsApiConstants.guardianCifNumberParamName); + fixedDepositResponseData.add(SavingsApiConstants.guardianRelationshipParamName); + fixedDepositResponseData.add(SavingsApiConstants.guardianClientIdParamName); + fixedDepositResponseData.add(SavingsApiConstants.minorDateOfBirthParamName); + fixedDepositResponseData.add(SavingsApiConstants.introducerIdParamName); + fixedDepositResponseData.add(SavingsApiConstants.introducerNameParamName); + fixedDepositResponseData.add(SavingsApiConstants.introducerCifNumberParamName); + fixedDepositResponseData.add(SavingsApiConstants.nomineeParamName); + fixedDepositResponseData.add(SavingsApiConstants.linkedAccountIdParamName); + fixedDepositResponseData.add(SavingsApiConstants.linkedAccountTypeParamName); + fixedDepositResponseData.add(SavingsApiConstants.linkedAccountNoParamName); + fixedDepositResponseData.add(SavingsApiConstants.jointHoldersParamName); + fixedDepositResponseData.add(SavingsApiConstants.nominationFacilityParamName); + fixedDepositResponseData.add(SavingsApiConstants.autoDebitFacilityParamName); + return fixedDepositResponseData; + } + + private static Set recurringDepositAccountRequestData() { + final Set recurringDepositRequestData = new HashSet<>(); + recurringDepositRequestData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS); + recurringDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS); + recurringDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS); + recurringDepositRequestData.addAll(RECURRING_DETAILS_REQUEST_DATA_PARAMETERS); + // CBS Account fields + recurringDepositRequestData.add("accountType"); + recurringDepositRequestData.add("customerSegment"); + recurringDepositRequestData.add(SavingsApiConstants.accountStatusParamName); + recurringDepositRequestData.add(SavingsApiConstants.modeOfOperationParamName); + recurringDepositRequestData.add(SavingsApiConstants.specialCategoryParamName); + recurringDepositRequestData.add(SavingsApiConstants.weakerSectionParamName); + recurringDepositRequestData.add(SavingsApiConstants.guardianNameParamName); + recurringDepositRequestData.add(SavingsApiConstants.guardianCifNumberParamName); + recurringDepositRequestData.add(SavingsApiConstants.guardianRelationshipParamName); + recurringDepositRequestData.add(SavingsApiConstants.guardianClientIdParamName); + recurringDepositRequestData.add(SavingsApiConstants.minorDateOfBirthParamName); + recurringDepositRequestData.add(SavingsApiConstants.introducerIdParamName); + recurringDepositRequestData.add(SavingsApiConstants.introducerNameParamName); + recurringDepositRequestData.add(SavingsApiConstants.introducerCifNumberParamName); + recurringDepositRequestData.add(SavingsApiConstants.nomineeParamName); + recurringDepositRequestData.add("nominees"); + recurringDepositRequestData.add(SavingsApiConstants.linkedAccountIdParamName); + recurringDepositRequestData.add(SavingsApiConstants.linkedAccountTypeParamName); + recurringDepositRequestData.add(SavingsApiConstants.linkedAccountNoParamName); + recurringDepositRequestData.add(SavingsApiConstants.jointHoldersParamName); + recurringDepositRequestData.add(SavingsApiConstants.balanceSwapOptionParamName); + recurringDepositRequestData.add(SavingsApiConstants.msmeParamName); + recurringDepositRequestData.add(SavingsApiConstants.autoDebitFacilityParamName); + recurringDepositRequestData.add(isSeniorCitizenParamName); + recurringDepositRequestData.add(seniorCitizenInterestRateParamName); + return recurringDepositRequestData; + } + + private static Set recurringDepositAccountResponseData() { + final Set recurringDepositResponseData = new HashSet<>(); + recurringDepositResponseData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS); + recurringDepositResponseData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS); + recurringDepositResponseData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS); + recurringDepositResponseData.addAll(RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS); + // CBS product fields — included in template response when productId is provided + recurringDepositResponseData.add("accountType"); + recurringDepositResponseData.add("customerSegment"); + recurringDepositResponseData.add("accountTypeOptions"); + recurringDepositResponseData.add("customerSegmentOptions"); + recurringDepositResponseData.add("productCode"); + recurringDepositResponseData.add(SavingsApiConstants.glAccountIdParamName); + recurringDepositResponseData.add("glAccountName"); + recurringDepositResponseData.add("effectiveDate"); + recurringDepositResponseData.add("effectiveTo"); + recurringDepositResponseData.add("endDate"); + recurringDepositResponseData.add("introducerRequired"); + recurringDepositResponseData.add("shareLinkingEnabled"); + recurringDepositResponseData.add("nominationFacility"); + recurringDepositResponseData.add("dormancyAlert"); + recurringDepositResponseData.add("interestCalculateSelector"); + recurringDepositResponseData.add("interestCalculationTypeName"); + recurringDepositResponseData.add("averagePeriod"); + recurringDepositResponseData.add("autoPost"); + recurringDepositResponseData.add("frequencyFixDay"); + recurringDepositResponseData.add("lastInterestDate"); + recurringDepositResponseData.add("enableLockinPeriod"); + recurringDepositResponseData.add("balanceFrequency"); + recurringDepositResponseData.add("accountClosedZeroBalanceInDayClosing"); + recurringDepositResponseData.add("balanceSwapOption"); + recurringDepositResponseData.add("msme"); + recurringDepositResponseData.add("autoDebitFacility"); + recurringDepositResponseData.add("loanFacility"); + recurringDepositResponseData.add("prematureWithdrawalOption"); + recurringDepositResponseData.add("tenureFlexibility"); + recurringDepositResponseData.add("lienMarked"); + recurringDepositResponseData.add("maturityInstructions"); + recurringDepositResponseData.add("creditMaturityProceeds"); + recurringDepositResponseData.add("penaltyGracePeriodMissedInstallments"); + recurringDepositResponseData.add("panelRequestApplicable"); + recurringDepositResponseData.add("noPenaltyNomineeHeirClosure"); + recurringDepositResponseData.add("deceasedSettlement"); + recurringDepositResponseData.add("allowDebitTxnAfterDueDate"); + recurringDepositResponseData.add("accountingMappings"); + recurringDepositResponseData.add("paymentChannelToFundSourceMappings"); + recurringDepositResponseData.add("feeToIncomeAccountMappings"); + recurringDepositResponseData.add("penaltyToIncomeAccountMappings"); + return recurringDepositResponseData; + } + + private static final Set RECURRING_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName, + runningBalanceParamName, reversedParamName)); + + private static final Set SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(localeParamName, dateFormatParamName, activatedOnDateParamName)); + + private static final Set SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(chargeIdParamName, savingsAccountChargeIdParamName, chargeNameParamName, penaltyParamName, + chargeTimeTypeParamName, dueAsOfDateParamName, chargeCalculationTypeParamName, percentageParamName, + amountPercentageAppliedToParamName, currencyParamName, amountWaivedParamName, amountWrittenOffParamName, + amountOutstandingParamName, amountOrPercentageParamName, amountParamName, amountPaidParamName, chargeOptionsParamName)); + + private static final Set SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, + feeOnMonthDayParamName, monthDayFormatParamName, feeIntervalParamName)); + + private static final Set SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName)); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java index 7055c5d6d..6fde2049b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java @@ -31,6 +31,8 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.deposit import static org.apache.fineract.portfolio.savings.DepositsApiConstants.expectedFirstDepositOnDateParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isCalendarInheritedParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isMandatoryDepositParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isSeniorCitizenParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.seniorCitizenInterestRateParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.mandatoryRecommendedDepositAmountParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maturityInstructionIdParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferInterestToSavingsParamName; @@ -230,6 +232,16 @@ public class DepositAccountAssembler { interestRate = product.nominalAnnualInterestRate(); } + // Senior citizen interest rate override: use the higher rate when applicable + if (command.parameterExists(isSeniorCitizenParamName) && command.booleanPrimitiveValueOfParameterNamed(isSeniorCitizenParamName)) { + if (command.parameterExists(seniorCitizenInterestRateParamName)) { + final BigDecimal scRate = command.bigDecimalValueOfParameterNamed(seniorCitizenInterestRateParamName); + if (scRate != null && scRate.compareTo(BigDecimal.ZERO) > 0) { + interestRate = scRate; + } + } + } + SavingsCompoundingInterestPeriodType interestCompoundingPeriodType = null; final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName); if (interestPeriodTypeValue != null) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateEffective.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateEffective.java index 688b29e18..e5891c0f3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateEffective.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateEffective.java @@ -1,75 +1,75 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.apache.fineract.portfolio.savings.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import java.time.LocalDate; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table(name = "m_fd_base_interest_rate_effective") -@Getter -@NoArgsConstructor -public class FdBaseInterestRateEffective { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "fd_type", length = 20) - private String fdType; - - @Column(name = "version_label") - private String versionLabel; - - @Column(name = "effective_from_date") - private LocalDate effectiveFromDate; - - @Column(name = "effective_to_date") - private LocalDate effectiveToDate; - - @Column(name = "is_active") - private Boolean isActive; - - @Column(name = "remarks") - private String remarks; - - @Column(name = "created_by_id") - private Long createdById; - - @Column(name = "created_date") - private LocalDate createdDate; - - public static FdBaseInterestRateEffective create(String fdType, String versionLabel, LocalDate effectiveFromDate, String remarks, - Long createdById) { - FdBaseInterestRateEffective e = new FdBaseInterestRateEffective(); - e.fdType = fdType; - e.versionLabel = versionLabel; - e.effectiveFromDate = effectiveFromDate; - e.isActive = true; - e.remarks = remarks; - e.createdById = createdById; - e.createdDate = LocalDate.now(); - return e; - } - - public void deactivate(LocalDate toDate) { - this.isActive = false; - this.effectiveToDate = toDate; - } -} +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.apache.fineract.portfolio.savings.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.time.LocalDate; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "m_cbs_base_interest_rate_effective") +@Getter +@NoArgsConstructor +public class FdBaseInterestRateEffective { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "fd_type", length = 20) + private String fdType; + + @Column(name = "version_label") + private String versionLabel; + + @Column(name = "effective_from_date") + private LocalDate effectiveFromDate; + + @Column(name = "effective_to_date") + private LocalDate effectiveToDate; + + @Column(name = "is_active") + private Boolean isActive; + + @Column(name = "remarks") + private String remarks; + + @Column(name = "created_by_id") + private Long createdById; + + @Column(name = "created_date") + private LocalDate createdDate; + + public static FdBaseInterestRateEffective create(String fdType, String versionLabel, LocalDate effectiveFromDate, String remarks, + Long createdById) { + FdBaseInterestRateEffective e = new FdBaseInterestRateEffective(); + e.fdType = fdType; + e.versionLabel = versionLabel; + e.effectiveFromDate = effectiveFromDate; + e.isActive = true; + e.remarks = remarks; + e.createdById = createdById; + e.createdDate = LocalDate.now(); + return e; + } + + public void deactivate(LocalDate toDate) { + this.isActive = false; + this.effectiveToDate = toDate; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateSlabHistory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateSlabHistory.java index 6a6974660..2b6e842f1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateSlabHistory.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateSlabHistory.java @@ -1,76 +1,76 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.apache.fineract.portfolio.savings.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import java.math.BigDecimal; -import java.time.LocalDate; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table(name = "m_fd_base_interest_rate_slab_history") -@Getter -@NoArgsConstructor -public class FdBaseInterestRateSlabHistory { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "effective_id") - private Long effectiveId; - - @Column(name = "sr_no") - private Integer srNo; - - @Column(name = "description") - private String description; - - @Column(name = "period_type_enum") - private Integer periodTypeEnum; - - @Column(name = "from_period") - private Integer fromPeriod; - - @Column(name = "to_period") - private Integer toPeriod; - - @Column(name = "annual_interest_rate") - private BigDecimal annualInterestRate; - - @Column(name = "senior_citizen_rate") - private BigDecimal seniorCitizenRate; - - @Column(name = "created_date") - private LocalDate createdDate; - - public static FdBaseInterestRateSlabHistory create(Long effectiveId, Integer srNo, String description, Integer periodTypeEnum, - Integer fromPeriod, Integer toPeriod, BigDecimal annualInterestRate, BigDecimal seniorCitizenRate) { - FdBaseInterestRateSlabHistory s = new FdBaseInterestRateSlabHistory(); - s.effectiveId = effectiveId; - s.srNo = srNo; - s.description = description; - s.periodTypeEnum = periodTypeEnum != null ? periodTypeEnum : 1; - s.fromPeriod = fromPeriod; - s.toPeriod = toPeriod; - s.annualInterestRate = annualInterestRate; - s.seniorCitizenRate = seniorCitizenRate; - s.createdDate = LocalDate.now(); - return s; - } -} +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.apache.fineract.portfolio.savings.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.math.BigDecimal; +import java.time.LocalDate; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "m_cbs_base_interest_rate_slab_history") +@Getter +@NoArgsConstructor +public class FdBaseInterestRateSlabHistory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "effective_id") + private Long effectiveId; + + @Column(name = "sr_no") + private Integer srNo; + + @Column(name = "description") + private String description; + + @Column(name = "period_type_enum") + private Integer periodTypeEnum; + + @Column(name = "from_period") + private Integer fromPeriod; + + @Column(name = "to_period") + private Integer toPeriod; + + @Column(name = "annual_interest_rate") + private BigDecimal annualInterestRate; + + @Column(name = "senior_citizen_rate") + private BigDecimal seniorCitizenRate; + + @Column(name = "created_date") + private LocalDate createdDate; + + public static FdBaseInterestRateSlabHistory create(Long effectiveId, Integer srNo, String description, Integer periodTypeEnum, + Integer fromPeriod, Integer toPeriod, BigDecimal annualInterestRate, BigDecimal seniorCitizenRate) { + FdBaseInterestRateSlabHistory s = new FdBaseInterestRateSlabHistory(); + s.effectiveId = effectiveId; + s.srNo = srNo; + s.description = description; + s.periodTypeEnum = periodTypeEnum != null ? periodTypeEnum : 1; + s.fromPeriod = fromPeriod; + s.toPeriod = toPeriod; + s.annualInterestRate = annualInterestRate; + s.seniorCitizenRate = seniorCitizenRate; + s.createdDate = LocalDate.now(); + return s; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java index 8421e9ca0..688b6feca 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java @@ -1,894 +1,910 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.portfolio.savings.domain; - -import static org.apache.fineract.portfolio.savings.DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME; -import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodParamName; -import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermParamName; -import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.OneToOne; -import jakarta.persistence.Transient; -import java.math.BigDecimal; -import java.math.MathContext; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.ApiParameterError; -import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; -import org.apache.fineract.infrastructure.core.domain.ExternalId; -import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; -import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.organisation.monetary.domain.Money; -import org.apache.fineract.organisation.staff.domain.Staff; -import org.apache.fineract.portfolio.accountdetails.domain.AccountType; -import org.apache.fineract.portfolio.client.domain.Client; -import org.apache.fineract.portfolio.group.domain.Group; -import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart; -import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartAssembler; -import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType; -import org.apache.fineract.portfolio.savings.DepositAccountType; -import org.apache.fineract.portfolio.savings.DepositsApiConstants; -import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType; -import org.apache.fineract.portfolio.savings.SavingsApiConstants; -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.domain.interest.PostingPeriod; -import org.apache.fineract.portfolio.savings.domain.interest.SavingsAccountTransactionDetailsForPostingPeriod; -import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; -import org.apache.fineract.useradministration.domain.AppUser; - -@Entity -@DiscriminatorValue("200") -public class FixedDepositAccount extends SavingsAccount { - - @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) - private DepositAccountTermAndPreClosure accountTermAndPreClosure; - - @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "account") - protected DepositAccountInterestRateChart chart; - - @Transient - protected InterestRateChartAssembler chartAssembler; - @Transient - private ConfigurationDomainService configurationDomainService; - - protected FixedDepositAccount() { - // - } - - public static FixedDepositAccount createNewApplicationForSubmittal(final Client client, final Group group, final SavingsProduct product, - final Staff fieldOfficer, final String accountNo, final ExternalId externalId, final AccountType accountType, - final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate, - final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, - final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, - final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountInterestRateChart chart, - boolean withHoldTax) { - - final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; - final boolean allowOverdraft = false; - final BigDecimal overdraftLimit = new BigDecimal(0); - FixedDepositAccount account = new FixedDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, - accountType, submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, chart, - allowOverdraft, overdraftLimit, withHoldTax); - - return account; - } - - private FixedDepositAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, - final String accountNo, final ExternalId externalId, final SavingsAccountStatusType status, final AccountType accountType, - final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal nominalAnnualInterestRate, - final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, - final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, - final DepositAccountTermAndPreClosure accountTermAndPreClosure, DepositAccountInterestRateChart chart, - final boolean allowOverdraft, final BigDecimal overdraftLimit, boolean withHoldTax) { - - super(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy, - nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, - interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, withHoldTax); - - this.accountTermAndPreClosure = accountTermAndPreClosure; - this.chart = chart; - if (this.chart != null) { - this.chart.updateDepositAccountReference(this); - } - } - - @Override - public void modifyApplication(final JsonCommand command, final Map actualChanges) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.modifyApplicationAction); - super.modifyApplication(command, actualChanges, baseDataValidator); - final Map termAndPreClosureChanges = accountTermAndPreClosure.update(command, baseDataValidator); - actualChanges.putAll(termAndPreClosureChanges); - validateDomainRules(baseDataValidator); - super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - @Override - protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate) { - boolean isPreMatureClosure = false; - return getEffectiveInterestRateAsFraction(mc, interestPostingUpToDate, isPreMatureClosure); - } - - protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate, - final boolean isPreMatureClosure) { - - // default it to nominalAnnualInterst rate. interest chart overrrides - // this value. - BigDecimal applicableInterestRate = this.nominalAnnualInterestRate; - if (this.chart != null) { - boolean applyPreMaturePenalty = false; - BigDecimal penalInterest = BigDecimal.ZERO; - LocalDate depositCloseDate = calculateMaturityDate(); - if (isPreMatureClosure) { - if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) { - applyPreMaturePenalty = true; - penalInterest = this.accountTermAndPreClosure.depositPreClosureDetail().preClosurePenalInterest(); - final PreClosurePenalInterestOnType preClosurePenalInterestOnType = this.accountTermAndPreClosure - .depositPreClosureDetail().preClosurePenalInterestOnType(); - if (preClosurePenalInterestOnType.isWholeTerm()) { - depositCloseDate = interestCalculatedUpto(); - } else if (preClosurePenalInterestOnType.isTillPrematureWithdrawal()) { - depositCloseDate = interestPostingUpToDate; - } - } - } - - final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount(); - applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate, this.client); - - if (applyPreMaturePenalty) { - applicableInterestRate = applicableInterestRate.subtract(penalInterest); - applicableInterestRate = applicableInterestRate.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : applicableInterestRate; - } - } - this.nominalAnnualInterestRate = applicableInterestRate; - - return applicableInterestRate.divide(BigDecimal.valueOf(100L), mc); - } - - public void updateMaturityDateAndAmountBeforeAccountActivation(final MathContext mc, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { - List allTransactions = new ArrayList<>(); - String refNo = null; - final Money transactionAmountMoney = Money.of(getCurrency(), this.accountTermAndPreClosure.depositAmount()); - final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, - this.accountSubmittedOrActivationDate(), transactionAmountMoney, refNo); - transaction.setRunningBalance(transactionAmountMoney); - transaction.updateCumulativeBalanceAndDates(this.getCurrency(), interestCalculatedUpto()); - allTransactions.add(transaction); - updateMaturityDateAndAmount(mc, allTransactions, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - } - - public void updateMaturityDateAndAmount(final MathContext mc, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { - updateMaturityDateAndAmount(mc, retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - } - - public void updateMaturityDateAndAmount(final MathContext mc, final List transactions, - final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final LocalDate maturityDate = calculateMaturityDate(); - final LocalDate interestCalculationUpto = maturityDate.minusDays(1); - - // set end of day balance to maturity date for maturity interest - // calculation - this.resetAccountTransactionsEndOfDayBalances(transactions, maturityDate); - - final List postingPeriods = calculateInterestPayable(mc, interestCalculationUpto, transactions, isPreMatureClosure, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - - // reset end of day balance back to today's date - this.resetAccountTransactionsEndOfDayBalances(transactions, DateUtils.getBusinessLocalDate()); - - Money totalInterestPayable = Money.zero(getCurrency()); - for (PostingPeriod postingPeriod : postingPeriods) { - totalInterestPayable = totalInterestPayable.plus(postingPeriod.getInterestEarned()); - } - final Money depositAmount = Money.of(getCurrency(), this.accountTermAndPreClosure.depositAmount()); - final Money maturityAmount = depositAmount.plus(totalInterestPayable); - - this.accountTermAndPreClosure.updateMaturityDetails(maturityAmount.getAmount(), maturityDate); - } - - public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.updateMaturityDetailsAction); - - final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); - if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - if (!DateUtils.isDateInTheFuture(maturityDate())) { - // update account status - this.status = SavingsAccountStatusType.MATURED.getValue(); - postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - } - } - - public LocalDate calculateMaturityDate() { - - final LocalDate startDate = accountSubmittedOrActivationDate(); - LocalDate maturityDate = null; - final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod(); - switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) { - case DAYS: - maturityDate = startDate.plusDays(depositPeriod); - break; - case WEEKS: - maturityDate = startDate.plusWeeks(depositPeriod); - break; - case MONTHS: - maturityDate = startDate.plusMonths(depositPeriod); - break; - case YEARS: - maturityDate = startDate.plusYears(depositPeriod); - break; - case INVALID: - break; - } - - return maturityDate; - } - - private List calculateInterestPayable(final MathContext mc, final LocalDate maturityDate, - final List transactions, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { - - final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); - - final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType - .fromInt(this.interestCompoundingPeriodType); - - final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType - .fromInt(this.interestCalculationDaysInYearType); - List postedAsOnTransactionDates = getManualPostingDates(); - - final List postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods( - accountSubmittedOrActivationDate(), maturityDate, postingPeriodType, financialYearBeginningMonth, - postedAsOnTransactionDates); - - final List allPostingPeriods = new ArrayList<>(); - - Money periodStartingBalance = Money.zero(currency); - - final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); - final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, maturityDate, isPreMatureClosure); - final Collection interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(getId()); - boolean isInterestTransfer = false; - final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation()); - List savingsAccountTransactionDetailsForPostingPeriodList = toSavingsAccountTransactionDetailsForPostingPeriodList( - transactions); - for (final LocalDateInterval periodInterval : postingPeriodIntervals) { - boolean isUserPosting = false; - if (postedAsOnTransactionDates.contains(periodInterval.endDate())) { - isUserPosting = true; - } - final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, - savingsAccountTransactionDetailsForPostingPeriodList, this.currency, compoundingPeriodType, interestCalculationType, - interestRateAsFraction, daysInYearType.getValue(), maturityDate, interestPostTransactions, isInterestTransfer, - minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd, isUserPosting, - financialYearBeginningMonth); - - periodStartingBalance = postingPeriod.closingBalance(); - - allPostingPeriods.add(postingPeriod); - } - - this.summary.updateFromInterestPeriodSummaries(this.currency, allPostingPeriods); - this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, getLockedInUntilDate(), - isTransferInterestToOtherAccount()); - return allPostingPeriods; - } - - public void prematureClosure(final AppUser currentUser, final JsonCommand command, final Map actualChanges) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction); - - final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); - if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - final Locale locale = command.extractLocale(); - final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); - final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - - if (DateUtils.isBefore(closedDate, getActivationDate())) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.activation.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - if (isAccountLocked(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.lockin.period"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - if (maturityDate() != null && DateUtils.isAfter(closedDate, maturityDate())) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.before.maturity.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - if (DateUtils.isAfterBusinessDate(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("cannot.be.a.future.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - final List savingsAccountTransactions = retrieveListOfTransactions(); - if (savingsAccountTransactions.size() > 0) { - final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); - if (accountTransaction.isAfter(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.last.transaction.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - } - - validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); - this.status = SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue(); - - final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); - final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); - this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); - - /* - * // withdraw deposit amount before closing the account final Money transactionAmountMoney = - * Money.of(this.currency, this.getAccountBalance()); final SavingsAccountTransaction withdraw = - * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, closedDate, transactionAmountMoney, new - * Date()); this.transactions.add(withdraw); - */ - actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); - actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); - actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); - actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.format(fmt)); - - this.rejectedOnDate = null; - this.rejectedBy = null; - this.withdrawnOnDate = null; - this.withdrawnBy = null; - this.closedOnDate = closedDate; - this.closedBy = currentUser; - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - } - - @Override - public Money activateWithBalance() { - return Money.of(this.currency, this.accountTermAndPreClosure.depositAmount()); - } - - public void close(final AppUser currentUser, final JsonCommand command, final Map actualChanges) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction); - - final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); - if (!SavingsAccountStatusType.MATURED.hasStateOf(currentStatus)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.matured.state"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - final Locale locale = command.extractLocale(); - final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); - final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - - if (DateUtils.isBefore(closedDate, getActivationDate())) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.activation.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - if (DateUtils.isBefore(closedDate, maturityDate())) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.account.maturity.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - if (DateUtils.isAfterBusinessDate(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("cannot.be.a.future.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - final List savingsAccountTransactions = retrieveListOfTransactions(); - if (savingsAccountTransactions.size() > 0) { - final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); - if (accountTransaction.isAfter(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.last.transaction.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - } - - validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); - this.status = SavingsAccountStatusType.CLOSED.getValue(); - - final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); - final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); - this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); - - // // withdraw deposit amount before closing the account - // final Money transactionAmountMoney = Money.of(this.currency, - // this.getAccountBalance()); - // final SavingsAccountTransaction withdraw = - // SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, - // closedDate, - // transactionAmountMoney, new Date()); - // this.transactions.add(withdraw); - - actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); - actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); - actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); - actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.format(fmt)); - - this.rejectedOnDate = null; - this.rejectedBy = null; - this.withdrawnOnDate = null; - this.withdrawnBy = null; - this.closedOnDate = closedDate; - this.closedBy = currentUser; - // this.summary.updateSummary(this.currency, - // this.savingsAccountTransactionSummaryWrapper, this.transactions); - } - - public void updateClosedStatus() { - this.status = SavingsAccountStatusType.CLOSED.getValue(); - } - - public void updateOnAccountClosureStatus(DepositAccountOnClosureType onClosureType) { - this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); - } - - public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { - final LocalDate interestPostingUpToDate = maturityDate(); - final MathContext mc = MathContext.DECIMAL64; - final boolean isInterestTransfer = false; - final LocalDate postInterestOnDate = null; - final boolean backdatedTxnsAllowedTill = false; - boolean postReversals = false; - final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, - postReversals); - - Money interestPostedToDate = Money.zero(this.currency); - - boolean recalucateDailyBalanceDetails = false; - - for (final PostingPeriod interestPostingPeriod : postingPeriods) { - - LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction(); - - interestPostingTransactionDate = DateUtils.isAfter(interestPostingTransactionDate, interestPostingUpToDate) - ? interestPostingUpToDate - : interestPostingTransactionDate; - - final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned(); - - interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod); - - final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); - if (postingTransaction == null) { - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting()); - this.transactions.add(newPostingTransaction); - recalucateDailyBalanceDetails = true; - } else { - final boolean correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod); - if (correctionRequired) { - postingTransaction.reverse(); - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting()); - this.transactions.add(newPostingTransaction); - recalucateDailyBalanceDetails = true; - } - } - } - recalucateDailyBalanceDetails = applyWithholdTaxForDepositAccounts(interestPostingUpToDate, recalucateDailyBalanceDetails, - backdatedTxnsAllowedTill); - if (recalucateDailyBalanceDetails) { - // update existing transactions so derived balance fields are - // correct. - recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate, backdatedTxnsAllowedTill, postReversals); - } - - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - } - - public void postPreMaturityInterest(final LocalDate accountCloseDate, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { - - Money interestPostedToDate = totalInterestPosted(); - // calculate interest before one day of closure date - final LocalDate interestCalculatedToDate = accountCloseDate.minusDays(1); - final Money interestOnMaturity = calculatePreMatureInterest(interestCalculatedToDate, - retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - boolean recalucateDailyBalance = false; - - // post remaining interest - final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate); - final boolean backdatedTxnsAllowedTill = false; - if (!remainigInterestToBePosted.isZero()) { - final boolean postInterestAsOn = false; - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - accountCloseDate, remainigInterestToBePosted, postInterestAsOn); - this.transactions.add(newPostingTransaction); - recalucateDailyBalance = true; - } - - recalucateDailyBalance = applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance, backdatedTxnsAllowedTill); - boolean postReversals = false; - if (recalucateDailyBalance) { - // update existing transactions so derived balance fields are - // correct. - recalculateDailyBalances(Money.zero(this.currency), accountCloseDate, backdatedTxnsAllowedTill, postReversals); - } - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - this.accountTermAndPreClosure.updateMaturityDetails(this.getAccountBalance(), accountCloseDate); - - } - - public BigDecimal calculatePreMatureAmount(final LocalDate preMatureDate, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { - - final Money interestPostedToDate = totalInterestPosted().copy(); - - final Money interestEarnedTillDate = calculatePreMatureInterest(preMatureDate, retreiveOrderedNonInterestPostingTransactions(), - isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - - final Money accountBalance = Money.of(getCurrency(), getAccountBalance()); - final Money maturityAmount = accountBalance.minus(interestPostedToDate).plus(interestEarnedTillDate); - - return maturityAmount.getAmount(); - } - - private Money calculatePreMatureInterest(final LocalDate preMatureDate, final List transactions, - final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final MathContext mc = MathContext.DECIMAL64; - final List postingPeriods = calculateInterestPayable(mc, preMatureDate, transactions, isPreMatureClosure, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - - Money interestOnMaturity = Money.zero(this.currency); - - for (final PostingPeriod interestPostingPeriod : postingPeriods) { - final Money interestEarnedForPeriod = interestPostingPeriod.getInterestEarned(); - interestOnMaturity = interestOnMaturity.plus(interestEarnedForPeriod); - } - - return interestOnMaturity; - } - - public void postInterest(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill) { - final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); - boolean postReversals = false; - super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); - } - - @Override - public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final LocalDate postAsInterestOn, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { - final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); - return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postAsInterestOn, backdatedTxnsAllowedTill, postReversals); - } - - private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { - LocalDate interestPostingUpToDate = interestPostingDate; - final LocalDate uptoMaturityDate = interestCalculatedUpto(); - if (uptoMaturityDate != null && DateUtils.isBefore(uptoMaturityDate, interestPostingDate)) { - interestPostingUpToDate = uptoMaturityDate; - } - return interestPostingUpToDate; - } - - public LocalDate maturityDate() { - return this.accountTermAndPreClosure.getMaturityDate(); - } - - public BigDecimal maturityAmount() { - return this.accountTermAndPreClosure.maturityAmount(); - } - - private Money totalInterestPosted() { - Money interestPostedToDate = Money.zero(this.currency); - for (final SavingsAccountTransaction transaction : this.transactions) { - if (transaction.isInterestPostingAndNotReversed()) { - interestPostedToDate = interestPostedToDate.plus(transaction.getAmount(currency)); - } - } - - return interestPostedToDate; - } - - @Override - public Map activate(final AppUser currentUser, final JsonCommand command) { - final Map actualChanges = super.activate(currentUser, command); - - // if (isAccountLocked(calculateMaturityDate())) { - // final List dataValidationErrors = new - // ArrayList(); - // final DataValidatorBuilder baseDataValidator = new - // DataValidatorBuilder(dataValidationErrors) - // .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); - // baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("deposit.period.must.be.greater.than.lock.in.period", - // "Deposit period must be greater than account lock-in period."); - // if (!dataValidationErrors.isEmpty()) { throw new - // PlatformApiDataValidationException(dataValidationErrors); } - // } - return actualChanges; - } - - private LocalDate depositStartDate() { - // TODO: Support to add deposit start date which can be a date after - // account activation date. - final LocalDate depositStartDate = accountSubmittedOrActivationDate(); - return depositStartDate; - } - - private LocalDate interestCalculatedUpto() { - LocalDate uptoMaturityDate = calculateMaturityDate(); - if (uptoMaturityDate != null) { - // interest should not be calculated for maturity day - uptoMaturityDate = uptoMaturityDate.minusDays(1); - } - return uptoMaturityDate; - } - - public void validateDomainRules() { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); - validateDomainRules(baseDataValidator); - super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - private void validateDomainRules(final DataValidatorBuilder baseDataValidator) { - - final boolean isMinTermGreaterThanMax = this.accountTermAndPreClosure.depositTermDetail() - .isMinDepositTermGreaterThanMaxDepositTerm(); - final boolean isValidDepositPeriod = this.accountTermAndPreClosure.depositTermDetail().isDepositBetweenMinAndMax(depositStartDate(), - calculateMaturityDate()); - // deposit period should be within min and max deposit term - if (isMinTermGreaterThanMax) { - final Integer maxTerm = this.accountTermAndPreClosure.depositTermDetail().maxDepositTerm(); - baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm) - .failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term"); - } - final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod(); - final SavingsPeriodFrequencyType depositPeriodFrequencyType = this.accountTermAndPreClosure.depositPeriodFrequencyType(); - - if (!isValidDepositPeriod) { - baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod) - .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.between.min.and.max.deposit.term"); - } else { - final Integer inMultiplesOf = this.accountTermAndPreClosure.depositTermDetail().inMultiplesOfDepositTerm(); - if (inMultiplesOf != null) { - final boolean isValid = this.accountTermAndPreClosure.depositTermDetail().isValidInMultiplesOfPeriod(depositPeriod, - depositPeriodFrequencyType); - if (!isValid) { - baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod) - .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.multiple.of.term"); - } - } - } - - if (this.chart != null) { - final LocalDate chartFromDate = this.chart.getFromDate(); - LocalDate chartEndDate = this.chart.getEndDate(); - chartEndDate = chartEndDate == null ? DateUtils.getBusinessLocalDate() : chartEndDate; - - final LocalDateInterval chartInterval = LocalDateInterval.create(chartFromDate, chartEndDate); - if (!chartInterval.contains(accountSubmittedOrActivationDate())) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.valid.interest.rate.slab.available"); - } - - final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount(); - BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), - calculateMaturityDate(), this.client); - - if (applicableInterestRate.compareTo(BigDecimal.ZERO) == 0) { - baseDataValidator.reset() - .failWithCodeNoParameterAddedToErrorCode("no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period"); - } - - } else if (this.nominalAnnualInterestRate == null || this.nominalAnnualInterestRate.compareTo(BigDecimal.ZERO) == 0) { - baseDataValidator.reset().parameter(DepositsApiConstants.nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate) - .failWithCodeNoParameterAddedToErrorCode("valid.interest.chart.or.nominal.interest.rate.required"); - } - - } - - public boolean isReinvestOnClosure() { - return this.accountTermAndPreClosure.isReinvestOnClosure(); - } - - public boolean isTransferToSavingsOnClosure() { - return this.accountTermAndPreClosure.isTransferToSavingsOnClosure(); - } - - public Integer getOnAccountClosureId() { - return this.accountTermAndPreClosure.getOnAccountClosureType(); - } - - public Long getTransferToSavingsAccountId() { - return this.accountTermAndPreClosure.getTransferToSavingsAccountId(); - } - - public FixedDepositAccount reInvest(BigDecimal depositAmount, ExternalId externalId) { - - final DepositAccountTermAndPreClosure newAccountTermAndPreClosure = this.accountTermAndPreClosure.copy(depositAmount); - final SavingsProduct product = this.product; - final InterestRateChart productChart = product.applicableChart(getClosedOnDate()); - final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart.from(productChart); - - final AccountType accountType = AccountType.fromInt(this.accountType); - final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); - final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType - .fromInt(this.interestCompoundingPeriodType); - final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); - final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType - .fromInt(this.interestCalculationDaysInYearType); - final BigDecimal minRequiredOpeningBalance = null; - final BigDecimal interestRate = BigDecimal.ZERO; - final Set savingsAccountCharges = null; - final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType); - final Integer lockinPeriodFrequency = this.lockinPeriodFrequency; - final boolean withdrawalFeeApplicableForTransfer = false; - final String accountNumber = null; - final boolean withHoldTax = this.withHoldTax; - final FixedDepositAccount reInvestedAccount = FixedDepositAccount.createNewApplicationForSubmittal(client, group, product, - savingsOfficer, accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRate, compoundingPeriodType, - postingPeriodType, interestCalculationType, daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, newAccountTermAndPreClosure, newChart, - withHoldTax); - - newAccountTermAndPreClosure.updateAccountReference(reInvestedAccount); - newChart.updateDepositAccountReference(reInvestedAccount); - - return reInvestedAccount; - - } - - @Override - protected boolean isTransferInterestToOtherAccount() { - return this.accountTermAndPreClosure.isTransferInterestToLinkedAccount(); - } - - @Override - public boolean allowDeposit() { - return false; - } - - @Override - public boolean allowWithdrawal() { - return false; - } - - @Override - public boolean allowModify() { - return false; - } - - @Override - public boolean isTransactionsAllowed() { - return isActive() || isAccountMatured(); - } - - private boolean isAccountMatured() { - return SavingsAccountStatusType.fromInt(status).isMatured(); - } - - @Override - public BigDecimal minBalanceForInterestCalculation() { - return null; - } - - @Override - public void loadLazyCollections() { - super.loadLazyCollections(); - this.chart.getId(); - } - - public BigDecimal getDepositAmount() { - return this.accountTermAndPreClosure.depositAmount(); - } - - @Override - public BigDecimal getAccountBalance() { - return this.summary.getAccountBalance(this.currency).getAmount(); - } - - public boolean isMatured() { - return SavingsAccountStatusType.MATURED.getValue().equals(this.status); - } - - public void setClosedOnDate(final LocalDate closedOnDate) { - this.closedOnDate = closedOnDate; - } - - @Override - public DepositAccountType depositAccountType() { - return DepositAccountType.fromInt(200); - } -} +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.savings.domain; + +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Transient; +import java.math.BigDecimal; +import java.math.MathContext; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.staff.domain.Staff; +import org.apache.fineract.portfolio.accountdetails.domain.AccountType; +import org.apache.fineract.portfolio.client.domain.Client; +import org.apache.fineract.portfolio.group.domain.Group; +import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart; +import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartAssembler; +import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType; +import org.apache.fineract.portfolio.savings.DepositAccountType; +import org.apache.fineract.portfolio.savings.DepositsApiConstants; +import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType; +import org.apache.fineract.portfolio.savings.SavingsApiConstants; +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.domain.interest.PostingPeriod; +import org.apache.fineract.portfolio.savings.domain.interest.SavingsAccountTransactionDetailsForPostingPeriod; +import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; +import org.apache.fineract.useradministration.domain.AppUser; + +@Entity +@DiscriminatorValue("200") +public class FixedDepositAccount extends SavingsAccount { + + @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) + private DepositAccountTermAndPreClosure accountTermAndPreClosure; + + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "account") + protected DepositAccountInterestRateChart chart; + + @Transient + protected InterestRateChartAssembler chartAssembler; + @Transient + private ConfigurationDomainService configurationDomainService; + + protected FixedDepositAccount() { + // + } + + public static FixedDepositAccount createNewApplicationForSubmittal(final Client client, final Group group, final SavingsProduct product, + final Staff fieldOfficer, final String accountNo, final ExternalId externalId, final AccountType accountType, + final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, + final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, + final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountInterestRateChart chart, + boolean withHoldTax) { + + final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; + final boolean allowOverdraft = false; + final BigDecimal overdraftLimit = new BigDecimal(0); + FixedDepositAccount account = new FixedDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, + accountType, submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, chart, + allowOverdraft, overdraftLimit, withHoldTax); + + return account; + } + + private FixedDepositAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, + final String accountNo, final ExternalId externalId, final SavingsAccountStatusType status, final AccountType accountType, + final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal nominalAnnualInterestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, + final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, + final DepositAccountTermAndPreClosure accountTermAndPreClosure, DepositAccountInterestRateChart chart, + final boolean allowOverdraft, final BigDecimal overdraftLimit, boolean withHoldTax) { + + super(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy, + nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, + interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, + withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, withHoldTax); + + this.accountTermAndPreClosure = accountTermAndPreClosure; + this.chart = chart; + if (this.chart != null) { + this.chart.updateDepositAccountReference(this); + } + } + + @Override + public void modifyApplication(final JsonCommand command, final Map actualChanges) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.modifyApplicationAction); + super.modifyApplication(command, actualChanges, baseDataValidator); + final Map termAndPreClosureChanges = accountTermAndPreClosure.update(command, baseDataValidator); + actualChanges.putAll(termAndPreClosureChanges); + validateDomainRules(baseDataValidator); + super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + @Override + protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate) { + boolean isPreMatureClosure = false; + return getEffectiveInterestRateAsFraction(mc, interestPostingUpToDate, isPreMatureClosure); + } + + protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate, + final boolean isPreMatureClosure) { + + // default it to nominalAnnualInterst rate. interest chart overrrides + // this value. + BigDecimal applicableInterestRate = this.nominalAnnualInterestRate; + if (this.chart != null) { + boolean applyPreMaturePenalty = false; + BigDecimal penalInterest = BigDecimal.ZERO; + LocalDate depositCloseDate = calculateMaturityDate(); + if (isPreMatureClosure) { + if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) { + applyPreMaturePenalty = true; + penalInterest = this.accountTermAndPreClosure.depositPreClosureDetail().preClosurePenalInterest(); + final PreClosurePenalInterestOnType preClosurePenalInterestOnType = this.accountTermAndPreClosure + .depositPreClosureDetail().preClosurePenalInterestOnType(); + if (preClosurePenalInterestOnType.isWholeTerm()) { + depositCloseDate = interestCalculatedUpto(); + } else if (preClosurePenalInterestOnType.isTillPrematureWithdrawal()) { + depositCloseDate = interestPostingUpToDate; + } + } + } + + final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount(); + applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate, this.client); + + // CBS: override with senior citizen rate if applicable + if (isSeniorCitizen() && getSeniorCitizenInterestRate() != null + && getSeniorCitizenInterestRate().compareTo(BigDecimal.ZERO) > 0) { + applicableInterestRate = getSeniorCitizenInterestRate(); + } + + if (applyPreMaturePenalty) { + applicableInterestRate = applicableInterestRate.subtract(penalInterest); + applicableInterestRate = applicableInterestRate.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : applicableInterestRate; + } + } + this.nominalAnnualInterestRate = applicableInterestRate; + + return applicableInterestRate.divide(BigDecimal.valueOf(100L), mc); + } + + @Override + public BigDecimal getEffectiveInterestRateAsFractionAccrual(final MathContext mc, final LocalDate upToInterestCalculationDate) { + // CBS: accrual must also use senior citizen rate to stay consistent with maturity calculation + if (isSeniorCitizen() && getSeniorCitizenInterestRate() != null + && getSeniorCitizenInterestRate().compareTo(BigDecimal.ZERO) > 0) { + return getSeniorCitizenInterestRate().divide(BigDecimal.valueOf(100L), mc); + } + return super.getEffectiveInterestRateAsFractionAccrual(mc, upToInterestCalculationDate); + } + + public void updateMaturityDateAndAmountBeforeAccountActivation(final MathContext mc, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + List allTransactions = new ArrayList<>(); + String refNo = null; + final Money transactionAmountMoney = Money.of(getCurrency(), this.accountTermAndPreClosure.depositAmount()); + final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, + this.accountSubmittedOrActivationDate(), transactionAmountMoney, refNo); + transaction.setRunningBalance(transactionAmountMoney); + transaction.updateCumulativeBalanceAndDates(this.getCurrency(), interestCalculatedUpto()); + allTransactions.add(transaction); + updateMaturityDateAndAmount(mc, allTransactions, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + } + + public void updateMaturityDateAndAmount(final MathContext mc, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + updateMaturityDateAndAmount(mc, retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + } + + public void updateMaturityDateAndAmount(final MathContext mc, final List transactions, + final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + final LocalDate maturityDate = calculateMaturityDate(); + final LocalDate interestCalculationUpto = maturityDate.minusDays(1); + + // set end of day balance to maturity date for maturity interest + // calculation + this.resetAccountTransactionsEndOfDayBalances(transactions, maturityDate); + + final List postingPeriods = calculateInterestPayable(mc, interestCalculationUpto, transactions, isPreMatureClosure, + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + + // reset end of day balance back to today's date + this.resetAccountTransactionsEndOfDayBalances(transactions, DateUtils.getBusinessLocalDate()); + + Money totalInterestPayable = Money.zero(getCurrency()); + for (PostingPeriod postingPeriod : postingPeriods) { + totalInterestPayable = totalInterestPayable.plus(postingPeriod.getInterestEarned()); + } + final Money depositAmount = Money.of(getCurrency(), this.accountTermAndPreClosure.depositAmount()); + final Money maturityAmount = depositAmount.plus(totalInterestPayable); + + this.accountTermAndPreClosure.updateMaturityDetails(maturityAmount.getAmount(), maturityDate); + } + + public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.updateMaturityDetailsAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + if (!DateUtils.isDateInTheFuture(maturityDate())) { + // update account status + this.status = SavingsAccountStatusType.MATURED.getValue(); + postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + } + } + + public LocalDate calculateMaturityDate() { + + final LocalDate startDate = accountSubmittedOrActivationDate(); + LocalDate maturityDate = null; + final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod(); + switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) { + case DAYS: + maturityDate = startDate.plusDays(depositPeriod); + break; + case WEEKS: + maturityDate = startDate.plusWeeks(depositPeriod); + break; + case MONTHS: + maturityDate = startDate.plusMonths(depositPeriod); + break; + case YEARS: + maturityDate = startDate.plusYears(depositPeriod); + break; + case INVALID: + break; + } + + return maturityDate; + } + + private List calculateInterestPayable(final MathContext mc, final LocalDate maturityDate, + final List transactions, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + + final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); + + final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType + .fromInt(this.interestCompoundingPeriodType); + + final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType + .fromInt(this.interestCalculationDaysInYearType); + List postedAsOnTransactionDates = getManualPostingDates(); + + final List postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods( + accountSubmittedOrActivationDate(), maturityDate, postingPeriodType, financialYearBeginningMonth, + postedAsOnTransactionDates); + + final List allPostingPeriods = new ArrayList<>(); + + Money periodStartingBalance = Money.zero(currency); + + final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); + final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, maturityDate, isPreMatureClosure); + final Collection interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(getId()); + boolean isInterestTransfer = false; + final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation()); + List savingsAccountTransactionDetailsForPostingPeriodList = toSavingsAccountTransactionDetailsForPostingPeriodList( + transactions); + for (final LocalDateInterval periodInterval : postingPeriodIntervals) { + boolean isUserPosting = false; + if (postedAsOnTransactionDates.contains(periodInterval.endDate())) { + isUserPosting = true; + } + final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, + savingsAccountTransactionDetailsForPostingPeriodList, this.currency, compoundingPeriodType, interestCalculationType, + interestRateAsFraction, daysInYearType.getValue(), maturityDate, interestPostTransactions, isInterestTransfer, + minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd, isUserPosting, + financialYearBeginningMonth); + + periodStartingBalance = postingPeriod.closingBalance(); + + allPostingPeriods.add(postingPeriod); + } + + this.summary.updateFromInterestPeriodSummaries(this.currency, allPostingPeriods); + this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, getLockedInUntilDate(), + isTransferInterestToOtherAccount()); + return allPostingPeriods; + } + + public void prematureClosure(final AppUser currentUser, final JsonCommand command, final Map actualChanges) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); + final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); + + if (DateUtils.isBefore(closedDate, getActivationDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.activation.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + if (isAccountLocked(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.lockin.period"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + if (maturityDate() != null && DateUtils.isAfter(closedDate, maturityDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.before.maturity.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + if (DateUtils.isAfterBusinessDate(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("cannot.be.a.future.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + final List savingsAccountTransactions = retrieveListOfTransactions(); + if (savingsAccountTransactions.size() > 0) { + final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); + if (accountTransaction.isAfter(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.last.transaction.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + } + + validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); + this.status = SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue(); + + final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); + final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); + this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); + + /* + * // withdraw deposit amount before closing the account final Money transactionAmountMoney = + * Money.of(this.currency, this.getAccountBalance()); final SavingsAccountTransaction withdraw = + * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, closedDate, transactionAmountMoney, new + * Date()); this.transactions.add(withdraw); + */ + actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); + actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); + actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); + actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.format(fmt)); + + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = closedDate; + this.closedBy = currentUser; + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + } + + @Override + public Money activateWithBalance() { + return Money.of(this.currency, this.accountTermAndPreClosure.depositAmount()); + } + + public void close(final AppUser currentUser, final JsonCommand command, final Map actualChanges) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.MATURED.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.matured.state"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); + final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); + + if (DateUtils.isBefore(closedDate, getActivationDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.activation.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + if (DateUtils.isBefore(closedDate, maturityDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.account.maturity.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + if (DateUtils.isAfterBusinessDate(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("cannot.be.a.future.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + final List savingsAccountTransactions = retrieveListOfTransactions(); + if (savingsAccountTransactions.size() > 0) { + final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); + if (accountTransaction.isAfter(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.last.transaction.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + } + + validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); + this.status = SavingsAccountStatusType.CLOSED.getValue(); + + final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); + final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); + this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); + + // // withdraw deposit amount before closing the account + // final Money transactionAmountMoney = Money.of(this.currency, + // this.getAccountBalance()); + // final SavingsAccountTransaction withdraw = + // SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, + // closedDate, + // transactionAmountMoney, new Date()); + // this.transactions.add(withdraw); + + actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); + actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); + actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); + actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.format(fmt)); + + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = closedDate; + this.closedBy = currentUser; + // this.summary.updateSummary(this.currency, + // this.savingsAccountTransactionSummaryWrapper, this.transactions); + } + + public void updateClosedStatus() { + this.status = SavingsAccountStatusType.CLOSED.getValue(); + } + + public void updateOnAccountClosureStatus(DepositAccountOnClosureType onClosureType) { + this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); + } + + public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + final LocalDate interestPostingUpToDate = maturityDate(); + final MathContext mc = MathContext.DECIMAL64; + final boolean isInterestTransfer = false; + final LocalDate postInterestOnDate = null; + final boolean backdatedTxnsAllowedTill = false; + boolean postReversals = false; + final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, + postReversals); + + Money interestPostedToDate = Money.zero(this.currency); + + boolean recalucateDailyBalanceDetails = false; + + for (final PostingPeriod interestPostingPeriod : postingPeriods) { + + LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction(); + + interestPostingTransactionDate = DateUtils.isAfter(interestPostingTransactionDate, interestPostingUpToDate) + ? interestPostingUpToDate + : interestPostingTransactionDate; + + final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned(); + + interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod); + + final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); + if (postingTransaction == null) { + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), + interestPostingTransactionDate, interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting()); + this.transactions.add(newPostingTransaction); + recalucateDailyBalanceDetails = true; + } else { + final boolean correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod); + if (correctionRequired) { + postingTransaction.reverse(); + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), + interestPostingTransactionDate, interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting()); + this.transactions.add(newPostingTransaction); + recalucateDailyBalanceDetails = true; + } + } + } + recalucateDailyBalanceDetails = applyWithholdTaxForDepositAccounts(interestPostingUpToDate, recalucateDailyBalanceDetails, + backdatedTxnsAllowedTill); + if (recalucateDailyBalanceDetails) { + // update existing transactions so derived balance fields are + // correct. + recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate, backdatedTxnsAllowedTill, postReversals); + } + + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + } + + public void postPreMaturityInterest(final LocalDate accountCloseDate, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + + Money interestPostedToDate = totalInterestPosted(); + // calculate interest before one day of closure date + final LocalDate interestCalculatedToDate = accountCloseDate.minusDays(1); + final Money interestOnMaturity = calculatePreMatureInterest(interestCalculatedToDate, + retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + boolean recalucateDailyBalance = false; + + // post remaining interest + final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate); + final boolean backdatedTxnsAllowedTill = false; + if (!remainigInterestToBePosted.isZero()) { + final boolean postInterestAsOn = false; + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), + accountCloseDate, remainigInterestToBePosted, postInterestAsOn); + this.transactions.add(newPostingTransaction); + recalucateDailyBalance = true; + } + + recalucateDailyBalance = applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance, backdatedTxnsAllowedTill); + boolean postReversals = false; + if (recalucateDailyBalance) { + // update existing transactions so derived balance fields are + // correct. + recalculateDailyBalances(Money.zero(this.currency), accountCloseDate, backdatedTxnsAllowedTill, postReversals); + } + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + this.accountTermAndPreClosure.updateMaturityDetails(this.getAccountBalance(), accountCloseDate); + + } + + public BigDecimal calculatePreMatureAmount(final LocalDate preMatureDate, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + + final Money interestPostedToDate = totalInterestPosted().copy(); + + final Money interestEarnedTillDate = calculatePreMatureInterest(preMatureDate, retreiveOrderedNonInterestPostingTransactions(), + isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + + final Money accountBalance = Money.of(getCurrency(), getAccountBalance()); + final Money maturityAmount = accountBalance.minus(interestPostedToDate).plus(interestEarnedTillDate); + + return maturityAmount.getAmount(); + } + + private Money calculatePreMatureInterest(final LocalDate preMatureDate, final List transactions, + final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + final MathContext mc = MathContext.DECIMAL64; + final List postingPeriods = calculateInterestPayable(mc, preMatureDate, transactions, isPreMatureClosure, + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + + Money interestOnMaturity = Money.zero(this.currency); + + for (final PostingPeriod interestPostingPeriod : postingPeriods) { + final Money interestEarnedForPeriod = interestPostingPeriod.getInterestEarned(); + interestOnMaturity = interestOnMaturity.plus(interestEarnedForPeriod); + } + + return interestOnMaturity; + } + + public void postInterest(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, + final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill) { + final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); + boolean postReversals = false; + super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); + } + + @Override + public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, + final LocalDate postAsInterestOn, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { + final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); + return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postAsInterestOn, backdatedTxnsAllowedTill, postReversals); + } + + private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { + LocalDate interestPostingUpToDate = interestPostingDate; + final LocalDate uptoMaturityDate = interestCalculatedUpto(); + if (uptoMaturityDate != null && DateUtils.isBefore(uptoMaturityDate, interestPostingDate)) { + interestPostingUpToDate = uptoMaturityDate; + } + return interestPostingUpToDate; + } + + public LocalDate maturityDate() { + return this.accountTermAndPreClosure.getMaturityDate(); + } + + public BigDecimal maturityAmount() { + return this.accountTermAndPreClosure.maturityAmount(); + } + + private Money totalInterestPosted() { + Money interestPostedToDate = Money.zero(this.currency); + for (final SavingsAccountTransaction transaction : this.transactions) { + if (transaction.isInterestPostingAndNotReversed()) { + interestPostedToDate = interestPostedToDate.plus(transaction.getAmount(currency)); + } + } + + return interestPostedToDate; + } + + @Override + public Map activate(final AppUser currentUser, final JsonCommand command) { + final Map actualChanges = super.activate(currentUser, command); + + // if (isAccountLocked(calculateMaturityDate())) { + // final List dataValidationErrors = new + // ArrayList(); + // final DataValidatorBuilder baseDataValidator = new + // DataValidatorBuilder(dataValidationErrors) + // .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); + // baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("deposit.period.must.be.greater.than.lock.in.period", + // "Deposit period must be greater than account lock-in period."); + // if (!dataValidationErrors.isEmpty()) { throw new + // PlatformApiDataValidationException(dataValidationErrors); } + // } + return actualChanges; + } + + private LocalDate depositStartDate() { + // TODO: Support to add deposit start date which can be a date after + // account activation date. + final LocalDate depositStartDate = accountSubmittedOrActivationDate(); + return depositStartDate; + } + + private LocalDate interestCalculatedUpto() { + LocalDate uptoMaturityDate = calculateMaturityDate(); + if (uptoMaturityDate != null) { + // interest should not be calculated for maturity day + uptoMaturityDate = uptoMaturityDate.minusDays(1); + } + return uptoMaturityDate; + } + + public void validateDomainRules() { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME); + validateDomainRules(baseDataValidator); + super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + private void validateDomainRules(final DataValidatorBuilder baseDataValidator) { + + final boolean isMinTermGreaterThanMax = this.accountTermAndPreClosure.depositTermDetail() + .isMinDepositTermGreaterThanMaxDepositTerm(); + final boolean isValidDepositPeriod = this.accountTermAndPreClosure.depositTermDetail().isDepositBetweenMinAndMax(depositStartDate(), + calculateMaturityDate()); + // deposit period should be within min and max deposit term + if (isMinTermGreaterThanMax) { + final Integer maxTerm = this.accountTermAndPreClosure.depositTermDetail().maxDepositTerm(); + baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm) + .failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term"); + } + final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod(); + final SavingsPeriodFrequencyType depositPeriodFrequencyType = this.accountTermAndPreClosure.depositPeriodFrequencyType(); + + if (!isValidDepositPeriod) { + baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod) + .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.between.min.and.max.deposit.term"); + } else { + final Integer inMultiplesOf = this.accountTermAndPreClosure.depositTermDetail().inMultiplesOfDepositTerm(); + if (inMultiplesOf != null) { + final boolean isValid = this.accountTermAndPreClosure.depositTermDetail().isValidInMultiplesOfPeriod(depositPeriod, + depositPeriodFrequencyType); + if (!isValid) { + baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod) + .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.multiple.of.term"); + } + } + } + + if (this.chart != null) { + final LocalDate chartFromDate = this.chart.getFromDate(); + LocalDate chartEndDate = this.chart.getEndDate(); + chartEndDate = chartEndDate == null ? DateUtils.getBusinessLocalDate() : chartEndDate; + + final LocalDateInterval chartInterval = LocalDateInterval.create(chartFromDate, chartEndDate); + if (!chartInterval.contains(accountSubmittedOrActivationDate())) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.valid.interest.rate.slab.available"); + } + + final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount(); + BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), + calculateMaturityDate(), this.client); + + if (applicableInterestRate.compareTo(BigDecimal.ZERO) == 0) { + baseDataValidator.reset() + .failWithCodeNoParameterAddedToErrorCode("no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period"); + } + + } else if (this.nominalAnnualInterestRate == null || this.nominalAnnualInterestRate.compareTo(BigDecimal.ZERO) == 0) { + baseDataValidator.reset().parameter(DepositsApiConstants.nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate) + .failWithCodeNoParameterAddedToErrorCode("valid.interest.chart.or.nominal.interest.rate.required"); + } + + } + + public boolean isReinvestOnClosure() { + return this.accountTermAndPreClosure.isReinvestOnClosure(); + } + + public boolean isTransferToSavingsOnClosure() { + return this.accountTermAndPreClosure.isTransferToSavingsOnClosure(); + } + + public Integer getOnAccountClosureId() { + return this.accountTermAndPreClosure.getOnAccountClosureType(); + } + + public Long getTransferToSavingsAccountId() { + return this.accountTermAndPreClosure.getTransferToSavingsAccountId(); + } + + public FixedDepositAccount reInvest(BigDecimal depositAmount, ExternalId externalId) { + + final DepositAccountTermAndPreClosure newAccountTermAndPreClosure = this.accountTermAndPreClosure.copy(depositAmount); + final SavingsProduct product = this.product; + final InterestRateChart productChart = product.applicableChart(getClosedOnDate()); + final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart.from(productChart); + + final AccountType accountType = AccountType.fromInt(this.accountType); + final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); + final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType + .fromInt(this.interestCompoundingPeriodType); + final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); + final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType + .fromInt(this.interestCalculationDaysInYearType); + final BigDecimal minRequiredOpeningBalance = null; + final BigDecimal interestRate = BigDecimal.ZERO; + final Set savingsAccountCharges = null; + final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType); + final Integer lockinPeriodFrequency = this.lockinPeriodFrequency; + final boolean withdrawalFeeApplicableForTransfer = false; + final String accountNumber = null; + final boolean withHoldTax = this.withHoldTax; + final FixedDepositAccount reInvestedAccount = FixedDepositAccount.createNewApplicationForSubmittal(client, group, product, + savingsOfficer, accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRate, compoundingPeriodType, + postingPeriodType, interestCalculationType, daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, newAccountTermAndPreClosure, newChart, + withHoldTax); + + newAccountTermAndPreClosure.updateAccountReference(reInvestedAccount); + newChart.updateDepositAccountReference(reInvestedAccount); + + return reInvestedAccount; + + } + + @Override + protected boolean isTransferInterestToOtherAccount() { + return this.accountTermAndPreClosure.isTransferInterestToLinkedAccount(); + } + + @Override + public boolean allowDeposit() { + return false; + } + + @Override + public boolean allowWithdrawal() { + return false; + } + + @Override + public boolean allowModify() { + return false; + } + + @Override + public boolean isTransactionsAllowed() { + return isActive() || isAccountMatured(); + } + + private boolean isAccountMatured() { + return SavingsAccountStatusType.fromInt(status).isMatured(); + } + + @Override + public BigDecimal minBalanceForInterestCalculation() { + return null; + } + + @Override + public void loadLazyCollections() { + super.loadLazyCollections(); + this.chart.getId(); + } + + public BigDecimal getDepositAmount() { + return this.accountTermAndPreClosure.depositAmount(); + } + + @Override + public BigDecimal getAccountBalance() { + return this.summary.getAccountBalance(this.currency).getAmount(); + } + + public boolean isMatured() { + return SavingsAccountStatusType.MATURED.getValue().equals(this.status); + } + + public void setClosedOnDate(final LocalDate closedOnDate) { + this.closedOnDate = closedOnDate; + } + + @Override + public DepositAccountType depositAccountType() { + return DepositAccountType.fromInt(200); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java index 290b91fce..45f24fd81 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java @@ -1,1269 +1,1285 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.portfolio.savings.domain; - -import static org.apache.fineract.portfolio.savings.DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME; -import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodParamName; -import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermParamName; -import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.OneToMany; -import jakarta.persistence.OneToOne; -import jakarta.persistence.OrderBy; -import java.math.BigDecimal; -import java.math.MathContext; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.ApiParameterError; -import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; -import org.apache.fineract.infrastructure.core.domain.ExternalId; -import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; -import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.organisation.monetary.domain.Money; -import org.apache.fineract.organisation.staff.domain.Staff; -import org.apache.fineract.portfolio.accountdetails.domain.AccountType; -import org.apache.fineract.portfolio.calendar.domain.Calendar; -import org.apache.fineract.portfolio.calendar.service.CalendarUtils; -import org.apache.fineract.portfolio.client.domain.Client; -import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; -import org.apache.fineract.portfolio.group.domain.Group; -import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart; -import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType; -import org.apache.fineract.portfolio.savings.DepositAccountType; -import org.apache.fineract.portfolio.savings.DepositAccountUtils; -import org.apache.fineract.portfolio.savings.DepositsApiConstants; -import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType; -import org.apache.fineract.portfolio.savings.SavingsApiConstants; -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.SavingsAccountTransactionDTO; -import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod; -import org.apache.fineract.portfolio.savings.domain.interest.SavingsAccountTransactionDetailsForPostingPeriod; -import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; -import org.apache.fineract.useradministration.domain.AppUser; - -@Entity -@DiscriminatorValue("300") -public class RecurringDepositAccount extends SavingsAccount { - - @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) - private DepositAccountTermAndPreClosure accountTermAndPreClosure; - - @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) - private DepositAccountRecurringDetail recurringDetail; - - @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "account") - private DepositAccountInterestRateChart chart; - - @OrderBy(value = "installmentNumber, id") - @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval = true, fetch = FetchType.LAZY) - private List depositScheduleInstallments = new ArrayList<>(); - - protected RecurringDepositAccount() { - // - } - - public static RecurringDepositAccount createNewApplicationForSubmittal(final Client client, final Group group, - final SavingsProduct product, final Staff fieldOfficer, final String accountNo, final ExternalId externalId, - final AccountType accountType, final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate, - final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, - final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, - final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, - final DepositAccountInterestRateChart chart, final boolean withHoldTax) { - - final boolean allowOverdraft = false; - final BigDecimal overdraftLimit = new BigDecimal(0); - - final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; - return new RecurringDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, - submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, - recurringDetail, chart, allowOverdraft, overdraftLimit, withHoldTax); - } - - public static RecurringDepositAccount createNewActivatedAccount(final Client client, final Group group, final SavingsProduct product, - final Staff fieldOfficer, final String accountNo, final ExternalId externalId, final AccountType accountType, - final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate, - final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, - final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, - final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, - final DepositAccountInterestRateChart chart, final boolean withHoldTax) { - - final boolean allowOverdraft = false; - final BigDecimal overdraftLimit = new BigDecimal(0); - - final SavingsAccountStatusType status = SavingsAccountStatusType.ACTIVE; - return new RecurringDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, - submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, - recurringDetail, chart, allowOverdraft, overdraftLimit, withHoldTax); - } - - private RecurringDepositAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, - final String accountNo, final ExternalId externalId, final SavingsAccountStatusType status, final AccountType accountType, - final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal nominalAnnualInterestRate, - final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, - final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, - final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, - final DepositAccountInterestRateChart chart, final boolean allowOverdraft, final BigDecimal overdraftLimit, - final boolean withHoldTax) { - - super(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy, - nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, - interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, withHoldTax); - - this.accountTermAndPreClosure = accountTermAndPreClosure; - this.recurringDetail = recurringDetail; - this.chart = chart; - if (this.chart != null) { - this.chart.updateDepositAccountReference(this); - } - } - - @Override - public void modifyApplication(final JsonCommand command, final Map actualChanges) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.modifyApplicationAction); - super.modifyApplication(command, actualChanges, baseDataValidator); - final Map termAndPreClosureChanges = accountTermAndPreClosure.update(command, baseDataValidator); - actualChanges.putAll(termAndPreClosureChanges); - recurringDetail.update(command); - validateDomainRules(baseDataValidator); - super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - private void updateDepositAmount() { - BigDecimal recurringAmount = getRecurringDetail().mandatoryRecommendedDepositAmount(); - Integer numberOfDepositPeriods = depositScheduleInstallments().size(); - if (this.accountTermAndPreClosure.depositPeriod() != null && recurringAmount != null && numberOfDepositPeriods != null) { - BigDecimal depositAmount = Money.of(product.currency(), recurringAmount).multipliedBy(numberOfDepositPeriods) - .plus(this.minRequiredOpeningBalance).getAmount(); - accountTermAndPreClosure.updateDepositAmount(depositAmount); - } else if (accountTermAndPreClosure.depositAmount() == null) { - accountTermAndPreClosure.updateDepositAmount(Money.zero(product.currency()).getAmount()); - } - } - - public void updateDepositAmount(final BigDecimal depositAmount) { - if (this.accountTermAndPreClosure.depositPeriod() == null) { - accountTermAndPreClosure.updateDepositAmount(accountTermAndPreClosure.depositAmount().add(depositAmount)); - } - } - - @Override - protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate) { - boolean isPreMatureClosure = false; - return getEffectiveInterestRateAsFraction(mc, interestPostingUpToDate, isPreMatureClosure); - } - - protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate, - final boolean isPreMatureClosure) { - - boolean applyPreMaturePenalty = false; - BigDecimal penalInterest = BigDecimal.ZERO; - LocalDate depositCloseDate = calculateMaturityDate(); - if (isPreMatureClosure) { - if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) { - applyPreMaturePenalty = true; - penalInterest = this.accountTermAndPreClosure.depositPreClosureDetail().preClosurePenalInterest(); - final PreClosurePenalInterestOnType preClosurePenalInterestOnType = this.accountTermAndPreClosure.depositPreClosureDetail() - .preClosurePenalInterestOnType(); - if (preClosurePenalInterestOnType.isWholeTerm()) { - depositCloseDate = interestCalculatedUpto(); - } else if (preClosurePenalInterestOnType.isTillPrematureWithdrawal()) { - depositCloseDate = interestPostingUpToDate; - } - } - } - - if (depositCloseDate == null) { - depositCloseDate = DateUtils.getBusinessLocalDate(); - } - - final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount(); - BigDecimal applicableInterestRate = BigDecimal.ZERO; - if (this.chart != null) { - applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate, this.client); - - if (applyPreMaturePenalty) { - applicableInterestRate = applicableInterestRate.subtract(penalInterest); - applicableInterestRate = applicableInterestRate.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : applicableInterestRate; - } - } - - this.nominalAnnualInterestRate = applicableInterestRate; - - return applicableInterestRate.divide(BigDecimal.valueOf(100L), mc); - } - - public void updateMaturityDateAndAmount(final MathContext mc, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { - final LocalDate maturityDate = calculateMaturityDate(); - LocalDate interestCalculationUpto = null; - List allTransactions = null; - if (maturityDate == null) { - interestCalculationUpto = DateUtils.getBusinessLocalDate(); - allTransactions = getTransactions(interestCalculationUpto, false); - } else { - interestCalculationUpto = maturityDate.minusDays(1); - allTransactions = getTransactions(interestCalculationUpto, true); - - } - - final List postingPeriods = calculateInterestPayable(mc, interestCalculationUpto, allTransactions, - isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - Money totalInterestPayable = Money.zero(getCurrency()); - Money totalDepositAmount = Money.zero(getCurrency()); - for (PostingPeriod postingPeriod : postingPeriods) { - totalInterestPayable = totalInterestPayable.plus(postingPeriod.getInterestEarned()); - totalDepositAmount = totalDepositAmount.plus(postingPeriod.closingBalance()).minus(postingPeriod.openingBalance()); - } - if (maturityDate == null) { - this.accountTermAndPreClosure.updateDepositAmount(totalDepositAmount.getAmount()); - } else { - this.accountTermAndPreClosure.updateMaturityDetails(totalDepositAmount.getAmount(), totalInterestPayable.getAmount(), - maturityDate); - } - } - - public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final boolean postReversals) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.updateMaturityDetailsAction); - final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); - if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - final LocalDate todayDate = DateUtils.getBusinessLocalDate(); - if (!DateUtils.isAfter(this.maturityDate(), todayDate)) { - // update account status - this.status = SavingsAccountStatusType.MATURED.getValue(); - postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, todayDate, postReversals); - } - } - - public LocalDate calculateMaturityDate() { - - final LocalDate startDate = depositStartDate(); - LocalDate maturityDate = null; - final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod(); - if (depositPeriod == null) { - return maturityDate; - } - switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) { - case DAYS: - maturityDate = startDate.plusDays(depositPeriod); - break; - case WEEKS: - maturityDate = startDate.plusWeeks(depositPeriod); - break; - case MONTHS: - maturityDate = startDate.plusMonths(depositPeriod); - break; - case YEARS: - maturityDate = startDate.plusYears(depositPeriod); - break; - case INVALID: - break; - } - - return maturityDate; - } - - private List calculateInterestPayable(final MathContext mc, final LocalDate maturityDate, - final List transactions, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { - - // 1. default to calculate interest based on entire history OR - // 2. determine latest 'posting period' and find interest credited to - // that period - - // A generate list of EndOfDayBalances (not including interest postings) - final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); - - final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType - .fromInt(this.interestCompoundingPeriodType); - - final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType - .fromInt(this.interestCalculationDaysInYearType); - List PostedAsOnDates = getManualPostingDates(); - final List postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(depositStartDate(), - maturityDate, postingPeriodType, financialYearBeginningMonth, PostedAsOnDates); - - final List allPostingPeriods = new ArrayList<>(); - - Money periodStartingBalance = Money.zero(currency); - - final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); - final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, maturityDate, isPreMatureClosure); - final Collection interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(getId()); - boolean isInterestTransfer = false; - final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation()); - List savingsAccountTransactionDetailsForPostingPeriodList = toSavingsAccountTransactionDetailsForPostingPeriodList( - transactions); - for (final LocalDateInterval periodInterval : postingPeriodIntervals) { - boolean isUserPosting = false; - if (PostedAsOnDates.contains(periodInterval.endDate())) { - isUserPosting = true; - } - final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, - savingsAccountTransactionDetailsForPostingPeriodList, this.currency, compoundingPeriodType, interestCalculationType, - interestRateAsFraction, daysInYearType.getValue(), maturityDate, interestPostTransactions, isInterestTransfer, - minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd, isUserPosting, - financialYearBeginningMonth); - - periodStartingBalance = postingPeriod.closingBalance(); - - allPostingPeriods.add(postingPeriod); - } - - this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, this.getLockedInUntilDate(), - isTransferInterestToOtherAccount()); - // this.summary.updateFromInterestPeriodSummaries(this.currency, - // allPostingPeriods); - return allPostingPeriods; - } - - private List getTransactions(final LocalDate depositEndDate, final boolean generateFutureTransactions) { - List allTransactions = new ArrayList<>(); - // add existing transactions - allTransactions.addAll(retreiveOrderedNonInterestPostingTransactions()); - LocalDate latestTransactionDate = null; - for (final SavingsAccountTransaction installment : allTransactions) { - if (latestTransactionDate == null || DateUtils.isBefore(latestTransactionDate, installment.getTransactionDate())) { - latestTransactionDate = installment.getTransactionDate(); - } - } - String refNo = null; - if (generateFutureTransactions) { - for (RecurringDepositScheduleInstallment installment : depositScheduleInstallments()) { - if (installment.isPrincipalNotCompleted(getCurrency())) { - LocalDate dueDate = installment.dueDate(); - if (DateUtils.isBefore(dueDate, latestTransactionDate)) { - dueDate = latestTransactionDate; - } - final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, dueDate, - installment.getDepositAmountOutstanding(getCurrency()), refNo); - allTransactions.add(transaction); - } - } - } - - allTransactions = sortTransactions(allTransactions); - Money runningBalance = Money.zero(getCurrency()); - for (final SavingsAccountTransaction transaction : allTransactions) { - if (transaction.isReversed()) { - transaction.zeroBalanceFields(); - } else { - Money transactionAmount = Money.zero(this.currency); - if (transaction.isCredit()) { - transactionAmount = transactionAmount.plus(transaction.getAmount(this.currency)); - } else if (transaction.isDebit()) { - transactionAmount = transactionAmount.minus(transaction.getAmount(this.currency)); - } - - runningBalance = runningBalance.plus(transactionAmount); - transaction.setRunningBalance(runningBalance); - } - } - // loop over transactions in reverse - LocalDate endOfBalanceDate = depositEndDate; - for (int i = allTransactions.size() - 1; i >= 0; i--) { - final SavingsAccountTransaction transaction = allTransactions.get(i); - if (transaction.isNotReversed() && !transaction.isInterestPostingAndNotReversed()) { - transaction.updateCumulativeBalanceAndDates(this.currency, endOfBalanceDate); - endOfBalanceDate = transaction.getTransactionDate().minusDays(1); - } - } - return allTransactions; - } - - public LocalDate depositStartDate() { - final LocalDate depositStartDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate(); - if (depositStartDate == null) { - return accountSubmittedOrActivationDate(); - } - return depositStartDate; - } - - public void prematureClosure(final AppUser currentUser, final JsonCommand command, final Map actualChanges) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction); - - final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); - if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - final Locale locale = command.extractLocale(); - final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); - final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - - if (DateUtils.isBefore(closedDate, getActivationDate())) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.activation.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - if (isAccountLocked(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.lockin.period"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - if (maturityDate() != null && DateUtils.isAfter(closedDate, maturityDate())) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.before.maturity.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - if (DateUtils.isAfterBusinessDate(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("cannot.be.a.future.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - if (isAccountLocked(calculateMaturityDate())) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("deposit.period.must.be.greater.than.lock.in.period", - "Deposit period must be greater than account lock-in period."); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - final List savingsAccountTransactions = retrieveListOfTransactions(); - if (savingsAccountTransactions.size() > 0) { - final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); - if (accountTransaction.isAfter(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.last.transaction.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - } - - validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); - this.status = SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue(); - - final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); - final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); - this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); - - /* - * // withdraw deposit amount before closing the account final Money transactionAmountMoney = - * Money.of(this.currency, this.getAccountBalance()); final SavingsAccountTransaction withdraw = - * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, closedDate, transactionAmountMoney, new - * Date()); this.transactions.add(withdraw); - */ - actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); - actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); - actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); - actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.format(fmt)); - - this.rejectedOnDate = null; - this.rejectedBy = null; - this.withdrawnOnDate = null; - this.withdrawnBy = null; - this.closedOnDate = closedDate; - this.closedBy = currentUser; - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - - } - - @Override - public Money activateWithBalance() { - return Money.of(this.currency, this.minRequiredOpeningBalance); - } - - protected void processAccountUponActivation(final DateTimeFormatter fmt, final boolean postReversals, - final Long relaxingDaysConfigForPivotDate) { - final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); - final boolean backdatedTxnsAllowedTill = false; - String refNo = null; - if (minRequiredOpeningBalance.isGreaterThanZero()) { - final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationDate(), - minRequiredOpeningBalance.getAmount(), null, null, accountType); - deposit(transactionDTO, backdatedTxnsAllowedTill, relaxingDaysConfigForPivotDate, refNo); - - // update existing transactions so derived balance fields are - // correct. - recalculateDailyBalances(Money.zero(this.currency), DateUtils.getBusinessLocalDate(), backdatedTxnsAllowedTill, postReversals); - } - } - - public void close(final AppUser currentUser, final JsonCommand command, final Map actualChanges) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction); - - final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); - if (!SavingsAccountStatusType.MATURED.hasStateOf(currentStatus) && this.maturityDate() != null) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.matured.state"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - final Locale locale = command.extractLocale(); - final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); - final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - - if (DateUtils.isBefore(closedDate, getActivationDate())) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.activation.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - if (maturityDate() != null && DateUtils.isBefore(closedDate, maturityDate())) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.account.maturity.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - if (DateUtils.isAfterBusinessDate(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("cannot.be.a.future.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - final List savingsAccountTransactions = retrieveListOfTransactions(); - if (savingsAccountTransactions.size() > 0) { - final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); - if (accountTransaction.isAfter(closedDate)) { - baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) - .failWithCode("must.be.after.last.transaction.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - } - - validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); - this.status = SavingsAccountStatusType.CLOSED.getValue(); - - final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); - final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); - this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); - - /* - * // withdraw deposit amount before closing the account final Money transactionAmountMoney = - * Money.of(this.currency, this.getAccountBalance()); final SavingsAccountTransaction withdraw = - * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, closedDate, transactionAmountMoney, new - * Date()); this.transactions.add(withdraw); - */ - - actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); - actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); - actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); - actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.format(fmt)); - - this.rejectedOnDate = null; - this.rejectedBy = null; - this.withdrawnOnDate = null; - this.withdrawnBy = null; - this.closedOnDate = closedDate; - this.closedBy = currentUser; - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - } - - public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final LocalDate closeDate, final boolean postReversals) { - LocalDate interestPostingUpToDate = maturityDate(); - if (interestPostingUpToDate == null) { - interestPostingUpToDate = closeDate; - } - this.setClosedOnDate(closeDate); - final MathContext mc = MathContext.DECIMAL64; - boolean isInterestTransfer = false; - LocalDate postInterestOnDate = null; - final boolean backdatedTxnsAllowedTill = false; - final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate.minusDays(1), isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, - postReversals); - - Money interestPostedToDate = Money.zero(this.currency); - - boolean recalucateDailyBalanceDetails = false; - - for (final PostingPeriod interestPostingPeriod : postingPeriods) { - - LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction(); - interestPostingTransactionDate = DateUtils.isAfter(interestPostingTransactionDate, interestPostingUpToDate) - ? interestPostingUpToDate - : interestPostingTransactionDate; - final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned(); - - interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod); - - final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); - if (postingTransaction == null) { - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting()); - addTransaction(newPostingTransaction); - recalucateDailyBalanceDetails = true; - } else { - final boolean correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod); - if (correctionRequired) { - postingTransaction.reverse(); - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting()); - addTransaction(newPostingTransaction); - recalucateDailyBalanceDetails = true; - } - } - } - applyWithholdTaxForDepositAccounts(interestPostingUpToDate, recalucateDailyBalanceDetails, backdatedTxnsAllowedTill); - if (recalucateDailyBalanceDetails) { - // update existing transactions so derived balance fields are - // correct. - recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate, backdatedTxnsAllowedTill, postReversals); - } - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - } - - public void postPreMaturityInterest(final LocalDate accountCloseDate, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, boolean postReversals) { - - final Money interestPostedToDate = totalInterestPosted(); - // calculate interest before one day of closure date - final LocalDate interestCalculatedToDate = accountCloseDate.minusDays(1); - final Money interestOnMaturity = calculatePreMatureInterest(interestCalculatedToDate, - retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - - boolean recalucateDailyBalance = false; - final boolean backdatedTxnsAllowedTill = false; - - // post remaining interest - final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate); - if (!remainigInterestToBePosted.isZero()) { - final boolean postInterestAsOn = false; - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - accountCloseDate, remainigInterestToBePosted, postInterestAsOn); - addTransaction(newPostingTransaction); - recalucateDailyBalance = true; - } - - applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance, backdatedTxnsAllowedTill); - if (recalucateDailyBalance) { - // update existing transactions so derived balance fields are - // correct. - recalculateDailyBalances(Money.zero(this.currency), accountCloseDate, backdatedTxnsAllowedTill, postReversals); - } - - this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); - this.accountTermAndPreClosure.updateMaturityDetails(this.getAccountBalance(), accountCloseDate); - } - - public BigDecimal calculatePreMatureAmount(final LocalDate preMatureDate, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { - - final Money interestPostedToDate = totalInterestPosted().copy(); - - final Money interestEarnedTillDate = calculatePreMatureInterest(preMatureDate, retreiveOrderedNonInterestPostingTransactions(), - isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - - final Money accountBalance = Money.of(getCurrency(), getAccountBalance()); - final Money maturityAmount = accountBalance.minus(interestPostedToDate).plus(interestEarnedTillDate); - - return maturityAmount.getAmount(); - } - - private Money calculatePreMatureInterest(final LocalDate preMatureDate, final List transactions, - final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final MathContext mc = MathContext.DECIMAL64; - final List postingPeriods = calculateInterestPayable(mc, preMatureDate, transactions, isPreMatureClosure, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - - Money interestOnMaturity = Money.zero(this.currency); - - for (final PostingPeriod interestPostingPeriod : postingPeriods) { - final Money interestEarnedForPeriod = interestPostingPeriod.getInterestEarned(); - interestOnMaturity = interestOnMaturity.plus(interestEarnedForPeriod); - } - this.summary.updateFromInterestPeriodSummaries(this.currency, postingPeriods); - return interestOnMaturity; - } - - @Override - public void postInterest(final MathContext mc, final LocalDate postingDate, final boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final LocalDate postInterestAson, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { - final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); - super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestAson, backdatedTxnsAllowedTill, postReversals); - } - - @Override - public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final LocalDate postAsInterestOn, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { - final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); - return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postAsInterestOn, backdatedTxnsAllowedTill, postReversals); - } - - private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { - LocalDate interestPostingUpToDate = interestPostingDate; - final LocalDate uptoMaturityDate = interestCalculatedUpto(); - if (uptoMaturityDate != null && DateUtils.isBefore(uptoMaturityDate, interestPostingDate)) { - interestPostingUpToDate = uptoMaturityDate; - } - return interestPostingUpToDate; - } - - public LocalDate maturityDate() { - return this.accountTermAndPreClosure.getMaturityDate(); - } - - public BigDecimal maturityAmount() { - return this.accountTermAndPreClosure.maturityAmount(); - } - - private LocalDate interestCalculatedUpto() { - LocalDate uptoMaturityDate = calculateMaturityDate(); - if (uptoMaturityDate != null) { - // interest should not be calculated for maturity day - uptoMaturityDate = uptoMaturityDate.minusDays(1); - } - return uptoMaturityDate; - } - - private Money totalInterestPosted() { - Money interestPostedToDate = Money.zero(this.currency); - List trans = getTransactions(); - for (final SavingsAccountTransaction transaction : trans) { - if (transaction.isInterestPostingAndNotReversed()) { - interestPostedToDate = interestPostedToDate.plus(transaction.getAmount(currency)); - } - } - - return interestPostedToDate; - } - - @Override - public Map activate(final AppUser currentUser, final JsonCommand command) { - final Map actualChanges = super.activate(currentUser, command); - - if (accountTermAndPreClosure.isAfterExpectedFirstDepositDate(getActivationDate())) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(command.extractLocale()); - final String dateAsString = formatter.format(this.accountTermAndPreClosure.getExpectedFirstDepositOnDate()); - baseDataValidator.reset().parameter(DepositsApiConstants.activatedOnDateParamName).value(dateAsString) - .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.expected.first.deposit.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - return actualChanges; - } - - protected List sortTransactions(final List transactions) { - final List listOfTransactionsSorted = new ArrayList<>(); - listOfTransactionsSorted.addAll(transactions); - - final SavingsAccountTransactionComparator transactionComparator = new SavingsAccountTransactionComparator(); - Collections.sort(listOfTransactionsSorted, transactionComparator); - return listOfTransactionsSorted; - } - - @Override - public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO, final boolean backdatedTxnsAllowedTill, - final Long relaxingDaysConfigForPivotDate, final String refNo) { - - if (isAccountMatured()) { - final String defaultUserMessage = "Transaction is not allowed. Account is matured."; - final ApiParameterError error = ApiParameterError.parameterError( - "error.msg.recurring.deposit.account.transaction.account.is.matured", defaultUserMessage, "transactionDate", - transactionDTO.getTransactionDate().format(transactionDTO.getFormatter())); - - final List dataValidationErrors = new ArrayList<>(); - dataValidationErrors.add(error); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - if (!isBeforeMaturityDate(transactionDTO.getTransactionDate())) { - final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account maturity date."; - final ApiParameterError error = ApiParameterError.parameterError( - "error.msg.recurring.deposit.account.transaction.date.is.after.account.maturity.date", defaultUserMessage, - "transactionDate", transactionDTO.getTransactionDate().format(transactionDTO.getFormatter())); - - final List dataValidationErrors = new ArrayList<>(); - dataValidationErrors.add(error); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - if (isBeforeDepositStartDate(transactionDTO.getTransactionDate())) { - final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account activation and deposit start date."; - final ApiParameterError error = ApiParameterError.parameterError( - "error.msg.recurring.deposit.account.transaction.date.is.before.account.activation.or.deposit.date", defaultUserMessage, - "transactionDate", transactionDTO.getTransactionDate().format(transactionDTO.getFormatter())); - - final List dataValidationErrors = new ArrayList<>(); - dataValidationErrors.add(error); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - final SavingsAccountTransaction transaction = super.deposit(transactionDTO, backdatedTxnsAllowedTill, - relaxingDaysConfigForPivotDate, refNo); - - return transaction; - } - - public void handleScheduleInstallments(final SavingsAccountTransaction transaction) { - final LocalDate transactionDate = transaction.getTransactionDate(); - Money transactionAmountUnprocessed = transaction.getAmount(getCurrency()); - - for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { - if (currentInstallment.isNotFullyPaidOff() && transactionAmountUnprocessed.isGreaterThanZero()) { - if (!this.adjustAdvanceTowardsFuturePayments() && DateUtils.isBefore(transactionDate, currentInstallment.dueDate())) { - transactionAmountUnprocessed = Money.zero(getCurrency()); - } - transactionAmountUnprocessed = handleInstallmentTransaction(currentInstallment, transactionAmountUnprocessed, - transactionDate); - } - } - - } - - public void updateScheduleInstallments() { - // reset all installments to process from the beginning - for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { - currentInstallment.resetDerivedFields(); - } - - final List orderedDepositTransactions = retreiveOrderedDepositTransactions(); - for (SavingsAccountTransaction transaction : orderedDepositTransactions) { - handleScheduleInstallments(transaction); - } - } - - public void updateScheduleInstallmentsWithNewRecommendedDepositAmount(BigDecimal newDepositAmount, - LocalDate depositAmountupdatedFromDate) { - // reset all installments to process from the beginning, also update - // deposit amount as necessary - for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { - if (!DateUtils.isAfter(depositAmountupdatedFromDate, currentInstallment.dueDate())) { - currentInstallment.updateDepositAmountAndResetDerivedFields(newDepositAmount); - } else { - currentInstallment.resetDerivedFields(); - } - } - - final List orderedDepositTransactions = retreiveOrderedDepositTransactions(); - for (SavingsAccountTransaction transaction : orderedDepositTransactions) { - handleScheduleInstallments(transaction); - } - } - - private List retreiveOrderedDepositTransactions() { - final List listOfTransactionsSorted = retrieveListOfTransactions(); - - final List orderedDepositTransactions = new ArrayList<>(); - - for (final SavingsAccountTransaction transaction : listOfTransactionsSorted) { - if (transaction.isDepositAndNotReversed()) { - orderedDepositTransactions.add(transaction); - } - } - - return orderedDepositTransactions; - } - - /** - * This method is responsible for checking if the current transaction is 'an advance/early payment' based on the - * details passed through. - * - * Default implementation is check transaction date is before installment due date. - */ - protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex, - final List installments, final LocalDate transactionDate) { - final RecurringDepositScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex); - return DateUtils.isBefore(transactionDate, currentInstallment.dueDate()); - } - - private Money handleInstallmentTransaction(final RecurringDepositScheduleInstallment currentInstallment, - final Money transactionAmountUnprocessed, final LocalDate transactionDate) { - Money transactionAmountRemaining = transactionAmountUnprocessed; - Money depositAmountPortion = currentInstallment.payInstallment(transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining.minus(depositAmountPortion); - - return transactionAmountRemaining; - - } - - private boolean isAccountMatured() { - return SavingsAccountStatusType.fromInt(status).isMatured(); - } - - private boolean isBeforeMaturityDate(final LocalDate compareDate) { - final LocalDate maturityDate = this.maturityDate(); - return maturityDate == null || DateUtils.isBefore(compareDate, maturityDate); - } - - private boolean isBeforeDepositStartDate(LocalDate compareDate) { - return DateUtils.isBefore(compareDate, depositStartDate()); - } - - public void validateDomainRules() { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - validateDomainRules(baseDataValidator); - super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - private void validateDomainRules(final DataValidatorBuilder baseDataValidator) { - final boolean isMinTermGreaterThanMax = this.accountTermAndPreClosure.depositTermDetail() - .isMinDepositTermGreaterThanMaxDepositTerm(); - // deposit period should be within min and max deposit term - if (isMinTermGreaterThanMax) { - final Integer maxTerm = this.accountTermAndPreClosure.depositTermDetail().maxDepositTerm(); - baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm) - .failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term"); - } - - final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod(); - if (this.accountTermAndPreClosure.depositTermDetail().maxDepositTerm() != null) { - baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull(); - } - if (depositPeriod != null) { - final SavingsPeriodFrequencyType depositPeriodFrequencyType = this.accountTermAndPreClosure.depositPeriodFrequencyType(); - final boolean isValidDepositPeriod = this.accountTermAndPreClosure.depositTermDetail() - .isDepositBetweenMinAndMax(depositStartDate(), calculateMaturityDate()); - if (!isValidDepositPeriod) { - baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod) - .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.between.min.and.max.deposit.term"); - } else { - final Integer inMultiplesOf = this.accountTermAndPreClosure.depositTermDetail().inMultiplesOfDepositTerm(); - if (inMultiplesOf != null) { - final boolean isValid = this.accountTermAndPreClosure.depositTermDetail().isValidInMultiplesOfPeriod(depositPeriod, - depositPeriodFrequencyType); - if (!isValid) { - baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod) - .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.multiple.of.term"); - } - } - } - if (isAccountLocked(calculateMaturityDate())) { - baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).failWithCode( - "deposit.period.must.be.greater.than.lock.in.period", - "Deposit period must be greater than account lock-in period."); - } - - } - if (firstDepositDateBeforeAccountSubmittedOrActivationDate()) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( - "expected.first.deposit.date.must.be.after.account.submitted.or.activation.date"); - } - - // FIXME: Handle this scenario - /* - * //final boolean recurringFrequencyBeforeDepositPeriod = recurringFrequencyBeforeDepositPeriod(); - * - * if (!recurringFrequencyBeforeDepositPeriod) { - * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( - * "recurring.frequency.not.before.deposit.period"); } - */ - } - - public void validateApplicableInterestRate() { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - LocalDate maturityDate = calculateMaturityDate(); - if (this.chart != null) { - final LocalDate chartFromDate = this.chart.getFromDate(); - LocalDate chartEndDate = this.chart.getEndDate(); - chartEndDate = chartEndDate == null ? DateUtils.getBusinessLocalDate() : chartEndDate; - - final LocalDateInterval chartInterval = LocalDateInterval.create(chartFromDate, chartEndDate); - if (!chartInterval.contains(accountSubmittedOrActivationDate())) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.valid.interest.rate.slab.available.for.date.range"); - } - - if (maturityDate == null) { - maturityDate = DateUtils.getBusinessLocalDate(); - } - - final BigDecimal maturityAmount = this.accountTermAndPreClosure.depositAmount(); - BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(maturityAmount, depositStartDate(), maturityDate, - this.client); - - if (applicableInterestRate.compareTo(BigDecimal.ZERO) == 0) { - baseDataValidator.reset() - .failWithCodeNoParameterAddedToErrorCode("no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period"); - } - - } - // CBS: chart is optional — CBS base interest rate chart is used instead - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - /* - * final boolean recurringFrequencyBeforeDepositPeriod = recurringFrequencyBeforeDepositPeriod(); - * - * if (!recurringFrequencyBeforeDepositPeriod) { - * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( - * "recurring.frequency.not.before.deposit.period"); } - */ - } - - public boolean isReinvestOnClosure() { - return this.accountTermAndPreClosure.isReinvestOnClosure(); - } - - public boolean isTransferToSavingsOnClosure() { - return this.accountTermAndPreClosure.isTransferToSavingsOnClosure(); - } - - public RecurringDepositAccount reInvest(BigDecimal depositAmount) { - - final DepositAccountTermAndPreClosure newAccountTermAndPreClosure = this.accountTermAndPreClosure.copy(depositAmount); - final DepositAccountRecurringDetail recurringDetail = this.recurringDetail.copy(); - final SavingsProduct product = this.product; - final InterestRateChart productChart = product.applicableChart(getClosedOnDate()); - final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart.from(productChart); - final String accountNumber = null; - final ExternalId externalId = this.externalId; - final AccountType accountType = AccountType.fromInt(this.accountType); - final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); - final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType - .fromInt(this.interestCompoundingPeriodType); - final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); - final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType - .fromInt(this.interestCalculationDaysInYearType); - final BigDecimal minRequiredOpeningBalance = depositAmount; - final BigDecimal interestRate = BigDecimal.ZERO; - final Set savingsAccountCharges = null; - final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType); - final Integer lockinPeriodFrequency = this.lockinPeriodFrequency; - final boolean withdrawalFeeApplicableForTransfer = false; - - LocalDate now = getClosedOnDate(); - newAccountTermAndPreClosure.updateExpectedFirstDepositDate(now); - - RecurringDepositAccount rdAccount = RecurringDepositAccount.createNewActivatedAccount(client, group, product, savingsOfficer, - accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRate, compoundingPeriodType, postingPeriodType, - interestCalculationType, daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeApplicableForTransfer, savingsAccountCharges, newAccountTermAndPreClosure, recurringDetail, newChart, - withHoldTax); - - rdAccount.setDatesFrom(now); - newAccountTermAndPreClosure.updateAccountReference(rdAccount); - recurringDetail.updateAccountReference(rdAccount); - - return rdAccount; - - } - - private boolean firstDepositDateBeforeAccountSubmittedOrActivationDate() { - final LocalDate expectedFirstDepositLocalDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate(); - return expectedFirstDepositLocalDate != null - && DateUtils.isBefore(expectedFirstDepositLocalDate, accountSubmittedOrActivationDate()); - } - - public void setDatesFrom(final LocalDate now) { - this.rejectedOnDate = null; - this.rejectedBy = null; - this.withdrawnOnDate = null; - this.withdrawnBy = null; - this.closedOnDate = null; - this.closedBy = null; - - this.activatedBy = null; - this.lockedInUntilDate = null; - - this.activatedOnDate = now; - } - - public void setClosedOnDate(final LocalDate closedOnDate) { - this.closedOnDate = closedOnDate; - } - - @Override - protected boolean isTransferInterestToOtherAccount() { - return this.accountTermAndPreClosure.isTransferInterestToLinkedAccount(); - } - - public void generateSchedule(final PeriodFrequencyType frequency, final Integer recurringEvery, final Calendar calendar) { - this.depositScheduleInstallments.clear(); - LocalDate installmentDate = null; - if (this.isCalendarInherited()) { - installmentDate = CalendarUtils.getNextScheduleDate(calendar, accountSubmittedOrActivationDate()); - } else { - installmentDate = depositStartDate(); - } - - int installmentNumber = 1; - final LocalDate maturityDate = calcualteScheduleTillDate(frequency, recurringEvery); - final BigDecimal depositAmount = this.recurringDetail.mandatoryRecommendedDepositAmount(); - while (DateUtils.isBefore(installmentDate, maturityDate)) { - final RecurringDepositScheduleInstallment installment = RecurringDepositScheduleInstallment.installment(this, installmentNumber, - installmentDate, depositAmount); - addDepositScheduleInstallment(installment); - installmentDate = DepositAccountUtils.calculateNextDepositDate(installmentDate, frequency, recurringEvery); - installmentNumber += 1; - } - updateDepositAmount(); - } - - private LocalDate calcualteScheduleTillDate(final PeriodFrequencyType frequency, final Integer recurringEvery) { - LocalDate tillDate = calculateMaturityDate(); - if (tillDate == null) { - final LocalDate today = DateUtils.getBusinessLocalDate(); - tillDate = DepositAccountUtils.calculateNextDepositDate(today, frequency, - recurringEvery * (DepositAccountUtils.GENERATE_MINIMUM_NUMBER_OF_FUTURE_INSTALMENTS + 1)); - } - return tillDate; - } - - private List depositScheduleInstallments() { - return this.depositScheduleInstallments; - } - - private void addDepositScheduleInstallment(final RecurringDepositScheduleInstallment installment) { - this.depositScheduleInstallments.add(installment); - } - - public boolean isCalendarInherited() { - return this.recurringDetail.isCalendarInherited(); - } - - public void updateOverduePayments(final LocalDate todayDate) { - LocalDate overdueUptoDate = this.maturityDate(); - if (overdueUptoDate == null || DateUtils.isAfter(overdueUptoDate, todayDate)) { - overdueUptoDate = todayDate; - } - - final List installments = depositScheduleInstallments(); - int noOfOverdueInstallments = 0; - Money totalOverdueAmount = Money.zero(getCurrency()); - for (RecurringDepositScheduleInstallment installment : installments) { - if (installment.isNotFullyPaidOff() && DateUtils.isAfter(overdueUptoDate, installment.dueDate())) { - noOfOverdueInstallments++; - totalOverdueAmount = totalOverdueAmount.plus(installment.getDepositAmountOutstanding(getCurrency())); - } - } - this.recurringDetail.updateOverdueDetails(noOfOverdueInstallments, totalOverdueAmount); - } - - @Override - public boolean allowWithdrawal() { - return this.recurringDetail.allowWithdrawal(); - } - - public boolean adjustAdvanceTowardsFuturePayments() { - return this.recurringDetail.adjustAdvanceTowardsFuturePayments(); - } - - @Override - public boolean isTransactionsAllowed() { - return isActive() || isAccountMatured(); - } - - public DepositAccountRecurringDetail getRecurringDetail() { - return this.recurringDetail; - } - - static class RecurringDepositScheduleInstallmentComparator implements Comparator { - - @Override - public int compare(final RecurringDepositScheduleInstallment o1, final RecurringDepositScheduleInstallment o2) { - return o1.installmentNumber().compareTo(o2.installmentNumber()); - } - } - - @Override - public void loadLazyCollections() { - this.depositScheduleInstallments.size(); - super.loadLazyCollections(); - this.chart.getId(); - } - - public BigDecimal getDepositAmount() { - return this.accountTermAndPreClosure.depositAmount(); - } - - @Override - public DepositAccountType depositAccountType() { - return DepositAccountType.fromInt(300); - } -} +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.savings.domain; + +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.OrderBy; +import java.math.BigDecimal; +import java.math.MathContext; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.staff.domain.Staff; +import org.apache.fineract.portfolio.accountdetails.domain.AccountType; +import org.apache.fineract.portfolio.calendar.domain.Calendar; +import org.apache.fineract.portfolio.calendar.service.CalendarUtils; +import org.apache.fineract.portfolio.client.domain.Client; +import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; +import org.apache.fineract.portfolio.group.domain.Group; +import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart; +import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType; +import org.apache.fineract.portfolio.savings.DepositAccountType; +import org.apache.fineract.portfolio.savings.DepositAccountUtils; +import org.apache.fineract.portfolio.savings.DepositsApiConstants; +import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType; +import org.apache.fineract.portfolio.savings.SavingsApiConstants; +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.SavingsAccountTransactionDTO; +import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod; +import org.apache.fineract.portfolio.savings.domain.interest.SavingsAccountTransactionDetailsForPostingPeriod; +import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; +import org.apache.fineract.useradministration.domain.AppUser; + +@Entity +@DiscriminatorValue("300") +public class RecurringDepositAccount extends SavingsAccount { + + @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) + private DepositAccountTermAndPreClosure accountTermAndPreClosure; + + @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) + private DepositAccountRecurringDetail recurringDetail; + + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "account") + private DepositAccountInterestRateChart chart; + + @OrderBy(value = "installmentNumber, id") + @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval = true, fetch = FetchType.LAZY) + private List depositScheduleInstallments = new ArrayList<>(); + + protected RecurringDepositAccount() { + // + } + + public static RecurringDepositAccount createNewApplicationForSubmittal(final Client client, final Group group, + final SavingsProduct product, final Staff fieldOfficer, final String accountNo, final ExternalId externalId, + final AccountType accountType, final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, + final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, + final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, + final DepositAccountInterestRateChart chart, final boolean withHoldTax) { + + final boolean allowOverdraft = false; + final BigDecimal overdraftLimit = new BigDecimal(0); + + final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; + return new RecurringDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, + submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, + recurringDetail, chart, allowOverdraft, overdraftLimit, withHoldTax); + } + + public static RecurringDepositAccount createNewActivatedAccount(final Client client, final Group group, final SavingsProduct product, + final Staff fieldOfficer, final String accountNo, final ExternalId externalId, final AccountType accountType, + final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, + final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, + final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, + final DepositAccountInterestRateChart chart, final boolean withHoldTax) { + + final boolean allowOverdraft = false; + final BigDecimal overdraftLimit = new BigDecimal(0); + + final SavingsAccountStatusType status = SavingsAccountStatusType.ACTIVE; + return new RecurringDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, + submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, + recurringDetail, chart, allowOverdraft, overdraftLimit, withHoldTax); + } + + private RecurringDepositAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, + final String accountNo, final ExternalId externalId, final SavingsAccountStatusType status, final AccountType accountType, + final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal nominalAnnualInterestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, + final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, + final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, + final DepositAccountInterestRateChart chart, final boolean allowOverdraft, final BigDecimal overdraftLimit, + final boolean withHoldTax) { + + super(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy, + nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, + interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, + withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, withHoldTax); + + this.accountTermAndPreClosure = accountTermAndPreClosure; + this.recurringDetail = recurringDetail; + this.chart = chart; + if (this.chart != null) { + this.chart.updateDepositAccountReference(this); + } + } + + @Override + public void modifyApplication(final JsonCommand command, final Map actualChanges) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.modifyApplicationAction); + super.modifyApplication(command, actualChanges, baseDataValidator); + final Map termAndPreClosureChanges = accountTermAndPreClosure.update(command, baseDataValidator); + actualChanges.putAll(termAndPreClosureChanges); + recurringDetail.update(command); + validateDomainRules(baseDataValidator); + super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + private void updateDepositAmount() { + BigDecimal recurringAmount = getRecurringDetail().mandatoryRecommendedDepositAmount(); + Integer numberOfDepositPeriods = depositScheduleInstallments().size(); + if (this.accountTermAndPreClosure.depositPeriod() != null && recurringAmount != null && numberOfDepositPeriods != null) { + BigDecimal depositAmount = Money.of(product.currency(), recurringAmount).multipliedBy(numberOfDepositPeriods) + .plus(this.minRequiredOpeningBalance).getAmount(); + accountTermAndPreClosure.updateDepositAmount(depositAmount); + } else if (accountTermAndPreClosure.depositAmount() == null) { + accountTermAndPreClosure.updateDepositAmount(Money.zero(product.currency()).getAmount()); + } + } + + public void updateDepositAmount(final BigDecimal depositAmount) { + if (this.accountTermAndPreClosure.depositPeriod() == null) { + accountTermAndPreClosure.updateDepositAmount(accountTermAndPreClosure.depositAmount().add(depositAmount)); + } + } + + @Override + protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate) { + boolean isPreMatureClosure = false; + return getEffectiveInterestRateAsFraction(mc, interestPostingUpToDate, isPreMatureClosure); + } + + protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate, + final boolean isPreMatureClosure) { + + boolean applyPreMaturePenalty = false; + BigDecimal penalInterest = BigDecimal.ZERO; + LocalDate depositCloseDate = calculateMaturityDate(); + if (isPreMatureClosure) { + if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) { + applyPreMaturePenalty = true; + penalInterest = this.accountTermAndPreClosure.depositPreClosureDetail().preClosurePenalInterest(); + final PreClosurePenalInterestOnType preClosurePenalInterestOnType = this.accountTermAndPreClosure.depositPreClosureDetail() + .preClosurePenalInterestOnType(); + if (preClosurePenalInterestOnType.isWholeTerm()) { + depositCloseDate = interestCalculatedUpto(); + } else if (preClosurePenalInterestOnType.isTillPrematureWithdrawal()) { + depositCloseDate = interestPostingUpToDate; + } + } + } + + if (depositCloseDate == null) { + depositCloseDate = DateUtils.getBusinessLocalDate(); + } + + final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount(); + BigDecimal applicableInterestRate = BigDecimal.ZERO; + if (this.chart != null) { + applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate, this.client); + + // CBS: override with senior citizen rate if applicable + if (isSeniorCitizen() && getSeniorCitizenInterestRate() != null + && getSeniorCitizenInterestRate().compareTo(BigDecimal.ZERO) > 0) { + applicableInterestRate = getSeniorCitizenInterestRate(); + } + + if (applyPreMaturePenalty) { + applicableInterestRate = applicableInterestRate.subtract(penalInterest); + applicableInterestRate = applicableInterestRate.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : applicableInterestRate; + } + } + + this.nominalAnnualInterestRate = applicableInterestRate; + + return applicableInterestRate.divide(BigDecimal.valueOf(100L), mc); + } + + @Override + public BigDecimal getEffectiveInterestRateAsFractionAccrual(final MathContext mc, final LocalDate upToInterestCalculationDate) { + // CBS: accrual must also use senior citizen rate to stay consistent with maturity calculation + if (isSeniorCitizen() && getSeniorCitizenInterestRate() != null + && getSeniorCitizenInterestRate().compareTo(BigDecimal.ZERO) > 0) { + return getSeniorCitizenInterestRate().divide(BigDecimal.valueOf(100L), mc); + } + return super.getEffectiveInterestRateAsFractionAccrual(mc, upToInterestCalculationDate); + } + + public void updateMaturityDateAndAmount(final MathContext mc, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + final LocalDate maturityDate = calculateMaturityDate(); + LocalDate interestCalculationUpto = null; + List allTransactions = null; + if (maturityDate == null) { + interestCalculationUpto = DateUtils.getBusinessLocalDate(); + allTransactions = getTransactions(interestCalculationUpto, false); + } else { + interestCalculationUpto = maturityDate.minusDays(1); + allTransactions = getTransactions(interestCalculationUpto, true); + + } + + final List postingPeriods = calculateInterestPayable(mc, interestCalculationUpto, allTransactions, + isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + Money totalInterestPayable = Money.zero(getCurrency()); + Money totalDepositAmount = Money.zero(getCurrency()); + for (PostingPeriod postingPeriod : postingPeriods) { + totalInterestPayable = totalInterestPayable.plus(postingPeriod.getInterestEarned()); + totalDepositAmount = totalDepositAmount.plus(postingPeriod.closingBalance()).minus(postingPeriod.openingBalance()); + } + if (maturityDate == null) { + this.accountTermAndPreClosure.updateDepositAmount(totalDepositAmount.getAmount()); + } else { + this.accountTermAndPreClosure.updateMaturityDetails(totalDepositAmount.getAmount(), totalInterestPayable.getAmount(), + maturityDate); + } + } + + public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, + final boolean postReversals) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.updateMaturityDetailsAction); + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + final LocalDate todayDate = DateUtils.getBusinessLocalDate(); + if (!DateUtils.isAfter(this.maturityDate(), todayDate)) { + // update account status + this.status = SavingsAccountStatusType.MATURED.getValue(); + postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, todayDate, postReversals); + } + } + + public LocalDate calculateMaturityDate() { + + final LocalDate startDate = depositStartDate(); + LocalDate maturityDate = null; + final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod(); + if (depositPeriod == null) { + return maturityDate; + } + switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) { + case DAYS: + maturityDate = startDate.plusDays(depositPeriod); + break; + case WEEKS: + maturityDate = startDate.plusWeeks(depositPeriod); + break; + case MONTHS: + maturityDate = startDate.plusMonths(depositPeriod); + break; + case YEARS: + maturityDate = startDate.plusYears(depositPeriod); + break; + case INVALID: + break; + } + + return maturityDate; + } + + private List calculateInterestPayable(final MathContext mc, final LocalDate maturityDate, + final List transactions, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + + // 1. default to calculate interest based on entire history OR + // 2. determine latest 'posting period' and find interest credited to + // that period + + // A generate list of EndOfDayBalances (not including interest postings) + final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); + + final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType + .fromInt(this.interestCompoundingPeriodType); + + final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType + .fromInt(this.interestCalculationDaysInYearType); + List PostedAsOnDates = getManualPostingDates(); + final List postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(depositStartDate(), + maturityDate, postingPeriodType, financialYearBeginningMonth, PostedAsOnDates); + + final List allPostingPeriods = new ArrayList<>(); + + Money periodStartingBalance = Money.zero(currency); + + final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); + final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, maturityDate, isPreMatureClosure); + final Collection interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(getId()); + boolean isInterestTransfer = false; + final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation()); + List savingsAccountTransactionDetailsForPostingPeriodList = toSavingsAccountTransactionDetailsForPostingPeriodList( + transactions); + for (final LocalDateInterval periodInterval : postingPeriodIntervals) { + boolean isUserPosting = false; + if (PostedAsOnDates.contains(periodInterval.endDate())) { + isUserPosting = true; + } + final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, + savingsAccountTransactionDetailsForPostingPeriodList, this.currency, compoundingPeriodType, interestCalculationType, + interestRateAsFraction, daysInYearType.getValue(), maturityDate, interestPostTransactions, isInterestTransfer, + minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd, isUserPosting, + financialYearBeginningMonth); + + periodStartingBalance = postingPeriod.closingBalance(); + + allPostingPeriods.add(postingPeriod); + } + + this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, this.getLockedInUntilDate(), + isTransferInterestToOtherAccount()); + // this.summary.updateFromInterestPeriodSummaries(this.currency, + // allPostingPeriods); + return allPostingPeriods; + } + + private List getTransactions(final LocalDate depositEndDate, final boolean generateFutureTransactions) { + List allTransactions = new ArrayList<>(); + // add existing transactions + allTransactions.addAll(retreiveOrderedNonInterestPostingTransactions()); + LocalDate latestTransactionDate = null; + for (final SavingsAccountTransaction installment : allTransactions) { + if (latestTransactionDate == null || DateUtils.isBefore(latestTransactionDate, installment.getTransactionDate())) { + latestTransactionDate = installment.getTransactionDate(); + } + } + String refNo = null; + if (generateFutureTransactions) { + for (RecurringDepositScheduleInstallment installment : depositScheduleInstallments()) { + if (installment.isPrincipalNotCompleted(getCurrency())) { + LocalDate dueDate = installment.dueDate(); + if (DateUtils.isBefore(dueDate, latestTransactionDate)) { + dueDate = latestTransactionDate; + } + final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, dueDate, + installment.getDepositAmountOutstanding(getCurrency()), refNo); + allTransactions.add(transaction); + } + } + } + + allTransactions = sortTransactions(allTransactions); + Money runningBalance = Money.zero(getCurrency()); + for (final SavingsAccountTransaction transaction : allTransactions) { + if (transaction.isReversed()) { + transaction.zeroBalanceFields(); + } else { + Money transactionAmount = Money.zero(this.currency); + if (transaction.isCredit()) { + transactionAmount = transactionAmount.plus(transaction.getAmount(this.currency)); + } else if (transaction.isDebit()) { + transactionAmount = transactionAmount.minus(transaction.getAmount(this.currency)); + } + + runningBalance = runningBalance.plus(transactionAmount); + transaction.setRunningBalance(runningBalance); + } + } + // loop over transactions in reverse + LocalDate endOfBalanceDate = depositEndDate; + for (int i = allTransactions.size() - 1; i >= 0; i--) { + final SavingsAccountTransaction transaction = allTransactions.get(i); + if (transaction.isNotReversed() && !transaction.isInterestPostingAndNotReversed()) { + transaction.updateCumulativeBalanceAndDates(this.currency, endOfBalanceDate); + endOfBalanceDate = transaction.getTransactionDate().minusDays(1); + } + } + return allTransactions; + } + + public LocalDate depositStartDate() { + final LocalDate depositStartDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate(); + if (depositStartDate == null) { + return accountSubmittedOrActivationDate(); + } + return depositStartDate; + } + + public void prematureClosure(final AppUser currentUser, final JsonCommand command, final Map actualChanges) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); + final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); + + if (DateUtils.isBefore(closedDate, getActivationDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.activation.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + if (isAccountLocked(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.lockin.period"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + if (maturityDate() != null && DateUtils.isAfter(closedDate, maturityDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.before.maturity.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + if (DateUtils.isAfterBusinessDate(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("cannot.be.a.future.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + if (isAccountLocked(calculateMaturityDate())) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("deposit.period.must.be.greater.than.lock.in.period", + "Deposit period must be greater than account lock-in period."); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + final List savingsAccountTransactions = retrieveListOfTransactions(); + if (savingsAccountTransactions.size() > 0) { + final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); + if (accountTransaction.isAfter(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.last.transaction.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + } + + validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); + this.status = SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue(); + + final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); + final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); + this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); + + /* + * // withdraw deposit amount before closing the account final Money transactionAmountMoney = + * Money.of(this.currency, this.getAccountBalance()); final SavingsAccountTransaction withdraw = + * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, closedDate, transactionAmountMoney, new + * Date()); this.transactions.add(withdraw); + */ + actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); + actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); + actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); + actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.format(fmt)); + + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = closedDate; + this.closedBy = currentUser; + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + + } + + @Override + public Money activateWithBalance() { + return Money.of(this.currency, this.minRequiredOpeningBalance); + } + + protected void processAccountUponActivation(final DateTimeFormatter fmt, final boolean postReversals, + final Long relaxingDaysConfigForPivotDate) { + final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); + final boolean backdatedTxnsAllowedTill = false; + String refNo = null; + if (minRequiredOpeningBalance.isGreaterThanZero()) { + final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationDate(), + minRequiredOpeningBalance.getAmount(), null, null, accountType); + deposit(transactionDTO, backdatedTxnsAllowedTill, relaxingDaysConfigForPivotDate, refNo); + + // update existing transactions so derived balance fields are + // correct. + recalculateDailyBalances(Money.zero(this.currency), DateUtils.getBusinessLocalDate(), backdatedTxnsAllowedTill, postReversals); + } + } + + public void close(final AppUser currentUser, final JsonCommand command, final Map actualChanges) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.MATURED.hasStateOf(currentStatus) && this.maturityDate() != null) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.matured.state"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); + final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); + + if (DateUtils.isBefore(closedDate, getActivationDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.activation.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + if (maturityDate() != null && DateUtils.isBefore(closedDate, maturityDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.account.maturity.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + if (DateUtils.isAfterBusinessDate(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("cannot.be.a.future.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + final List savingsAccountTransactions = retrieveListOfTransactions(); + if (savingsAccountTransactions.size() > 0) { + final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); + if (accountTransaction.isAfter(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.last.transaction.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + } + + validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); + this.status = SavingsAccountStatusType.CLOSED.getValue(); + + final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); + final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); + this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); + + /* + * // withdraw deposit amount before closing the account final Money transactionAmountMoney = + * Money.of(this.currency, this.getAccountBalance()); final SavingsAccountTransaction withdraw = + * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, closedDate, transactionAmountMoney, new + * Date()); this.transactions.add(withdraw); + */ + + actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); + actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); + actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); + actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.format(fmt)); + + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = closedDate; + this.closedBy = currentUser; + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + } + + public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, + final LocalDate closeDate, final boolean postReversals) { + LocalDate interestPostingUpToDate = maturityDate(); + if (interestPostingUpToDate == null) { + interestPostingUpToDate = closeDate; + } + this.setClosedOnDate(closeDate); + final MathContext mc = MathContext.DECIMAL64; + boolean isInterestTransfer = false; + LocalDate postInterestOnDate = null; + final boolean backdatedTxnsAllowedTill = false; + final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate.minusDays(1), isInterestTransfer, + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, + postReversals); + + Money interestPostedToDate = Money.zero(this.currency); + + boolean recalucateDailyBalanceDetails = false; + + for (final PostingPeriod interestPostingPeriod : postingPeriods) { + + LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction(); + interestPostingTransactionDate = DateUtils.isAfter(interestPostingTransactionDate, interestPostingUpToDate) + ? interestPostingUpToDate + : interestPostingTransactionDate; + final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned(); + + interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod); + + final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); + if (postingTransaction == null) { + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), + interestPostingTransactionDate, interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting()); + addTransaction(newPostingTransaction); + recalucateDailyBalanceDetails = true; + } else { + final boolean correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod); + if (correctionRequired) { + postingTransaction.reverse(); + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), + interestPostingTransactionDate, interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting()); + addTransaction(newPostingTransaction); + recalucateDailyBalanceDetails = true; + } + } + } + applyWithholdTaxForDepositAccounts(interestPostingUpToDate, recalucateDailyBalanceDetails, backdatedTxnsAllowedTill); + if (recalucateDailyBalanceDetails) { + // update existing transactions so derived balance fields are + // correct. + recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate, backdatedTxnsAllowedTill, postReversals); + } + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + } + + public void postPreMaturityInterest(final LocalDate accountCloseDate, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, boolean postReversals) { + + final Money interestPostedToDate = totalInterestPosted(); + // calculate interest before one day of closure date + final LocalDate interestCalculatedToDate = accountCloseDate.minusDays(1); + final Money interestOnMaturity = calculatePreMatureInterest(interestCalculatedToDate, + retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + + boolean recalucateDailyBalance = false; + final boolean backdatedTxnsAllowedTill = false; + + // post remaining interest + final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate); + if (!remainigInterestToBePosted.isZero()) { + final boolean postInterestAsOn = false; + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), + accountCloseDate, remainigInterestToBePosted, postInterestAsOn); + addTransaction(newPostingTransaction); + recalucateDailyBalance = true; + } + + applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance, backdatedTxnsAllowedTill); + if (recalucateDailyBalance) { + // update existing transactions so derived balance fields are + // correct. + recalculateDailyBalances(Money.zero(this.currency), accountCloseDate, backdatedTxnsAllowedTill, postReversals); + } + + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + this.accountTermAndPreClosure.updateMaturityDetails(this.getAccountBalance(), accountCloseDate); + } + + public BigDecimal calculatePreMatureAmount(final LocalDate preMatureDate, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + + final Money interestPostedToDate = totalInterestPosted().copy(); + + final Money interestEarnedTillDate = calculatePreMatureInterest(preMatureDate, retreiveOrderedNonInterestPostingTransactions(), + isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + + final Money accountBalance = Money.of(getCurrency(), getAccountBalance()); + final Money maturityAmount = accountBalance.minus(interestPostedToDate).plus(interestEarnedTillDate); + + return maturityAmount.getAmount(); + } + + private Money calculatePreMatureInterest(final LocalDate preMatureDate, final List transactions, + final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + final MathContext mc = MathContext.DECIMAL64; + final List postingPeriods = calculateInterestPayable(mc, preMatureDate, transactions, isPreMatureClosure, + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + + Money interestOnMaturity = Money.zero(this.currency); + + for (final PostingPeriod interestPostingPeriod : postingPeriods) { + final Money interestEarnedForPeriod = interestPostingPeriod.getInterestEarned(); + interestOnMaturity = interestOnMaturity.plus(interestEarnedForPeriod); + } + this.summary.updateFromInterestPeriodSummaries(this.currency, postingPeriods); + return interestOnMaturity; + } + + @Override + public void postInterest(final MathContext mc, final LocalDate postingDate, final boolean isInterestTransfer, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, + final LocalDate postInterestAson, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { + final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); + super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestAson, backdatedTxnsAllowedTill, postReversals); + } + + @Override + public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, + final LocalDate postAsInterestOn, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { + final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); + return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postAsInterestOn, backdatedTxnsAllowedTill, postReversals); + } + + private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { + LocalDate interestPostingUpToDate = interestPostingDate; + final LocalDate uptoMaturityDate = interestCalculatedUpto(); + if (uptoMaturityDate != null && DateUtils.isBefore(uptoMaturityDate, interestPostingDate)) { + interestPostingUpToDate = uptoMaturityDate; + } + return interestPostingUpToDate; + } + + public LocalDate maturityDate() { + return this.accountTermAndPreClosure.getMaturityDate(); + } + + public BigDecimal maturityAmount() { + return this.accountTermAndPreClosure.maturityAmount(); + } + + private LocalDate interestCalculatedUpto() { + LocalDate uptoMaturityDate = calculateMaturityDate(); + if (uptoMaturityDate != null) { + // interest should not be calculated for maturity day + uptoMaturityDate = uptoMaturityDate.minusDays(1); + } + return uptoMaturityDate; + } + + private Money totalInterestPosted() { + Money interestPostedToDate = Money.zero(this.currency); + List trans = getTransactions(); + for (final SavingsAccountTransaction transaction : trans) { + if (transaction.isInterestPostingAndNotReversed()) { + interestPostedToDate = interestPostedToDate.plus(transaction.getAmount(currency)); + } + } + + return interestPostedToDate; + } + + @Override + public Map activate(final AppUser currentUser, final JsonCommand command) { + final Map actualChanges = super.activate(currentUser, command); + + if (accountTermAndPreClosure.isAfterExpectedFirstDepositDate(getActivationDate())) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(command.extractLocale()); + final String dateAsString = formatter.format(this.accountTermAndPreClosure.getExpectedFirstDepositOnDate()); + baseDataValidator.reset().parameter(DepositsApiConstants.activatedOnDateParamName).value(dateAsString) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.expected.first.deposit.date"); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + return actualChanges; + } + + protected List sortTransactions(final List transactions) { + final List listOfTransactionsSorted = new ArrayList<>(); + listOfTransactionsSorted.addAll(transactions); + + final SavingsAccountTransactionComparator transactionComparator = new SavingsAccountTransactionComparator(); + Collections.sort(listOfTransactionsSorted, transactionComparator); + return listOfTransactionsSorted; + } + + @Override + public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO, final boolean backdatedTxnsAllowedTill, + final Long relaxingDaysConfigForPivotDate, final String refNo) { + + if (isAccountMatured()) { + final String defaultUserMessage = "Transaction is not allowed. Account is matured."; + final ApiParameterError error = ApiParameterError.parameterError( + "error.msg.recurring.deposit.account.transaction.account.is.matured", defaultUserMessage, "transactionDate", + transactionDTO.getTransactionDate().format(transactionDTO.getFormatter())); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (!isBeforeMaturityDate(transactionDTO.getTransactionDate())) { + final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account maturity date."; + final ApiParameterError error = ApiParameterError.parameterError( + "error.msg.recurring.deposit.account.transaction.date.is.after.account.maturity.date", defaultUserMessage, + "transactionDate", transactionDTO.getTransactionDate().format(transactionDTO.getFormatter())); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (isBeforeDepositStartDate(transactionDTO.getTransactionDate())) { + final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account activation and deposit start date."; + final ApiParameterError error = ApiParameterError.parameterError( + "error.msg.recurring.deposit.account.transaction.date.is.before.account.activation.or.deposit.date", defaultUserMessage, + "transactionDate", transactionDTO.getTransactionDate().format(transactionDTO.getFormatter())); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + final SavingsAccountTransaction transaction = super.deposit(transactionDTO, backdatedTxnsAllowedTill, + relaxingDaysConfigForPivotDate, refNo); + + return transaction; + } + + public void handleScheduleInstallments(final SavingsAccountTransaction transaction) { + final LocalDate transactionDate = transaction.getTransactionDate(); + Money transactionAmountUnprocessed = transaction.getAmount(getCurrency()); + + for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { + if (currentInstallment.isNotFullyPaidOff() && transactionAmountUnprocessed.isGreaterThanZero()) { + if (!this.adjustAdvanceTowardsFuturePayments() && DateUtils.isBefore(transactionDate, currentInstallment.dueDate())) { + transactionAmountUnprocessed = Money.zero(getCurrency()); + } + transactionAmountUnprocessed = handleInstallmentTransaction(currentInstallment, transactionAmountUnprocessed, + transactionDate); + } + } + + } + + public void updateScheduleInstallments() { + // reset all installments to process from the beginning + for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { + currentInstallment.resetDerivedFields(); + } + + final List orderedDepositTransactions = retreiveOrderedDepositTransactions(); + for (SavingsAccountTransaction transaction : orderedDepositTransactions) { + handleScheduleInstallments(transaction); + } + } + + public void updateScheduleInstallmentsWithNewRecommendedDepositAmount(BigDecimal newDepositAmount, + LocalDate depositAmountupdatedFromDate) { + // reset all installments to process from the beginning, also update + // deposit amount as necessary + for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { + if (!DateUtils.isAfter(depositAmountupdatedFromDate, currentInstallment.dueDate())) { + currentInstallment.updateDepositAmountAndResetDerivedFields(newDepositAmount); + } else { + currentInstallment.resetDerivedFields(); + } + } + + final List orderedDepositTransactions = retreiveOrderedDepositTransactions(); + for (SavingsAccountTransaction transaction : orderedDepositTransactions) { + handleScheduleInstallments(transaction); + } + } + + private List retreiveOrderedDepositTransactions() { + final List listOfTransactionsSorted = retrieveListOfTransactions(); + + final List orderedDepositTransactions = new ArrayList<>(); + + for (final SavingsAccountTransaction transaction : listOfTransactionsSorted) { + if (transaction.isDepositAndNotReversed()) { + orderedDepositTransactions.add(transaction); + } + } + + return orderedDepositTransactions; + } + + /** + * This method is responsible for checking if the current transaction is 'an advance/early payment' based on the + * details passed through. + * + * Default implementation is check transaction date is before installment due date. + */ + protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex, + final List installments, final LocalDate transactionDate) { + final RecurringDepositScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex); + return DateUtils.isBefore(transactionDate, currentInstallment.dueDate()); + } + + private Money handleInstallmentTransaction(final RecurringDepositScheduleInstallment currentInstallment, + final Money transactionAmountUnprocessed, final LocalDate transactionDate) { + Money transactionAmountRemaining = transactionAmountUnprocessed; + Money depositAmountPortion = currentInstallment.payInstallment(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(depositAmountPortion); + + return transactionAmountRemaining; + + } + + private boolean isAccountMatured() { + return SavingsAccountStatusType.fromInt(status).isMatured(); + } + + private boolean isBeforeMaturityDate(final LocalDate compareDate) { + final LocalDate maturityDate = this.maturityDate(); + return maturityDate == null || DateUtils.isBefore(compareDate, maturityDate); + } + + private boolean isBeforeDepositStartDate(LocalDate compareDate) { + return DateUtils.isBefore(compareDate, depositStartDate()); + } + + public void validateDomainRules() { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + validateDomainRules(baseDataValidator); + super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + private void validateDomainRules(final DataValidatorBuilder baseDataValidator) { + final boolean isMinTermGreaterThanMax = this.accountTermAndPreClosure.depositTermDetail() + .isMinDepositTermGreaterThanMaxDepositTerm(); + // deposit period should be within min and max deposit term + if (isMinTermGreaterThanMax) { + final Integer maxTerm = this.accountTermAndPreClosure.depositTermDetail().maxDepositTerm(); + baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm) + .failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term"); + } + + final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod(); + if (this.accountTermAndPreClosure.depositTermDetail().maxDepositTerm() != null) { + baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull(); + } + if (depositPeriod != null) { + final SavingsPeriodFrequencyType depositPeriodFrequencyType = this.accountTermAndPreClosure.depositPeriodFrequencyType(); + final boolean isValidDepositPeriod = this.accountTermAndPreClosure.depositTermDetail() + .isDepositBetweenMinAndMax(depositStartDate(), calculateMaturityDate()); + if (!isValidDepositPeriod) { + baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod) + .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.between.min.and.max.deposit.term"); + } else { + final Integer inMultiplesOf = this.accountTermAndPreClosure.depositTermDetail().inMultiplesOfDepositTerm(); + if (inMultiplesOf != null) { + final boolean isValid = this.accountTermAndPreClosure.depositTermDetail().isValidInMultiplesOfPeriod(depositPeriod, + depositPeriodFrequencyType); + if (!isValid) { + baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod) + .failWithCodeNoParameterAddedToErrorCode("deposit.period.not.multiple.of.term"); + } + } + } + if (isAccountLocked(calculateMaturityDate())) { + baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).failWithCode( + "deposit.period.must.be.greater.than.lock.in.period", + "Deposit period must be greater than account lock-in period."); + } + + } + if (firstDepositDateBeforeAccountSubmittedOrActivationDate()) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + "expected.first.deposit.date.must.be.after.account.submitted.or.activation.date"); + } + + // FIXME: Handle this scenario + /* + * //final boolean recurringFrequencyBeforeDepositPeriod = recurringFrequencyBeforeDepositPeriod(); + * + * if (!recurringFrequencyBeforeDepositPeriod) { + * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + * "recurring.frequency.not.before.deposit.period"); } + */ + } + + public void validateApplicableInterestRate() { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + LocalDate maturityDate = calculateMaturityDate(); + if (this.chart != null) { + final LocalDate chartFromDate = this.chart.getFromDate(); + LocalDate chartEndDate = this.chart.getEndDate(); + chartEndDate = chartEndDate == null ? DateUtils.getBusinessLocalDate() : chartEndDate; + + final LocalDateInterval chartInterval = LocalDateInterval.create(chartFromDate, chartEndDate); + if (!chartInterval.contains(accountSubmittedOrActivationDate())) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.valid.interest.rate.slab.available.for.date.range"); + } + + if (maturityDate == null) { + maturityDate = DateUtils.getBusinessLocalDate(); + } + + final BigDecimal maturityAmount = this.accountTermAndPreClosure.depositAmount(); + BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(maturityAmount, depositStartDate(), maturityDate, + this.client); + + if (applicableInterestRate.compareTo(BigDecimal.ZERO) == 0) { + baseDataValidator.reset() + .failWithCodeNoParameterAddedToErrorCode("no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period"); + } + + } + // CBS: chart is optional — CBS base interest rate chart is used instead + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + /* + * final boolean recurringFrequencyBeforeDepositPeriod = recurringFrequencyBeforeDepositPeriod(); + * + * if (!recurringFrequencyBeforeDepositPeriod) { + * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + * "recurring.frequency.not.before.deposit.period"); } + */ + } + + public boolean isReinvestOnClosure() { + return this.accountTermAndPreClosure.isReinvestOnClosure(); + } + + public boolean isTransferToSavingsOnClosure() { + return this.accountTermAndPreClosure.isTransferToSavingsOnClosure(); + } + + public RecurringDepositAccount reInvest(BigDecimal depositAmount) { + + final DepositAccountTermAndPreClosure newAccountTermAndPreClosure = this.accountTermAndPreClosure.copy(depositAmount); + final DepositAccountRecurringDetail recurringDetail = this.recurringDetail.copy(); + final SavingsProduct product = this.product; + final InterestRateChart productChart = product.applicableChart(getClosedOnDate()); + final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart.from(productChart); + final String accountNumber = null; + final ExternalId externalId = this.externalId; + final AccountType accountType = AccountType.fromInt(this.accountType); + final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); + final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType + .fromInt(this.interestCompoundingPeriodType); + final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); + final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType + .fromInt(this.interestCalculationDaysInYearType); + final BigDecimal minRequiredOpeningBalance = depositAmount; + final BigDecimal interestRate = BigDecimal.ZERO; + final Set savingsAccountCharges = null; + final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType); + final Integer lockinPeriodFrequency = this.lockinPeriodFrequency; + final boolean withdrawalFeeApplicableForTransfer = false; + + LocalDate now = getClosedOnDate(); + newAccountTermAndPreClosure.updateExpectedFirstDepositDate(now); + + RecurringDepositAccount rdAccount = RecurringDepositAccount.createNewActivatedAccount(client, group, product, savingsOfficer, + accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRate, compoundingPeriodType, postingPeriodType, + interestCalculationType, daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, + withdrawalFeeApplicableForTransfer, savingsAccountCharges, newAccountTermAndPreClosure, recurringDetail, newChart, + withHoldTax); + + rdAccount.setDatesFrom(now); + newAccountTermAndPreClosure.updateAccountReference(rdAccount); + recurringDetail.updateAccountReference(rdAccount); + + return rdAccount; + + } + + private boolean firstDepositDateBeforeAccountSubmittedOrActivationDate() { + final LocalDate expectedFirstDepositLocalDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate(); + return expectedFirstDepositLocalDate != null + && DateUtils.isBefore(expectedFirstDepositLocalDate, accountSubmittedOrActivationDate()); + } + + public void setDatesFrom(final LocalDate now) { + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = null; + this.closedBy = null; + + this.activatedBy = null; + this.lockedInUntilDate = null; + + this.activatedOnDate = now; + } + + public void setClosedOnDate(final LocalDate closedOnDate) { + this.closedOnDate = closedOnDate; + } + + @Override + protected boolean isTransferInterestToOtherAccount() { + return this.accountTermAndPreClosure.isTransferInterestToLinkedAccount(); + } + + public void generateSchedule(final PeriodFrequencyType frequency, final Integer recurringEvery, final Calendar calendar) { + this.depositScheduleInstallments.clear(); + LocalDate installmentDate = null; + if (this.isCalendarInherited()) { + installmentDate = CalendarUtils.getNextScheduleDate(calendar, accountSubmittedOrActivationDate()); + } else { + installmentDate = depositStartDate(); + } + + int installmentNumber = 1; + final LocalDate maturityDate = calcualteScheduleTillDate(frequency, recurringEvery); + final BigDecimal depositAmount = this.recurringDetail.mandatoryRecommendedDepositAmount(); + while (DateUtils.isBefore(installmentDate, maturityDate)) { + final RecurringDepositScheduleInstallment installment = RecurringDepositScheduleInstallment.installment(this, installmentNumber, + installmentDate, depositAmount); + addDepositScheduleInstallment(installment); + installmentDate = DepositAccountUtils.calculateNextDepositDate(installmentDate, frequency, recurringEvery); + installmentNumber += 1; + } + updateDepositAmount(); + } + + private LocalDate calcualteScheduleTillDate(final PeriodFrequencyType frequency, final Integer recurringEvery) { + LocalDate tillDate = calculateMaturityDate(); + if (tillDate == null) { + final LocalDate today = DateUtils.getBusinessLocalDate(); + tillDate = DepositAccountUtils.calculateNextDepositDate(today, frequency, + recurringEvery * (DepositAccountUtils.GENERATE_MINIMUM_NUMBER_OF_FUTURE_INSTALMENTS + 1)); + } + return tillDate; + } + + private List depositScheduleInstallments() { + return this.depositScheduleInstallments; + } + + private void addDepositScheduleInstallment(final RecurringDepositScheduleInstallment installment) { + this.depositScheduleInstallments.add(installment); + } + + public boolean isCalendarInherited() { + return this.recurringDetail.isCalendarInherited(); + } + + public void updateOverduePayments(final LocalDate todayDate) { + LocalDate overdueUptoDate = this.maturityDate(); + if (overdueUptoDate == null || DateUtils.isAfter(overdueUptoDate, todayDate)) { + overdueUptoDate = todayDate; + } + + final List installments = depositScheduleInstallments(); + int noOfOverdueInstallments = 0; + Money totalOverdueAmount = Money.zero(getCurrency()); + for (RecurringDepositScheduleInstallment installment : installments) { + if (installment.isNotFullyPaidOff() && DateUtils.isAfter(overdueUptoDate, installment.dueDate())) { + noOfOverdueInstallments++; + totalOverdueAmount = totalOverdueAmount.plus(installment.getDepositAmountOutstanding(getCurrency())); + } + } + this.recurringDetail.updateOverdueDetails(noOfOverdueInstallments, totalOverdueAmount); + } + + @Override + public boolean allowWithdrawal() { + return this.recurringDetail.allowWithdrawal(); + } + + public boolean adjustAdvanceTowardsFuturePayments() { + return this.recurringDetail.adjustAdvanceTowardsFuturePayments(); + } + + @Override + public boolean isTransactionsAllowed() { + return isActive() || isAccountMatured(); + } + + public DepositAccountRecurringDetail getRecurringDetail() { + return this.recurringDetail; + } + + static class RecurringDepositScheduleInstallmentComparator implements Comparator { + + @Override + public int compare(final RecurringDepositScheduleInstallment o1, final RecurringDepositScheduleInstallment o2) { + return o1.installmentNumber().compareTo(o2.installmentNumber()); + } + } + + @Override + public void loadLazyCollections() { + this.depositScheduleInstallments.size(); + super.loadLazyCollections(); + this.chart.getId(); + } + + public BigDecimal getDepositAmount() { + return this.accountTermAndPreClosure.depositAmount(); + } + + @Override + public DepositAccountType depositAccountType() { + return DepositAccountType.fromInt(300); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java index 2d72f6d59..3199914b7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java @@ -1,623 +1,641 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.portfolio.savings.domain; - -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountFacilityConfigParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountNoParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountStatusParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianClientIdParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.modeOfOperationParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountTypeParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.customerSegmentParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianCifNumberParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianNameParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianRelationshipParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerCifNumberParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerIdParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerNameParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.jointHoldersParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.linkedAccountIdParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.linkedAccountNoParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.linkedAccountTypeParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minorDateOfBirthParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nomineeParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.specialCategoryParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.weakerSectionParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoDebitFacilityParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.clientIdParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.externalIdParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.groupIdParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productIdParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.submittedOnDateParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; -import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; -import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; -import org.apache.fineract.organisation.staff.domain.Staff; -import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper; -import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; -import org.apache.fineract.portfolio.accountdetails.domain.AccountType; -import org.apache.fineract.portfolio.client.domain.Client; -import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; -import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; -import org.apache.fineract.portfolio.group.domain.Group; -import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper; -import org.apache.fineract.portfolio.group.exception.CenterNotActiveException; -import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException; -import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; -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.SavingsAccountData; -import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; -import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException; -import org.apache.fineract.useradministration.domain.AppUser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Service; - -@Service -public class SavingsAccountAssembler { - - private static final Logger LOG = LoggerFactory.getLogger(SavingsAccountAssembler.class); - private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper; - private final SavingsAccountTransactionDataSummaryWrapper savingsAccountTransactionDataSummaryWrapper; - private final SavingsHelper savingsHelper; - private final ClientRepositoryWrapper clientRepository; - private final GroupRepositoryWrapper groupRepository; - private final StaffRepositoryWrapper staffRepository; - private final SavingsProductRepository savingProductRepository; - private final SavingsAccountRepositoryWrapper savingsAccountRepository; - private final SavingsAccountChargeAssembler savingsAccountChargeAssembler; - private final FromJsonHelper fromApiJsonHelper; - private final JdbcTemplate jdbcTemplate; - private final ConfigurationDomainService configurationDomainService; - private final ExternalIdFactory externalIdFactory; - - @Autowired - public SavingsAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, - final SavingsAccountTransactionDataSummaryWrapper savingsAccountTransactionDataSummaryWrapper, - final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository, - final StaffRepositoryWrapper staffRepository, final SavingsProductRepository savingProductRepository, - final SavingsAccountRepositoryWrapper savingsAccountRepository, - final SavingsAccountChargeAssembler savingsAccountChargeAssembler, final FromJsonHelper fromApiJsonHelper, - final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final JdbcTemplate jdbcTemplate, - final ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory) { - this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; - this.savingsAccountTransactionDataSummaryWrapper = savingsAccountTransactionDataSummaryWrapper; - this.clientRepository = clientRepository; - this.groupRepository = groupRepository; - this.staffRepository = staffRepository; - this.savingProductRepository = savingProductRepository; - this.savingsAccountRepository = savingsAccountRepository; - this.savingsAccountChargeAssembler = savingsAccountChargeAssembler; - this.fromApiJsonHelper = fromApiJsonHelper; - savingsHelper = new SavingsHelper(accountTransfersReadPlatformService); - this.jdbcTemplate = jdbcTemplate; - this.configurationDomainService = configurationDomainService; - this.externalIdFactory = externalIdFactory; - } - - /** - * Assembles a new {@link SavingsAccount} from JSON details passed in request inheriting details where relevant from - * chosen {@link SavingsProduct}. - */ - public SavingsAccount assembleFrom(final JsonCommand command, final AppUser submittedBy) { - - final JsonElement element = command.parsedJson(); - - final String accountNo = this.fromApiJsonHelper.extractStringNamed(accountNoParamName, element); - final String externalId = this.fromApiJsonHelper.extractStringNamed(externalIdParamName, element); - final Long productId = this.fromApiJsonHelper.extractLongNamed(productIdParamName, element); - - final SavingsProduct product = this.savingProductRepository.findById(productId) - .orElseThrow(() -> new SavingsProductNotFoundException(productId)); - - Client client = null; - Group group = null; - Staff fieldOfficer = null; - AccountType accountType = AccountType.INVALID; - - final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, element); - if (clientId != null) { - client = this.clientRepository.findOneWithNotFoundDetection(clientId); - accountType = AccountType.INDIVIDUAL; - if (client.isNotActive()) { - throw new ClientNotActiveException(clientId); - } - } - - final Long groupId = this.fromApiJsonHelper.extractLongNamed(groupIdParamName, element); - if (groupId != null) { - group = this.groupRepository.findOneWithNotFoundDetection(groupId); - accountType = AccountType.GROUP; - if (group.isNotActive()) { - if (group.isCenter()) { - throw new CenterNotActiveException(groupId); - } - throw new GroupNotActiveException(groupId); - } - } - - if (group != null && client != null) { - if (!group.hasClientAsMember(client)) { - throw new ClientNotInGroupException(clientId, groupId); - } - accountType = AccountType.JLG; - } - - if ((Boolean) command.booleanPrimitiveValueOfParameterNamed("isGSIM") != null) { - if (command.booleanPrimitiveValueOfParameterNamed("isGSIM")) { - accountType = AccountType.GSIM; - } - } - - final Long fieldOfficerId = this.fromApiJsonHelper.extractLongNamed(fieldOfficerIdParamName, element); - if (fieldOfficerId != null) { - fieldOfficer = this.staffRepository.findOneWithNotFoundDetection(fieldOfficerId); - } - - final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element); - - BigDecimal interestRate = null; - if (command.parameterExists(nominalAnnualInterestRateParamName)) { - interestRate = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName); - } else { - interestRate = product.nominalAnnualInterestRate(); - } - - SavingsCompoundingInterestPeriodType interestCompoundingPeriodType = null; - final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName); - if (interestPeriodTypeValue != null) { - interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(interestPeriodTypeValue); - } else { - interestCompoundingPeriodType = product.interestCompoundingPeriodType(); - } - - SavingsPostingInterestPeriodType interestPostingPeriodType = null; - final Integer interestPostingPeriodTypeValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName); - if (interestPostingPeriodTypeValue != null) { - interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(interestPostingPeriodTypeValue); - } else { - interestPostingPeriodType = product.interestPostingPeriodType(); - } - - SavingsInterestCalculationType interestCalculationType = null; - final Integer interestCalculationTypeValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName); - if (interestCalculationTypeValue != null) { - interestCalculationType = SavingsInterestCalculationType.fromInt(interestCalculationTypeValue); - } else { - interestCalculationType = product.interestCalculationType(); - } - - SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType = null; - final Integer interestCalculationDaysInYearTypeValue = command - .integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName); - if (interestCalculationDaysInYearTypeValue != null) { - interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(interestCalculationDaysInYearTypeValue); - } else { - interestCalculationDaysInYearType = product.interestCalculationDaysInYearType(); - } - - BigDecimal minRequiredOpeningBalance = null; - if (command.parameterExists(minRequiredOpeningBalanceParamName)) { - minRequiredOpeningBalance = command.bigDecimalValueOfParameterNamed(minRequiredOpeningBalanceParamName); - } else { - minRequiredOpeningBalance = product.minRequiredOpeningBalance(); - } - - Integer lockinPeriodFrequency = null; - if (command.parameterExists(lockinPeriodFrequencyParamName)) { - lockinPeriodFrequency = command.integerValueOfParameterNamed(lockinPeriodFrequencyParamName); - } else { - lockinPeriodFrequency = product.lockinPeriodFrequency(); - } - - SavingsPeriodFrequencyType lockinPeriodFrequencyType = null; - Integer lockinPeriodFrequencyTypeValue = null; - if (command.parameterExists(lockinPeriodFrequencyTypeParamName)) { - lockinPeriodFrequencyTypeValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName); - if (lockinPeriodFrequencyTypeValue != null) { - lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); - } - } else { - lockinPeriodFrequencyType = product.lockinPeriodFrequencyType(); - } - boolean iswithdrawalFeeApplicableForTransfer = false; - if (command.parameterExists(withdrawalFeeForTransfersParamName)) { - iswithdrawalFeeApplicableForTransfer = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName); - } - - final Set charges = this.savingsAccountChargeAssembler.fromParsedJson(element, product.currency().getCode()); - - boolean allowOverdraft = false; - if (command.parameterExists(allowOverdraftParamName)) { - allowOverdraft = command.booleanPrimitiveValueOfParameterNamed(allowOverdraftParamName); - } else { - allowOverdraft = product.isAllowOverdraft(); - } - - BigDecimal overdraftLimit = BigDecimal.ZERO; - if (command.parameterExists(overdraftLimitParamName)) { - overdraftLimit = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(overdraftLimitParamName); - } else { - overdraftLimit = product.overdraftLimit(); - } - - BigDecimal nominalAnnualInterestRateOverdraft = BigDecimal.ZERO; - if (command.parameterExists(nominalAnnualInterestRateOverdraftParamName)) { - nominalAnnualInterestRateOverdraft = command - .bigDecimalValueOfParameterNamedDefaultToNullIfZero(nominalAnnualInterestRateOverdraftParamName); - } else { - nominalAnnualInterestRateOverdraft = product.nominalAnnualInterestRateOverdraft(); - } - - BigDecimal minOverdraftForInterestCalculation = BigDecimal.ZERO; - if (command.parameterExists(minOverdraftForInterestCalculationParamName)) { - minOverdraftForInterestCalculation = command - .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minOverdraftForInterestCalculationParamName); - } else { - minOverdraftForInterestCalculation = product.minOverdraftForInterestCalculation(); - } - - boolean enforceMinRequiredBalance = false; - if (command.parameterExists(enforceMinRequiredBalanceParamName)) { - enforceMinRequiredBalance = command.booleanPrimitiveValueOfParameterNamed(enforceMinRequiredBalanceParamName); - } else { - enforceMinRequiredBalance = product.isMinRequiredBalanceEnforced(); - } - - BigDecimal minRequiredBalance = BigDecimal.ZERO; - if (command.parameterExists(minRequiredBalanceParamName)) { - minRequiredBalance = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredBalanceParamName); - } else { - minRequiredBalance = product.minRequiredBalance(); - } - - boolean lienAllowed = false; - if (command.parameterExists(lienAllowedParamName)) { - lienAllowed = command.booleanPrimitiveValueOfParameterNamed(lienAllowedParamName); - } else { - lienAllowed = product.isLienAllowed(); - } - - BigDecimal maxAllowedLienLimit = BigDecimal.ZERO; - if (command.parameterExists(maxAllowedLienLimitParamName)) { - maxAllowedLienLimit = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(maxAllowedLienLimitParamName); - } else { - maxAllowedLienLimit = product.maxAllowedLienLimit(); - } - - boolean withHoldTax = product.withHoldTax(); - if (command.parameterExists(withHoldTaxParamName)) { - withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); - if (withHoldTax && product.getTaxGroup() == null) { - throw new UnsupportedParameterException(Arrays.asList(withHoldTaxParamName)); - } - } - - final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNo, - externalIdFactory.create(externalId), accountType, submittedOnDate, submittedBy, interestRate, - interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, charges, - allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, maxAllowedLienLimit, lienAllowed, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax); - account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); - - account.validateNewApplicationState(SAVINGS_ACCOUNT_RESOURCE_NAME); - - account.validateAccountValuesWithProduct(); - - // CBS extended fields - applyCbsFields(command, element, account); - - return account; - } - - private static final Gson GSON = new Gson(); - - public void applyCbsFields(final JsonCommand command, final JsonElement element, final SavingsAccount account) { - // Product classification - final String cbsAccountType = this.fromApiJsonHelper.extractStringNamed(accountTypeParamName, element); - if (cbsAccountType != null) account.setCbsAccountType(cbsAccountType); - - final String customerSegment = this.fromApiJsonHelper.extractStringNamed(customerSegmentParamName, element); - if (customerSegment != null) account.setCustomerSegment(customerSegment); - - // Status of Account - final String accountStatus = this.fromApiJsonHelper.extractStringNamed(accountStatusParamName, element); - if (accountStatus != null) account.setAccountStatus(accountStatus); - - final String modeOfOperation = this.fromApiJsonHelper.extractStringNamed(modeOfOperationParamName, element); - if (modeOfOperation != null) account.setModeOfOperation(modeOfOperation); - - account.setSpecialCategory(this.fromApiJsonHelper.extractStringNamed(specialCategoryParamName, element)); - account.setWeakerSection(this.fromApiJsonHelper.extractStringNamed(weakerSectionParamName, element)); - - if (command.parameterExists(nominationFacilityParamName)) { - account.setNominationFacility(this.fromApiJsonHelper.extractBooleanNamed(nominationFacilityParamName, element)); - } - if (command.parameterExists(autoDebitFacilityParamName)) { - account.setAutoDebitFacility(this.fromApiJsonHelper.extractBooleanNamed(autoDebitFacilityParamName, element)); - } - - account.setGuardianName(this.fromApiJsonHelper.extractStringNamed(guardianNameParamName, element)); - account.setGuardianCifNumber(this.fromApiJsonHelper.extractStringNamed(guardianCifNumberParamName, element)); - account.setGuardianRelationship(this.fromApiJsonHelper.extractStringNamed(guardianRelationshipParamName, element)); - - if (command.parameterExists(minorDateOfBirthParamName)) { - final LocalDate minorDob = this.fromApiJsonHelper.extractLocalDateNamed(minorDateOfBirthParamName, element); - account.setMinorDateOfBirth(minorDob); - } - - final Long guardianClientId = this.fromApiJsonHelper.extractLongNamed(guardianClientIdParamName, element); - if (guardianClientId != null) account.setGuardianClientId(guardianClientId); - - // Introducer - final Long introducerId = this.fromApiJsonHelper.extractLongNamed(introducerIdParamName, element); - if (introducerId != null) account.setIntroducerClientId(introducerId); - account.setIntroducerName(this.fromApiJsonHelper.extractStringNamed(introducerNameParamName, element)); - account.setIntroducerCifNumber(this.fromApiJsonHelper.extractStringNamed(introducerCifNumberParamName, element)); - - // Nominee (stored as JSON string) - if (element.getAsJsonObject().has(nomineeParamName)) { - final JsonElement nomineeEl = element.getAsJsonObject().get(nomineeParamName); - if (nomineeEl != null && !nomineeEl.isJsonNull()) { - account.setNomineeData(nomineeEl.toString()); - } - } - - // Deposit Account Link - final Long linkedAccountId = this.fromApiJsonHelper.extractLongNamed(linkedAccountIdParamName, element); - if (linkedAccountId != null) account.setLinkedAccountId(linkedAccountId); - account.setLinkedAccountType(this.fromApiJsonHelper.extractStringNamed(linkedAccountTypeParamName, element)); - account.setLinkedAccountNo(this.fromApiJsonHelper.extractStringNamed(linkedAccountNoParamName, element)); - - // Account Facility Config - if (element.getAsJsonObject().has(accountFacilityConfigParamName)) { - final JsonElement configEl = element.getAsJsonObject().get(accountFacilityConfigParamName); - if (configEl != null && !configEl.isJsonNull() && configEl.isJsonObject()) { - SavingsAccountFacilityConfig facilityConfig = account.getFacilityConfig(); - if (facilityConfig == null) { - facilityConfig = new SavingsAccountFacilityConfig(account); - account.setFacilityConfig(facilityConfig); - } - facilityConfig.updateFrom(configEl.getAsJsonObject()); - } - } - - // Joint holders - if (element.getAsJsonObject().has(jointHoldersParamName)) { - final JsonElement holdersEl = element.getAsJsonObject().get(jointHoldersParamName); - if (holdersEl != null && !holdersEl.isJsonNull() && holdersEl.isJsonArray()) { - final com.google.gson.JsonArray holdersArray = holdersEl.getAsJsonArray(); - final List holders = new ArrayList<>(); - for (int i = 0; i < holdersArray.size(); i++) { - final JsonObject h = holdersArray.get(i).getAsJsonObject(); - final Long clientId = h.has("clientId") && !h.get("clientId").isJsonNull() - ? h.get("clientId").getAsLong() : null; - final String cifNumber = h.has("cifNumber") && !h.get("cifNumber").isJsonNull() - ? h.get("cifNumber").getAsString() : null; - final String displayName = h.has("displayName") && !h.get("displayName").isJsonNull() - ? h.get("displayName").getAsString() : null; - final BigDecimal sharePercentage = h.has("sharePercentage") && !h.get("sharePercentage").isJsonNull() - ? h.get("sharePercentage").getAsBigDecimal() : null; - final boolean isPrimary = h.has("isPrimary") && h.get("isPrimary").getAsBoolean(); - String signingAuthority = h.has("signingAuthority") && !h.get("signingAuthority").isJsonNull() - ? h.get("signingAuthority").getAsString() : null; - String role = h.has("role") && !h.get("role").isJsonNull() - ? h.get("role").getAsString() : null; - - if (signingAuthority == null || signingAuthority.trim().isEmpty()) { - signingAuthority = isPrimary ? "Mandatory" : "Optional"; - } - if (role == null || role.trim().isEmpty()) { - role = isPrimary ? "Primary Account Holder" : "Co-Holder"; - } - - final boolean isMandatory = h.has("isMandatory") && h.get("isMandatory").getAsBoolean(); - holders.add(new SavingsAccountJointHolder(account, clientId, cifNumber, displayName, - sharePercentage, signingAuthority, role, isPrimary, isMandatory)); - } - account.getJointHolders().clear(); - account.getJointHolders().addAll(holders); - } - } - } - - public SavingsAccount assembleFrom(final Long savingsId, final boolean backdatedTxnsAllowedTill) { - SavingsAccount account = this.savingsAccountRepository.findSavingsWithNotFoundDetection(savingsId, backdatedTxnsAllowedTill); - return loadTransactionsToSavingsAccount(account, backdatedTxnsAllowedTill); - } - - public SavingsAccount loadTransactionsToSavingsAccount(final SavingsAccount account, final boolean backdatedTxnsAllowedTill) { - List savingsAccountTransactions = null; - if (backdatedTxnsAllowedTill) { - LocalDate pivotDate = account.getSummary().getInterestPostedTillDate(); - boolean isNotPresent = pivotDate == null; - if (!isNotPresent) { - // Get savings account transactions - if (isRelaxingDaysConfigForPivotDateEnabled()) { - final Long relaxingDaysForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate(); - LocalDate interestPostedTillDate = account.getSummary().getInterestPostedTillDate(); - savingsAccountTransactions = this.savingsAccountRepository.findTransactionsAfterPivotDate(account, - interestPostedTillDate.minusDays(relaxingDaysForPivotDate)); - - savingsAccountTransactions.get(0).getSavingsAccount() - .setStartInterestCalculationDate(interestPostedTillDate.minusDays(relaxingDaysForPivotDate)); - List pivotDateTransaction = this.savingsAccountRepository - .findTransactionRunningBalanceBeforePivotDate(account, - interestPostedTillDate.minusDays(relaxingDaysForPivotDate + 1)); - if (pivotDateTransaction != null && !pivotDateTransaction.isEmpty()) { - account.getSummary().setRunningBalanceOnPivotDate(pivotDateTransaction.get(pivotDateTransaction.size() - 1) - .getRunningBalance(account.getCurrency()).getAmount()); - } - } else { - savingsAccountTransactions = this.savingsAccountRepository.findTransactionsAfterPivotDate(account, - account.getSummary().getInterestPostedTillDate()); - } - - if (savingsAccountTransactions != null && savingsAccountTransactions.size() > 0) { - // Update transient variable - account.setSavingsAccountTransactions(savingsAccountTransactions); - } - } else { - savingsAccountTransactions = this.savingsAccountRepository.findAllTransactions(account); - account.setSavingsAccountTransactions(savingsAccountTransactions); - } - } - - account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); - return account; - } - - public SavingsAccountData assembleSavings(final SavingsAccountData account) { - - // Update last running balance on account level - final boolean backdatedTxnsAllowedTill = this.configurationDomainService.retrievePivotDateConfig(); - if (backdatedTxnsAllowedTill && account.getSavingsAccountTransactionData() != null - && account.getSummary().getInterestPostedTillDate() != null) { - List removalList = new ArrayList<>(); - - for (int i = 0; i < account.getSavingsAccountTransactionData().size(); i++) { - SavingsAccountTransactionData savingsAccountTransaction = account.getSavingsAccountTransactionData().get(i); - removalList.add(savingsAccountTransaction); - if ((savingsAccountTransaction.isInterestPostingAndNotReversed() - || savingsAccountTransaction.isOverdraftInterestAndNotReversed()) - && !savingsAccountTransaction.isReversalTransaction()) { - account.getSummary().setRunningBalanceOnPivotDate(savingsAccountTransaction.getRunningBalance()); - account.setLastSavingsAccountTransaction(savingsAccountTransaction); - break; - } - } - account.getSavingsAccountTransactionData().removeAll(removalList); - } else { - account.getSummary().setRunningBalanceOnPivotDate(BigDecimal.ZERO); - } - account.setHelpers(this.savingsAccountTransactionDataSummaryWrapper, this.savingsHelper); - return account; - } - - public boolean getPivotConfigStatus() { - return this.configurationDomainService.retrievePivotDateConfig(); - } - - public boolean isRelaxingDaysConfigForPivotDateEnabled() { - return this.configurationDomainService.isRelaxingDaysConfigForPivotDateEnabled(); - } - - public void setHelpers(final SavingsAccount account) { - account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); - } - - /** - * Assembles a new {@link SavingsAccount} from JSON details passed in request inheriting details where relevant from - * chosen {@link SavingsProduct}. - */ - public SavingsAccount assembleFrom(final Client client, final Group group, final Long productId, final LocalDate appliedonDate, - final AppUser appliedBy) { - - AccountType accountType = AccountType.INVALID; - if (client != null) { - accountType = AccountType.INDIVIDUAL; - if (client.isNotActive()) { - throw new ClientNotActiveException(client.getId()); - } - } - - if (group != null) { - accountType = AccountType.GROUP; - if (group.isNotActive()) { - if (group.isCenter()) { - throw new CenterNotActiveException(group.getId()); - } - throw new GroupNotActiveException(group.getId()); - } - } - - if (group != null && client != null) { - if (!group.hasClientAsMember(client)) { - throw new ClientNotInGroupException(client.getId(), group.getId()); - } - accountType = AccountType.JLG; - } - final SavingsProduct product = this.savingProductRepository.findById(productId).orElseThrow(); - final Set charges = this.savingsAccountChargeAssembler.fromSavingsProduct(product); - final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, null, null, null, - accountType, appliedonDate, appliedBy, product.nominalAnnualInterestRate(), product.interestCompoundingPeriodType(), - product.interestPostingPeriodType(), product.interestCalculationType(), product.interestCalculationDaysInYearType(), - product.minRequiredOpeningBalance(), product.lockinPeriodFrequency(), product.lockinPeriodFrequencyType(), - product.isWithdrawalFeeApplicableForTransfer(), charges, product.isAllowOverdraft(), product.overdraftLimit(), - product.isMinRequiredBalanceEnforced(), product.minRequiredBalance(), product.maxAllowedLienLimit(), - product.isLienAllowed(), product.nominalAnnualInterestRateOverdraft(), product.minOverdraftForInterestCalculation(), - product.withHoldTax()); - account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); - - account.validateNewApplicationState(SAVINGS_ACCOUNT_RESOURCE_NAME); - - account.validateAccountValuesWithProduct(); - - return account; - } - - public void assignSavingAccountHelpers(final SavingsAccount savingsAccount) { - savingsAccount.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); - } - - public void assignSavingAccountHelpers(final SavingsAccountData savingsAccountData) { - savingsAccountData.setHelpers(this.savingsAccountTransactionDataSummaryWrapper, this.savingsHelper); - } -} +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.savings.domain; + +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountFacilityConfigParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountNoParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountStatusParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianClientIdParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.modeOfOperationParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.customerSegmentParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianCifNumberParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianNameParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianRelationshipParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerCifNumberParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerIdParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerNameParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.jointHoldersParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.linkedAccountIdParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.linkedAccountNoParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.linkedAccountTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minorDateOfBirthParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nomineeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.specialCategoryParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.weakerSectionParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoDebitFacilityParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isSeniorCitizenParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.seniorCitizenInterestRateParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.clientIdParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.externalIdParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.groupIdParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productIdParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.submittedOnDateParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.organisation.staff.domain.Staff; +import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper; +import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; +import org.apache.fineract.portfolio.accountdetails.domain.AccountType; +import org.apache.fineract.portfolio.client.domain.Client; +import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; +import org.apache.fineract.portfolio.group.domain.Group; +import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper; +import org.apache.fineract.portfolio.group.exception.CenterNotActiveException; +import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException; +import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; +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.SavingsAccountData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; +import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException; +import org.apache.fineract.useradministration.domain.AppUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +@Service +public class SavingsAccountAssembler { + + private static final Logger LOG = LoggerFactory.getLogger(SavingsAccountAssembler.class); + private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper; + private final SavingsAccountTransactionDataSummaryWrapper savingsAccountTransactionDataSummaryWrapper; + private final SavingsHelper savingsHelper; + private final ClientRepositoryWrapper clientRepository; + private final GroupRepositoryWrapper groupRepository; + private final StaffRepositoryWrapper staffRepository; + private final SavingsProductRepository savingProductRepository; + private final SavingsAccountRepositoryWrapper savingsAccountRepository; + private final SavingsAccountChargeAssembler savingsAccountChargeAssembler; + private final FromJsonHelper fromApiJsonHelper; + private final JdbcTemplate jdbcTemplate; + private final ConfigurationDomainService configurationDomainService; + private final ExternalIdFactory externalIdFactory; + + @Autowired + public SavingsAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, + final SavingsAccountTransactionDataSummaryWrapper savingsAccountTransactionDataSummaryWrapper, + final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository, + final StaffRepositoryWrapper staffRepository, final SavingsProductRepository savingProductRepository, + final SavingsAccountRepositoryWrapper savingsAccountRepository, + final SavingsAccountChargeAssembler savingsAccountChargeAssembler, final FromJsonHelper fromApiJsonHelper, + final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final JdbcTemplate jdbcTemplate, + final ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory) { + this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; + this.savingsAccountTransactionDataSummaryWrapper = savingsAccountTransactionDataSummaryWrapper; + this.clientRepository = clientRepository; + this.groupRepository = groupRepository; + this.staffRepository = staffRepository; + this.savingProductRepository = savingProductRepository; + this.savingsAccountRepository = savingsAccountRepository; + this.savingsAccountChargeAssembler = savingsAccountChargeAssembler; + this.fromApiJsonHelper = fromApiJsonHelper; + savingsHelper = new SavingsHelper(accountTransfersReadPlatformService); + this.jdbcTemplate = jdbcTemplate; + this.configurationDomainService = configurationDomainService; + this.externalIdFactory = externalIdFactory; + } + + /** + * Assembles a new {@link SavingsAccount} from JSON details passed in request inheriting details where relevant from + * chosen {@link SavingsProduct}. + */ + public SavingsAccount assembleFrom(final JsonCommand command, final AppUser submittedBy) { + + final JsonElement element = command.parsedJson(); + + final String accountNo = this.fromApiJsonHelper.extractStringNamed(accountNoParamName, element); + final String externalId = this.fromApiJsonHelper.extractStringNamed(externalIdParamName, element); + final Long productId = this.fromApiJsonHelper.extractLongNamed(productIdParamName, element); + + final SavingsProduct product = this.savingProductRepository.findById(productId) + .orElseThrow(() -> new SavingsProductNotFoundException(productId)); + + Client client = null; + Group group = null; + Staff fieldOfficer = null; + AccountType accountType = AccountType.INVALID; + + final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, element); + if (clientId != null) { + client = this.clientRepository.findOneWithNotFoundDetection(clientId); + accountType = AccountType.INDIVIDUAL; + if (client.isNotActive()) { + throw new ClientNotActiveException(clientId); + } + } + + final Long groupId = this.fromApiJsonHelper.extractLongNamed(groupIdParamName, element); + if (groupId != null) { + group = this.groupRepository.findOneWithNotFoundDetection(groupId); + accountType = AccountType.GROUP; + if (group.isNotActive()) { + if (group.isCenter()) { + throw new CenterNotActiveException(groupId); + } + throw new GroupNotActiveException(groupId); + } + } + + if (group != null && client != null) { + if (!group.hasClientAsMember(client)) { + throw new ClientNotInGroupException(clientId, groupId); + } + accountType = AccountType.JLG; + } + + if ((Boolean) command.booleanPrimitiveValueOfParameterNamed("isGSIM") != null) { + if (command.booleanPrimitiveValueOfParameterNamed("isGSIM")) { + accountType = AccountType.GSIM; + } + } + + final Long fieldOfficerId = this.fromApiJsonHelper.extractLongNamed(fieldOfficerIdParamName, element); + if (fieldOfficerId != null) { + fieldOfficer = this.staffRepository.findOneWithNotFoundDetection(fieldOfficerId); + } + + final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParamName, element); + + BigDecimal interestRate = null; + if (command.parameterExists(nominalAnnualInterestRateParamName)) { + interestRate = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName); + } else { + interestRate = product.nominalAnnualInterestRate(); + } + + SavingsCompoundingInterestPeriodType interestCompoundingPeriodType = null; + final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName); + if (interestPeriodTypeValue != null) { + interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(interestPeriodTypeValue); + } else { + interestCompoundingPeriodType = product.interestCompoundingPeriodType(); + } + + SavingsPostingInterestPeriodType interestPostingPeriodType = null; + final Integer interestPostingPeriodTypeValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName); + if (interestPostingPeriodTypeValue != null) { + interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(interestPostingPeriodTypeValue); + } else { + interestPostingPeriodType = product.interestPostingPeriodType(); + } + + SavingsInterestCalculationType interestCalculationType = null; + final Integer interestCalculationTypeValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName); + if (interestCalculationTypeValue != null) { + interestCalculationType = SavingsInterestCalculationType.fromInt(interestCalculationTypeValue); + } else { + interestCalculationType = product.interestCalculationType(); + } + + SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType = null; + final Integer interestCalculationDaysInYearTypeValue = command + .integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName); + if (interestCalculationDaysInYearTypeValue != null) { + interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(interestCalculationDaysInYearTypeValue); + } else { + interestCalculationDaysInYearType = product.interestCalculationDaysInYearType(); + } + + BigDecimal minRequiredOpeningBalance = null; + if (command.parameterExists(minRequiredOpeningBalanceParamName)) { + minRequiredOpeningBalance = command.bigDecimalValueOfParameterNamed(minRequiredOpeningBalanceParamName); + } else { + minRequiredOpeningBalance = product.minRequiredOpeningBalance(); + } + + Integer lockinPeriodFrequency = null; + if (command.parameterExists(lockinPeriodFrequencyParamName)) { + lockinPeriodFrequency = command.integerValueOfParameterNamed(lockinPeriodFrequencyParamName); + } else { + lockinPeriodFrequency = product.lockinPeriodFrequency(); + } + + SavingsPeriodFrequencyType lockinPeriodFrequencyType = null; + Integer lockinPeriodFrequencyTypeValue = null; + if (command.parameterExists(lockinPeriodFrequencyTypeParamName)) { + lockinPeriodFrequencyTypeValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName); + if (lockinPeriodFrequencyTypeValue != null) { + lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue); + } + } else { + lockinPeriodFrequencyType = product.lockinPeriodFrequencyType(); + } + boolean iswithdrawalFeeApplicableForTransfer = false; + if (command.parameterExists(withdrawalFeeForTransfersParamName)) { + iswithdrawalFeeApplicableForTransfer = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName); + } + + final Set charges = this.savingsAccountChargeAssembler.fromParsedJson(element, product.currency().getCode()); + + boolean allowOverdraft = false; + if (command.parameterExists(allowOverdraftParamName)) { + allowOverdraft = command.booleanPrimitiveValueOfParameterNamed(allowOverdraftParamName); + } else { + allowOverdraft = product.isAllowOverdraft(); + } + + BigDecimal overdraftLimit = BigDecimal.ZERO; + if (command.parameterExists(overdraftLimitParamName)) { + overdraftLimit = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(overdraftLimitParamName); + } else { + overdraftLimit = product.overdraftLimit(); + } + + BigDecimal nominalAnnualInterestRateOverdraft = BigDecimal.ZERO; + if (command.parameterExists(nominalAnnualInterestRateOverdraftParamName)) { + nominalAnnualInterestRateOverdraft = command + .bigDecimalValueOfParameterNamedDefaultToNullIfZero(nominalAnnualInterestRateOverdraftParamName); + } else { + nominalAnnualInterestRateOverdraft = product.nominalAnnualInterestRateOverdraft(); + } + + BigDecimal minOverdraftForInterestCalculation = BigDecimal.ZERO; + if (command.parameterExists(minOverdraftForInterestCalculationParamName)) { + minOverdraftForInterestCalculation = command + .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minOverdraftForInterestCalculationParamName); + } else { + minOverdraftForInterestCalculation = product.minOverdraftForInterestCalculation(); + } + + boolean enforceMinRequiredBalance = false; + if (command.parameterExists(enforceMinRequiredBalanceParamName)) { + enforceMinRequiredBalance = command.booleanPrimitiveValueOfParameterNamed(enforceMinRequiredBalanceParamName); + } else { + enforceMinRequiredBalance = product.isMinRequiredBalanceEnforced(); + } + + BigDecimal minRequiredBalance = BigDecimal.ZERO; + if (command.parameterExists(minRequiredBalanceParamName)) { + minRequiredBalance = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredBalanceParamName); + } else { + minRequiredBalance = product.minRequiredBalance(); + } + + boolean lienAllowed = false; + if (command.parameterExists(lienAllowedParamName)) { + lienAllowed = command.booleanPrimitiveValueOfParameterNamed(lienAllowedParamName); + } else { + lienAllowed = product.isLienAllowed(); + } + + BigDecimal maxAllowedLienLimit = BigDecimal.ZERO; + if (command.parameterExists(maxAllowedLienLimitParamName)) { + maxAllowedLienLimit = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(maxAllowedLienLimitParamName); + } else { + maxAllowedLienLimit = product.maxAllowedLienLimit(); + } + + boolean withHoldTax = product.withHoldTax(); + if (command.parameterExists(withHoldTaxParamName)) { + withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); + if (withHoldTax && product.getTaxGroup() == null) { + throw new UnsupportedParameterException(Arrays.asList(withHoldTaxParamName)); + } + } + + final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNo, + externalIdFactory.create(externalId), accountType, submittedOnDate, submittedBy, interestRate, + interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, + minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, charges, + allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, maxAllowedLienLimit, lienAllowed, + nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax); + account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + + account.validateNewApplicationState(SAVINGS_ACCOUNT_RESOURCE_NAME); + + account.validateAccountValuesWithProduct(); + + // CBS extended fields + applyCbsFields(command, element, account); + + return account; + } + + private static final Gson GSON = new Gson(); + + public void applyCbsFields(final JsonCommand command, final JsonElement element, final SavingsAccount account) { + // Product classification + final String cbsAccountType = this.fromApiJsonHelper.extractStringNamed(accountTypeParamName, element); + if (cbsAccountType != null) account.setCbsAccountType(cbsAccountType); + + final String customerSegment = this.fromApiJsonHelper.extractStringNamed(customerSegmentParamName, element); + if (customerSegment != null) account.setCustomerSegment(customerSegment); + + // Status of Account + final String accountStatus = this.fromApiJsonHelper.extractStringNamed(accountStatusParamName, element); + if (accountStatus != null) account.setAccountStatus(accountStatus); + + final String modeOfOperation = this.fromApiJsonHelper.extractStringNamed(modeOfOperationParamName, element); + if (modeOfOperation != null) account.setModeOfOperation(modeOfOperation); + + account.setSpecialCategory(this.fromApiJsonHelper.extractStringNamed(specialCategoryParamName, element)); + account.setWeakerSection(this.fromApiJsonHelper.extractStringNamed(weakerSectionParamName, element)); + + if (command.parameterExists(nominationFacilityParamName)) { + account.setNominationFacility(this.fromApiJsonHelper.extractBooleanNamed(nominationFacilityParamName, element)); + } + if (command.parameterExists(autoDebitFacilityParamName)) { + account.setAutoDebitFacility(this.fromApiJsonHelper.extractBooleanNamed(autoDebitFacilityParamName, element)); + } + + // Senior citizen rate — runs after validateDomainRules so it is the final persisted value + if (command.parameterExists(isSeniorCitizenParamName)) { + final Boolean scFlag = this.fromApiJsonHelper.extractBooleanNamed(isSeniorCitizenParamName, element); + if (scFlag != null) { + account.setSeniorCitizen(scFlag); + if (Boolean.TRUE.equals(scFlag) && command.parameterExists(seniorCitizenInterestRateParamName)) { + final BigDecimal scRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed( + seniorCitizenInterestRateParamName, element); + if (scRate != null && scRate.compareTo(BigDecimal.ZERO) > 0) { + account.setSeniorCitizenInterestRate(scRate); + account.updateNominalAnnualInterestRate(scRate); + } + } + } + } + + account.setGuardianName(this.fromApiJsonHelper.extractStringNamed(guardianNameParamName, element)); + account.setGuardianCifNumber(this.fromApiJsonHelper.extractStringNamed(guardianCifNumberParamName, element)); + account.setGuardianRelationship(this.fromApiJsonHelper.extractStringNamed(guardianRelationshipParamName, element)); + + if (command.parameterExists(minorDateOfBirthParamName)) { + final LocalDate minorDob = this.fromApiJsonHelper.extractLocalDateNamed(minorDateOfBirthParamName, element); + account.setMinorDateOfBirth(minorDob); + } + + final Long guardianClientId = this.fromApiJsonHelper.extractLongNamed(guardianClientIdParamName, element); + if (guardianClientId != null) account.setGuardianClientId(guardianClientId); + + // Introducer + final Long introducerId = this.fromApiJsonHelper.extractLongNamed(introducerIdParamName, element); + if (introducerId != null) account.setIntroducerClientId(introducerId); + account.setIntroducerName(this.fromApiJsonHelper.extractStringNamed(introducerNameParamName, element)); + account.setIntroducerCifNumber(this.fromApiJsonHelper.extractStringNamed(introducerCifNumberParamName, element)); + + // Nominee (stored as JSON string) + if (element.getAsJsonObject().has(nomineeParamName)) { + final JsonElement nomineeEl = element.getAsJsonObject().get(nomineeParamName); + if (nomineeEl != null && !nomineeEl.isJsonNull()) { + account.setNomineeData(nomineeEl.toString()); + } + } + + // Deposit Account Link + final Long linkedAccountId = this.fromApiJsonHelper.extractLongNamed(linkedAccountIdParamName, element); + if (linkedAccountId != null) account.setLinkedAccountId(linkedAccountId); + account.setLinkedAccountType(this.fromApiJsonHelper.extractStringNamed(linkedAccountTypeParamName, element)); + account.setLinkedAccountNo(this.fromApiJsonHelper.extractStringNamed(linkedAccountNoParamName, element)); + + // Account Facility Config + if (element.getAsJsonObject().has(accountFacilityConfigParamName)) { + final JsonElement configEl = element.getAsJsonObject().get(accountFacilityConfigParamName); + if (configEl != null && !configEl.isJsonNull() && configEl.isJsonObject()) { + SavingsAccountFacilityConfig facilityConfig = account.getFacilityConfig(); + if (facilityConfig == null) { + facilityConfig = new SavingsAccountFacilityConfig(account); + account.setFacilityConfig(facilityConfig); + } + facilityConfig.updateFrom(configEl.getAsJsonObject()); + } + } + + // Joint holders + if (element.getAsJsonObject().has(jointHoldersParamName)) { + final JsonElement holdersEl = element.getAsJsonObject().get(jointHoldersParamName); + if (holdersEl != null && !holdersEl.isJsonNull() && holdersEl.isJsonArray()) { + final com.google.gson.JsonArray holdersArray = holdersEl.getAsJsonArray(); + final List holders = new ArrayList<>(); + for (int i = 0; i < holdersArray.size(); i++) { + final JsonObject h = holdersArray.get(i).getAsJsonObject(); + final Long clientId = h.has("clientId") && !h.get("clientId").isJsonNull() + ? h.get("clientId").getAsLong() : null; + final String cifNumber = h.has("cifNumber") && !h.get("cifNumber").isJsonNull() + ? h.get("cifNumber").getAsString() : null; + final String displayName = h.has("displayName") && !h.get("displayName").isJsonNull() + ? h.get("displayName").getAsString() : null; + final BigDecimal sharePercentage = h.has("sharePercentage") && !h.get("sharePercentage").isJsonNull() + ? h.get("sharePercentage").getAsBigDecimal() : null; + final boolean isPrimary = h.has("isPrimary") && h.get("isPrimary").getAsBoolean(); + String signingAuthority = h.has("signingAuthority") && !h.get("signingAuthority").isJsonNull() + ? h.get("signingAuthority").getAsString() : null; + String role = h.has("role") && !h.get("role").isJsonNull() + ? h.get("role").getAsString() : null; + + if (signingAuthority == null || signingAuthority.trim().isEmpty()) { + signingAuthority = isPrimary ? "Mandatory" : "Optional"; + } + if (role == null || role.trim().isEmpty()) { + role = isPrimary ? "Primary Account Holder" : "Co-Holder"; + } + + final boolean isMandatory = h.has("isMandatory") && h.get("isMandatory").getAsBoolean(); + holders.add(new SavingsAccountJointHolder(account, clientId, cifNumber, displayName, + sharePercentage, signingAuthority, role, isPrimary, isMandatory)); + } + account.getJointHolders().clear(); + account.getJointHolders().addAll(holders); + } + } + } + + public SavingsAccount assembleFrom(final Long savingsId, final boolean backdatedTxnsAllowedTill) { + SavingsAccount account = this.savingsAccountRepository.findSavingsWithNotFoundDetection(savingsId, backdatedTxnsAllowedTill); + return loadTransactionsToSavingsAccount(account, backdatedTxnsAllowedTill); + } + + public SavingsAccount loadTransactionsToSavingsAccount(final SavingsAccount account, final boolean backdatedTxnsAllowedTill) { + List savingsAccountTransactions = null; + if (backdatedTxnsAllowedTill) { + LocalDate pivotDate = account.getSummary().getInterestPostedTillDate(); + boolean isNotPresent = pivotDate == null; + if (!isNotPresent) { + // Get savings account transactions + if (isRelaxingDaysConfigForPivotDateEnabled()) { + final Long relaxingDaysForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate(); + LocalDate interestPostedTillDate = account.getSummary().getInterestPostedTillDate(); + savingsAccountTransactions = this.savingsAccountRepository.findTransactionsAfterPivotDate(account, + interestPostedTillDate.minusDays(relaxingDaysForPivotDate)); + + savingsAccountTransactions.get(0).getSavingsAccount() + .setStartInterestCalculationDate(interestPostedTillDate.minusDays(relaxingDaysForPivotDate)); + List pivotDateTransaction = this.savingsAccountRepository + .findTransactionRunningBalanceBeforePivotDate(account, + interestPostedTillDate.minusDays(relaxingDaysForPivotDate + 1)); + if (pivotDateTransaction != null && !pivotDateTransaction.isEmpty()) { + account.getSummary().setRunningBalanceOnPivotDate(pivotDateTransaction.get(pivotDateTransaction.size() - 1) + .getRunningBalance(account.getCurrency()).getAmount()); + } + } else { + savingsAccountTransactions = this.savingsAccountRepository.findTransactionsAfterPivotDate(account, + account.getSummary().getInterestPostedTillDate()); + } + + if (savingsAccountTransactions != null && savingsAccountTransactions.size() > 0) { + // Update transient variable + account.setSavingsAccountTransactions(savingsAccountTransactions); + } + } else { + savingsAccountTransactions = this.savingsAccountRepository.findAllTransactions(account); + account.setSavingsAccountTransactions(savingsAccountTransactions); + } + } + + account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + return account; + } + + public SavingsAccountData assembleSavings(final SavingsAccountData account) { + + // Update last running balance on account level + final boolean backdatedTxnsAllowedTill = this.configurationDomainService.retrievePivotDateConfig(); + if (backdatedTxnsAllowedTill && account.getSavingsAccountTransactionData() != null + && account.getSummary().getInterestPostedTillDate() != null) { + List removalList = new ArrayList<>(); + + for (int i = 0; i < account.getSavingsAccountTransactionData().size(); i++) { + SavingsAccountTransactionData savingsAccountTransaction = account.getSavingsAccountTransactionData().get(i); + removalList.add(savingsAccountTransaction); + if ((savingsAccountTransaction.isInterestPostingAndNotReversed() + || savingsAccountTransaction.isOverdraftInterestAndNotReversed()) + && !savingsAccountTransaction.isReversalTransaction()) { + account.getSummary().setRunningBalanceOnPivotDate(savingsAccountTransaction.getRunningBalance()); + account.setLastSavingsAccountTransaction(savingsAccountTransaction); + break; + } + } + account.getSavingsAccountTransactionData().removeAll(removalList); + } else { + account.getSummary().setRunningBalanceOnPivotDate(BigDecimal.ZERO); + } + account.setHelpers(this.savingsAccountTransactionDataSummaryWrapper, this.savingsHelper); + return account; + } + + public boolean getPivotConfigStatus() { + return this.configurationDomainService.retrievePivotDateConfig(); + } + + public boolean isRelaxingDaysConfigForPivotDateEnabled() { + return this.configurationDomainService.isRelaxingDaysConfigForPivotDateEnabled(); + } + + public void setHelpers(final SavingsAccount account) { + account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + } + + /** + * Assembles a new {@link SavingsAccount} from JSON details passed in request inheriting details where relevant from + * chosen {@link SavingsProduct}. + */ + public SavingsAccount assembleFrom(final Client client, final Group group, final Long productId, final LocalDate appliedonDate, + final AppUser appliedBy) { + + AccountType accountType = AccountType.INVALID; + if (client != null) { + accountType = AccountType.INDIVIDUAL; + if (client.isNotActive()) { + throw new ClientNotActiveException(client.getId()); + } + } + + if (group != null) { + accountType = AccountType.GROUP; + if (group.isNotActive()) { + if (group.isCenter()) { + throw new CenterNotActiveException(group.getId()); + } + throw new GroupNotActiveException(group.getId()); + } + } + + if (group != null && client != null) { + if (!group.hasClientAsMember(client)) { + throw new ClientNotInGroupException(client.getId(), group.getId()); + } + accountType = AccountType.JLG; + } + final SavingsProduct product = this.savingProductRepository.findById(productId).orElseThrow(); + final Set charges = this.savingsAccountChargeAssembler.fromSavingsProduct(product); + final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, null, null, null, + accountType, appliedonDate, appliedBy, product.nominalAnnualInterestRate(), product.interestCompoundingPeriodType(), + product.interestPostingPeriodType(), product.interestCalculationType(), product.interestCalculationDaysInYearType(), + product.minRequiredOpeningBalance(), product.lockinPeriodFrequency(), product.lockinPeriodFrequencyType(), + product.isWithdrawalFeeApplicableForTransfer(), charges, product.isAllowOverdraft(), product.overdraftLimit(), + product.isMinRequiredBalanceEnforced(), product.minRequiredBalance(), product.maxAllowedLienLimit(), + product.isLienAllowed(), product.nominalAnnualInterestRateOverdraft(), product.minOverdraftForInterestCalculation(), + product.withHoldTax()); + account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + + account.validateNewApplicationState(SAVINGS_ACCOUNT_RESOURCE_NAME); + + account.validateAccountValuesWithProduct(); + + return account; + } + + public void assignSavingAccountHelpers(final SavingsAccount savingsAccount) { + savingsAccount.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + } + + public void assignSavingAccountHelpers(final SavingsAccountData savingsAccountData) { + savingsAccountData.setHelpers(this.savingsAccountTransactionDataSummaryWrapper, this.savingsHelper); + } +} diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 09f6a936b..592ae3103 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -316,4 +316,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0300_rename_fd_base_interest_rate_tables_to_cbs.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0300_rename_fd_base_interest_rate_tables_to_cbs.xml new file mode 100644 index 000000000..c75abc79e --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0300_rename_fd_base_interest_rate_tables_to_cbs.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index 60dfa4ec3..5d2cb13ca 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -3969,6 +3969,12 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom @Column(name = "auto_debit_facility", nullable = false, columnDefinition = "boolean default false") private Boolean autoDebitFacility = false; + @Column(name = "is_senior_citizen", nullable = false, columnDefinition = "boolean default false") + private boolean isSeniorCitizen = false; + + @Column(name = "senior_citizen_interest_rate", scale = 6, precision = 19, nullable = true) + private BigDecimal seniorCitizenInterestRate; + @jakarta.persistence.OneToOne(cascade = jakarta.persistence.CascadeType.ALL, mappedBy = "savingsAccount", orphanRemoval = true, fetch = FetchType.LAZY) private SavingsAccountFacilityConfig facilityConfig; @@ -4041,4 +4047,16 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom public Boolean getAutoDebitFacility() { return autoDebitFacility; } public void setAutoDebitFacility(Boolean v) { this.autoDebitFacility = v; } + + public boolean isSeniorCitizen() { return isSeniorCitizen; } + public void setSeniorCitizen(boolean v) { this.isSeniorCitizen = v; } + + public BigDecimal getSeniorCitizenInterestRate() { return seniorCitizenInterestRate; } + public void setSeniorCitizenInterestRate(BigDecimal v) { this.seniorCitizenInterestRate = v; } + + public void updateNominalAnnualInterestRate(BigDecimal rate) { + if (rate != null && rate.compareTo(BigDecimal.ZERO) > 0) { + this.nominalAnnualInterestRate = rate; + } + } } diff --git a/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/module-changelog-master.xml b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/module-changelog-master.xml index e3a40fc1b..f8c193c0b 100644 --- a/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/module-changelog-master.xml +++ b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/module-changelog-master.xml @@ -1,40 +1,41 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2015_add_senior_citizen_to_savings_account.xml b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2015_add_senior_citizen_to_savings_account.xml new file mode 100644 index 000000000..6031e6afa --- /dev/null +++ b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2015_add_senior_citizen_to_savings_account.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + +