merge(release): merge feature/minor-update into release/share-account-module

Combines election module + UAT catch-up migrations (0338-0367) with
minor-update changes: soft delete for tellers (0338), share linking
for current account (0339), terminate flag for savings/current (0340).
Resolved changelog conflict by appending minor-update entries after 0367.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-07-09 13:43:03 +05:30
co-authored by Claude Sonnet 4.6
125 changed files with 7194 additions and 167 deletions
@@ -34,7 +34,8 @@ public enum CommandProcessingResultType {
AWAITING_APPROVAL(2, "commandProcessingResultType.awaiting.approval"), //
REJECTED(3, "commandProcessingResultType.rejected"), //
UNDER_PROCESSING(4, "commandProcessingResultType.underProcessing"), //
ERROR(5, "commandProcessingResultType.error");
ERROR(5, "commandProcessingResultType.error"), //
TERMINATE(6, "commandProcessingResultType.terminate");
private static final Map<Integer, CommandProcessingResultType> BY_ID = Arrays.stream(values())
.collect(Collectors.toMap(CommandProcessingResultType::getValue, v -> v));
@@ -59,4 +60,8 @@ public enum CommandProcessingResultType {
public boolean isRejected() {
return this == REJECTED;
}
public boolean isTerminate() {
return this == TERMINATE;
}
}
@@ -1611,6 +1611,14 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder terminateAccount(final Long accountId) {
this.actionName = "TERMINATE";
this.entityName = "ACCOUNT";
this.entityId = accountId;
this.href = "/accounts/" + accountId + "/terminate";
return this;
}
public CommandWrapperBuilder approveGSIMAccountApplication(final Long accountId) {
this.actionName = "APPROVE";
this.entityName = "GSIMACCOUNT";
@@ -3565,7 +3573,7 @@ public class CommandWrapperBuilder {
this.actionName = "ACTIVATE";
this.entityName = "CURRENTACCOUNT";
this.savingsId = accountId;
this.entityId = null;
this.entityId = accountId;
this.href = "/currentaccounts/" + accountId + "?command=activate";
return this;
}
@@ -20,6 +20,7 @@ package org.apache.fineract.commands.service;
import static org.apache.fineract.commands.domain.CommandProcessingResultType.ERROR;
import static org.apache.fineract.commands.domain.CommandProcessingResultType.PROCESSED;
import static org.apache.fineract.commands.domain.CommandProcessingResultType.TERMINATE;
import static org.apache.http.HttpStatus.SC_OK;
import com.google.gson.Gson;
@@ -181,7 +182,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
currentSource.setResultStatusCode(SC_OK);
currentSource.updateForAudit(result);
currentSource.setResult(toApiResultJsonSerializer.serializeResult(result));
currentSource.setStatus(PROCESSED);
currentSource.setStatus("TERMINATE".equals(wrapper.getActionName()) ? TERMINATE : PROCESSED);
// Return saved command source
return commandSourceService.saveResultSameTransaction(currentSource);
@@ -224,7 +224,7 @@ public final class DepositsApiConstants {
* Deposit Product Parameters
*/
private static final Set<String> DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName,
dateFormatParamName, monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName,
digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName,
interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, accountingRuleParamName, chargesParamName,
@@ -170,6 +170,7 @@ public final class SavingsAccountData implements Serializable {
private Boolean balanceSwapOption;
private Boolean msme;
private Boolean autoDebitFacility;
private Boolean terminateTrue;
private String avgBalanceCalcPeriod;
private String validForPeriod;
private Boolean odAccountClosedZeroBalanceInDayClosing;
@@ -159,6 +159,9 @@ public final class SavingsProductData implements Serializable {
@Getter @Setter private Map<String, Object> depositLimitKyc;
@Getter @Setter private Map<String, List<Map<String, Object>>> otherGlMappingsMap;
// True when a row exists in m_saving_product_temp with status = PENDING for this product - that row is the lock.
@Getter @Setter private Boolean isPendingApproval;
public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
final EnumOptionData interestCalculationDaysInYearType, final EnumOptionData accountingRule,
@@ -0,0 +1,49 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.savings.data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
/**
* Read-side view of a single pending Savings Product edit row (m_savings_product_temp), for the Approvals ->
* Pending Savings Product Edits screen.
*/
@Getter
@Setter
public class SavingsProductTempData implements Serializable {
private Long id;
private Long savingsProductId;
private String savingsProductName;
private Long makerId;
private String makerUsername;
private Long checkerId;
private String checkerUsername;
private LocalDateTime createdOn;
private LocalDateTime checkedOn;
private String status;
private String rejectReason;
private Map<String, Object> proposedChanges;
public SavingsProductTempData() {}
}
@@ -36,7 +36,8 @@ public enum SavingsAccountStatusType {
REJECTED(500, "savingsAccountStatusType.rejected"), //
CLOSED(600, "savingsAccountStatusType.closed"), //
PRE_MATURE_CLOSURE(700, "savingsAccountStatusType.pre.mature.closure"), //
MATURED(800, "savingsAccountStatusType.matured"); //
MATURED(800, "savingsAccountStatusType.matured"), //
TERMINATED(900, "savingsAccountStatusType.terminated"); //
private final Integer value;
private final String code;
@@ -53,6 +54,7 @@ public enum SavingsAccountStatusType {
case 600 -> SavingsAccountStatusType.CLOSED;
case 700 -> SavingsAccountStatusType.PRE_MATURE_CLOSURE;
case 800 -> SavingsAccountStatusType.MATURED;
case 900 -> SavingsAccountStatusType.TERMINATED;
default -> SavingsAccountStatusType.INVALID;
};
}
@@ -113,4 +115,8 @@ public enum SavingsAccountStatusType {
public boolean isPreMatureClosure() {
return this.value.equals(SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue());
}
public boolean isTerminated() {
return this.value.equals(SavingsAccountStatusType.TERMINATED.getValue());
}
}
@@ -285,6 +285,11 @@ public final class SavingsEnumerations {
SavingsAccountStatusType.MATURED.getCode(), "Matured", submittedAndPendingApproval, isApproved, isRejected,
isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured);
break;
case TERMINATED:
optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.TERMINATED.getValue().longValue(),
SavingsAccountStatusType.TERMINATED.getCode(), "Terminated", submittedAndPendingApproval, isApproved, isRejected,
isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured);
break;
}
return optionData;
}