This is an automated email from the ASF dual-hosted git repository.

aleks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 8d560a1f7c FINERACT-2332: SavingCOB infrastructure
8d560a1f7c is described below

commit 8d560a1f7c0f23c1fd5099546754973528cc4c68
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Tue Jan 27 10:00:45 2026 -0500

    FINERACT-2332: SavingCOB infrastructure
---
 .../v1/SavingsAccountStayedLockedDataV1.avsc       |  27 +++++
 .../v1/SavingsAccountsStayedLockedDataV1.avsc      |  12 +++
 .../cob/savings/RetrieveSavingsIdServiceImpl.java  | 117 +++++++++++++++++++++
 .../SavingsAccountsStayedLockedDataMapper.java     |  34 ++++++
 ...ccountsStayedLockedBusinessEventSerializer.java |  54 ++++++++++
 ...nalEventConfigurationValidationServiceTest.java |   7 +-
 .../cob/savings/RetrieveSavingsIdService.java      |  44 ++++++++
 .../fineract/cob/savings/SavingsAccountLock.java   |  80 ++++++++++++++
 .../cob/savings/SavingsAccountLockRepository.java  |  50 +++++++++
 .../SavingsAccountsStayedLockedBusinessEvent.java  |  46 ++++++++
 .../savings/SavingsAccountsStayedLockedData.java   |  31 ++++++
 .../cob/savings/SavingsCOBBusinessStep.java        |  26 +++++
 .../fineract/cob/savings/SavingsCOBConstant.java   |  40 +++++++
 .../SavingsLockCannotBeAppliedException.java       |  26 +++++
 .../fineract/cob/savings/SavingsLockOwner.java     |  24 +++++
 .../cob/savings/SavingsLockingService.java         |  36 +++++++
 .../fineract/cob/savings/SavingsReadException.java |  33 ++++++
 .../portfolio/savings/domain/SavingsAccount.java   |  11 ++
 .../savings/domain/SavingsAccountRepository.java   |  50 +++++++++
 .../domain/SavingsAccountRepositoryWrapper.java    |  13 +--
 .../savings/parts/module-changelog-master.xml      |   1 +
 ...004_add_savings_account_cob_infrastructure.xml} |  24 +++--
 .../common/ExternalEventConfigurationHelper.java   |   5 +
 23 files changed, 772 insertions(+), 19 deletions(-)

diff --git 
a/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountStayedLockedDataV1.avsc
 
b/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountStayedLockedDataV1.avsc
new file mode 100644
index 0000000000..bcd6105cb6
--- /dev/null
+++ 
b/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountStayedLockedDataV1.avsc
@@ -0,0 +1,27 @@
+{
+    "name": "SavingsAccountStayedLockedDataV1",
+    "namespace": "org.apache.fineract.avro.savings.v1",
+    "type": "record",
+    "fields": [
+        {
+            "name": "id",
+            "type": "long"
+        },
+        {
+            "default": null,
+            "name": "externalId",
+            "type": [
+                "null",
+                "string"
+            ]
+        },
+        {
+            "default": null,
+            "name": "accountNo",
+            "type": [
+                "null",
+                "string"
+            ]
+        }
+    ]
+}
diff --git 
a/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountsStayedLockedDataV1.avsc
 
b/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountsStayedLockedDataV1.avsc
new file mode 100644
index 0000000000..0d7efc1e53
--- /dev/null
+++ 
b/fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountsStayedLockedDataV1.avsc
@@ -0,0 +1,12 @@
+{
+    "name": "SavingsAccountsStayedLockedDataV1",
+    "namespace": "org.apache.fineract.avro.savings.v1",
+    "type": "record",
+    "fields": [{
+        "name": "savingsAccounts",
+        "type": {
+            "type": "array",
+            "items": 
"org.apache.fineract.avro.savings.v1.SavingsAccountStayedLockedDataV1"
+        }
+    }]
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/savings/RetrieveSavingsIdServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/savings/RetrieveSavingsIdServiceImpl.java
new file mode 100644
index 0000000000..ba81701380
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/savings/RetrieveSavingsIdServiceImpl.java
@@ -0,0 +1,117 @@
+/**
+ * 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.cob.savings;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.cob.data.COBIdAndExternalIdAndAccountNo;
+import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
+import org.apache.fineract.cob.data.COBParameter;
+import org.apache.fineract.cob.data.COBPartition;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class RetrieveSavingsIdServiceImpl implements RetrieveSavingsIdService {
+
+    private static final Collection<Integer> NON_CLOSED_SAVINGS_STATUSES = new 
ArrayList<>(
+            
Arrays.asList(SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
 SavingsAccountStatusType.APPROVED.getValue(),
+                    SavingsAccountStatusType.ACTIVE.getValue(), 
SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getValue(),
+                    SavingsAccountStatusType.TRANSFER_ON_HOLD.getValue()));
+
+    private final SavingsAccountRepository savingsAccountRepository;
+    private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
+    @Override
+    public List<COBPartition> retrieveSavingsCOBPartitions(Long numberOfDays, 
LocalDate businessDate, boolean isCatchUp,
+            int partitionSize) {
+        String sql = """
+                    select min(id) as min, max(id) as max, page, count(id) as 
count from
+                    (select floor(((row_number() over(order by id))-1) / 
:pageSize) as page, t.* from
+                    (select id from m_savings_account where status_enum in 
(:statusIds) and
+                """;
+        if (isCatchUp) {
+            sql = sql + " last_closed_business_date = :businessDate ";
+        } else {
+            sql = sql + " (last_closed_business_date = :businessDate or 
last_closed_business_date is null) ";
+        }
+        sql = sql + " order by id) t) t2 group by page order by page";
+
+        MapSqlParameterSource parameters = new MapSqlParameterSource();
+        parameters.addValue("pageSize", partitionSize);
+        parameters.addValue("statusIds", NON_CLOSED_SAVINGS_STATUSES);
+        parameters.addValue("businessDate", 
businessDate.minusDays(numberOfDays));
+        return namedParameterJdbcTemplate.query(sql.toString(), parameters, 
RetrieveSavingsIdServiceImpl::mapRow);
+    }
+
+    private static COBPartition mapRow(ResultSet rs, int rowNum) throws 
SQLException {
+        return new COBPartition(rs.getLong("min"), rs.getLong("max"), 
rs.getLong("page"), rs.getLong("count"));
+    }
+
+    @Override
+    public List<COBIdAndLastClosedBusinessDate> 
retrieveSavingsIdsBehindDate(LocalDate businessDate, List<Long> savingsIds) {
+        return 
savingsAccountRepository.findAllSavingsIdsBehindDate(businessDate, savingsIds);
+    }
+
+    @Override
+    public List<COBIdAndLastClosedBusinessDate> 
retrieveSavingsIdsBehindDateOrNull(LocalDate businessDate, List<Long> 
savingsIds) {
+        return 
savingsAccountRepository.findAllSavingsIdsBehindDateOrNull(businessDate, 
savingsIds);
+    }
+
+    @Override
+    public List<COBIdAndLastClosedBusinessDate> 
retrieveSavingsIdsOldestCobProcessed(LocalDate businessDate) {
+        return savingsAccountRepository.findAllSavingsIdsOldestCobProcessed();
+    }
+
+    @Override
+    public List<Long> 
retrieveAllNonClosedSavingsByLastClosedBusinessDateAndMinAndMaxSavingsId(COBParameter
 savingsCOBParameter,
+            boolean isCatchUp) {
+        LocalDate cobBusinessDate = 
ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)
+                .minusDays(SavingsCOBConstant.NUMBER_OF_DAYS_BEHIND);
+
+        if (isCatchUp) {
+            return 
savingsAccountRepository.findAllSavingsByLastClosedBusinessDateNotNullAndMinAndMaxSavingsIdAndStatuses(
+                    savingsCOBParameter.getMinAccountId(), 
savingsCOBParameter.getMaxAccountId(), cobBusinessDate,
+                    NON_CLOSED_SAVINGS_STATUSES);
+        } else {
+            return 
savingsAccountRepository.findAllSavingsByLastClosedBusinessDateAndMinAndMaxSavingsIdAndStatuses(
+                    savingsCOBParameter.getMinAccountId(), 
savingsCOBParameter.getMaxAccountId(), cobBusinessDate,
+                    NON_CLOSED_SAVINGS_STATUSES);
+        }
+    }
+
+    @Override
+    public List<COBIdAndExternalIdAndAccountNo> 
findAllStayedLockedByCobBusinessDate(LocalDate cobBusinessDate) {
+        // This will be implemented when we add the query to join with 
SavingsAccountLock
+        // For now, return empty list as the lock table doesn't exist yet
+        return List.of();
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/savings/SavingsAccountsStayedLockedDataMapper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/savings/SavingsAccountsStayedLockedDataMapper.java
new file mode 100644
index 0000000000..fa8388f86e
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/savings/SavingsAccountsStayedLockedDataMapper.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package 
org.apache.fineract.infrastructure.event.external.service.serialization.mapper.savings;
+
+import org.apache.fineract.avro.savings.v1.SavingsAccountStayedLockedDataV1;
+import org.apache.fineract.avro.savings.v1.SavingsAccountsStayedLockedDataV1;
+import org.apache.fineract.cob.data.COBIdAndExternalIdAndAccountNo;
+import org.apache.fineract.cob.savings.SavingsAccountsStayedLockedData;
+import 
org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroMapperConfig;
+import org.mapstruct.Mapper;
+
+@Mapper(config = AvroMapperConfig.class)
+public interface SavingsAccountsStayedLockedDataMapper {
+
+    SavingsAccountsStayedLockedDataV1 map(SavingsAccountsStayedLockedData 
savingsAccountsStayedLockedData);
+
+    SavingsAccountStayedLockedDataV1 map(COBIdAndExternalIdAndAccountNo 
cobIdAndExternalIdAndAccountNo);
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/savings/SavingsAccountsStayedLockedBusinessEventSerializer.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/savings/SavingsAccountsStayedLockedBusinessEventSerializer.java
new file mode 100644
index 0000000000..2908b2253d
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/savings/SavingsAccountsStayedLockedBusinessEventSerializer.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package 
org.apache.fineract.infrastructure.event.external.service.serialization.serializer.savings;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.avro.generic.GenericContainer;
+import org.apache.fineract.avro.generator.ByteBufferSerializable;
+import org.apache.fineract.avro.savings.v1.SavingsAccountsStayedLockedDataV1;
+import 
org.apache.fineract.cob.savings.SavingsAccountsStayedLockedBusinessEvent;
+import org.apache.fineract.cob.savings.SavingsAccountsStayedLockedData;
+import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
+import 
org.apache.fineract.infrastructure.event.external.service.serialization.mapper.savings.SavingsAccountsStayedLockedDataMapper;
+import 
org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class SavingsAccountsStayedLockedBusinessEventSerializer implements 
BusinessEventSerializer {
+
+    private final SavingsAccountsStayedLockedDataMapper mapper;
+
+    @Override
+    public <T> boolean canSerialize(BusinessEvent<T> event) {
+        return event instanceof SavingsAccountsStayedLockedBusinessEvent;
+    }
+
+    @Override
+    public <T> ByteBufferSerializable toAvroDTO(BusinessEvent<T> rawEvent) {
+        SavingsAccountsStayedLockedBusinessEvent event = 
(SavingsAccountsStayedLockedBusinessEvent) rawEvent;
+        SavingsAccountsStayedLockedData savingsAccounts = event.get();
+        return mapper.map(savingsAccounts);
+    }
+
+    @Override
+    public Class<? extends GenericContainer> getSupportedSchema() {
+        return SavingsAccountsStayedLockedDataV1.class;
+    }
+}
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
index 697534d2de..b80cd8b4bc 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
@@ -112,7 +112,8 @@ public class 
ExternalEventConfigurationValidationServiceTest {
                 "LoanCapitalizedIncomeTransactionCreatedBusinessEvent", 
"LoanUndoContractTerminationBusinessEvent",
                 "LoanBuyDownFeeTransactionCreatedBusinessEvent", 
"LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent",
                 "LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent",
-                
"LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent", 
"LoanApprovedAmountChangedBusinessEvent");
+                
"LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent", 
"LoanApprovedAmountChangedBusinessEvent",
+                "SavingsAccountsStayedLockedBusinessEvent");
 
         List<FineractPlatformTenant> tenants = Arrays
                 .asList(new FineractPlatformTenant(1L, "default", "Default 
Tenant", "Europe/Budapest", null));
@@ -149,6 +150,7 @@ public class 
ExternalEventConfigurationValidationServiceTest {
 
         // then
         String expectedMessage = "No external events configured";
+
         String actualMessage = exceptionThrown.getMessage();
 
         assertTrue(actualMessage.contains(expectedMessage));
@@ -206,7 +208,8 @@ public class 
ExternalEventConfigurationValidationServiceTest {
                 "LoanCapitalizedIncomeTransactionCreatedBusinessEvent", 
"LoanUndoContractTerminationBusinessEvent",
                 "LoanBuyDownFeeTransactionCreatedBusinessEvent", 
"LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent",
                 "LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent",
-                
"LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent", 
"LoanApprovedAmountChangedBusinessEvent");
+                
"LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent", 
"LoanApprovedAmountChangedBusinessEvent",
+                "SavingsAccountsStayedLockedBusinessEvent");
 
         List<FineractPlatformTenant> tenants = Arrays
                 .asList(new FineractPlatformTenant(1L, "default", "Default 
Tenant", "Europe/Budapest", null));
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/cob/savings/RetrieveSavingsIdService.java
 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/RetrieveSavingsIdService.java
new file mode 100644
index 0000000000..e116178ac6
--- /dev/null
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/RetrieveSavingsIdService.java
@@ -0,0 +1,44 @@
+/**
+ * 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.cob.savings;
+
+import java.time.LocalDate;
+import java.util.List;
+import org.apache.fineract.cob.data.COBIdAndExternalIdAndAccountNo;
+import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
+import org.apache.fineract.cob.data.COBParameter;
+import org.apache.fineract.cob.data.COBPartition;
+import org.springframework.data.repository.query.Param;
+
+public interface RetrieveSavingsIdService {
+
+    List<COBPartition> retrieveSavingsCOBPartitions(Long numberOfDays, 
LocalDate businessDate, boolean isCatchUp, int partitionSize);
+
+    List<COBIdAndLastClosedBusinessDate> 
retrieveSavingsIdsBehindDate(LocalDate businessDate, List<Long> savingsIds);
+
+    List<COBIdAndLastClosedBusinessDate> 
retrieveSavingsIdsBehindDateOrNull(LocalDate businessDate, List<Long> 
savingsIds);
+
+    List<COBIdAndLastClosedBusinessDate> 
retrieveSavingsIdsOldestCobProcessed(LocalDate businessDate);
+
+    List<Long> 
retrieveAllNonClosedSavingsByLastClosedBusinessDateAndMinAndMaxSavingsId(COBParameter
 savingsCOBParameter,
+            boolean isCatchUp);
+
+    List<COBIdAndExternalIdAndAccountNo> 
findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate 
cobBusinessDate);
+
+}
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountLock.java
 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountLock.java
new file mode 100644
index 0000000000..c03082eb5f
--- /dev/null
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountLock.java
@@ -0,0 +1,80 @@
+/**
+ * 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.cob.savings;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import jakarta.persistence.Version;
+import java.time.LocalDate;
+import java.time.OffsetDateTime;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+
+@Entity
+@Table(name = "m_savings_account_locks")
+@NoArgsConstructor
+@Getter
+public class SavingsAccountLock {
+
+    @Id
+    @Column(name = "savings_id", nullable = false)
+    private Long savingsId;
+
+    @Version
+    @Column(name = "version")
+    private Long version;
+
+    @Enumerated(EnumType.STRING)
+    @Column(name = "lock_owner", nullable = false)
+    private SavingsLockOwner lockOwner;
+
+    @Column(name = "lock_placed_on", nullable = false)
+    private OffsetDateTime lockPlacedOn;
+
+    @Column(name = "error")
+    private String error;
+
+    @Column(name = "stacktrace")
+    private String stacktrace;
+
+    @Column(name = "lock_placed_on_cob_business_date")
+    private LocalDate lockPlacedOnCobBusinessDate;
+
+    public SavingsAccountLock(Long savingsId, SavingsLockOwner lockOwner, 
LocalDate lockPlacedOnCobBusinessDate) {
+        this.savingsId = savingsId;
+        this.lockOwner = lockOwner;
+        this.lockPlacedOn = DateUtils.getAuditOffsetDateTime();
+        this.lockPlacedOnCobBusinessDate = lockPlacedOnCobBusinessDate;
+    }
+
+    public void setError(String errorMessage, String stacktrace) {
+        this.error = errorMessage;
+        this.stacktrace = stacktrace;
+    }
+
+    public void setNewLockOwner(SavingsLockOwner newLockOwner) {
+        this.lockOwner = newLockOwner;
+        this.lockPlacedOn = DateUtils.getAuditOffsetDateTime();
+    }
+}
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountLockRepository.java
 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountLockRepository.java
new file mode 100644
index 0000000000..0371cbe353
--- /dev/null
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountLockRepository.java
@@ -0,0 +1,50 @@
+/**
+ * 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.cob.savings;
+
+import java.util.List;
+import java.util.Optional;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+
+public interface SavingsAccountLockRepository
+        extends JpaRepository<SavingsAccountLock, Long>, 
JpaSpecificationExecutor<SavingsAccountLock> {
+
+    Optional<SavingsAccountLock> findBySavingsIdAndLockOwner(Long savingsId, 
SavingsLockOwner lockOwner);
+
+    void deleteBySavingsIdInAndLockOwner(List<Long> savingsIds, 
SavingsLockOwner lockOwner);
+
+    List<SavingsAccountLock> findAllBySavingsIdIn(List<Long> savingsIds);
+
+    boolean existsBySavingsIdAndLockOwner(Long savingsId, SavingsLockOwner 
lockOwner);
+
+    boolean existsBySavingsIdAndLockOwnerAndErrorIsNotNull(Long savingsId, 
SavingsLockOwner lockOwner);
+
+    @Query("""
+            delete from SavingsAccountLock lck where 
lck.lockPlacedOnCobBusinessDate is not null and lck.error is not null and
+            lck.lockOwner in 
(org.apache.fineract.cob.savings.SavingsLockOwner.SAVINGS_COB_CHUNK_PROCESSING,
+                              
org.apache.fineract.cob.savings.SavingsLockOwner.SAVINGS_INLINE_COB_PROCESSING)
+            """)
+    @Modifying(flushAutomatically = true)
+    void removeLockByOwner();
+
+    List<SavingsAccountLock> findAllBySavingsIdInAndLockOwner(List<Long> 
savingsIds, SavingsLockOwner lockOwner);
+}
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountsStayedLockedBusinessEvent.java
 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountsStayedLockedBusinessEvent.java
new file mode 100644
index 0000000000..4dfd7d13c6
--- /dev/null
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountsStayedLockedBusinessEvent.java
@@ -0,0 +1,46 @@
+/**
+ * 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.cob.savings;
+
+import 
org.apache.fineract.infrastructure.event.business.domain.AbstractBusinessEvent;
+
+public class SavingsAccountsStayedLockedBusinessEvent extends 
AbstractBusinessEvent<SavingsAccountsStayedLockedData> {
+
+    private static final String CATEGORY = "Savings COB";
+    private static final String TYPE = 
"SavingsAccountsStayedLockedBusinessEvent";
+
+    public 
SavingsAccountsStayedLockedBusinessEvent(SavingsAccountsStayedLockedData value) 
{
+        super(value);
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+
+    @Override
+    public String getCategory() {
+        return CATEGORY;
+    }
+
+    @Override
+    public Long getAggregateRootId() {
+        return null;
+    }
+}
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountsStayedLockedData.java
 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountsStayedLockedData.java
new file mode 100644
index 0000000000..708173d273
--- /dev/null
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountsStayedLockedData.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.cob.savings;
+
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.fineract.cob.data.COBIdAndExternalIdAndAccountNo;
+
+@Getter
+@AllArgsConstructor
+public class SavingsAccountsStayedLockedData {
+
+    private List<COBIdAndExternalIdAndAccountNo> savingsAccounts;
+}
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsCOBBusinessStep.java
 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsCOBBusinessStep.java
new file mode 100644
index 0000000000..fe925e8fd1
--- /dev/null
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsCOBBusinessStep.java
@@ -0,0 +1,26 @@
+/**
+ * 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.cob.savings;
+
+import org.apache.fineract.cob.COBBusinessStep;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+
+public interface SavingsCOBBusinessStep extends 
COBBusinessStep<SavingsAccount> {
+
+}
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsCOBConstant.java
 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsCOBConstant.java
new file mode 100644
index 0000000000..daee3a9e69
--- /dev/null
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsCOBConstant.java
@@ -0,0 +1,40 @@
+/**
+ * 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.cob.savings;
+
+import org.apache.fineract.cob.COBConstant;
+
+public final class SavingsCOBConstant extends COBConstant {
+
+    public static final String JOB_NAME = "SAVINGS_COB";
+    public static final String JOB_HUMAN_READABLE_NAME = "Savings COB";
+    public static final String SAVINGS_COB_JOB_NAME = 
"SAVINGS_CLOSE_OF_BUSINESS";
+    public static final String SAVINGS_COB_PARAMETER = "savingsCobParameter";
+    public static final String SAVINGS_COB_WORKER_STEP = 
"savingsCOBWorkerStep";
+
+    public static final String INLINE_SAVINGS_COB_JOB_NAME = 
"INLINE_SAVINGS_COB";
+    public static final String SAVINGS_IDS_PARAMETER_NAME = "SavingsIds";
+
+    public static final String SAVINGS_COB_PARTITIONER_STEP = "Savings COB 
partition - Step";
+    public static final String PARTITION_KEY = "partition";
+
+    private SavingsCOBConstant() {
+
+    }
+}
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsLockCannotBeAppliedException.java
 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsLockCannotBeAppliedException.java
new file mode 100644
index 0000000000..926df51127
--- /dev/null
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsLockCannotBeAppliedException.java
@@ -0,0 +1,26 @@
+/**
+ * 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.cob.savings;
+
+public class SavingsLockCannotBeAppliedException extends Exception {
+
+    public SavingsLockCannotBeAppliedException(String message, Throwable 
cause) {
+        super(message, cause);
+    }
+}
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsLockOwner.java
 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsLockOwner.java
new file mode 100644
index 0000000000..7b41c4dadd
--- /dev/null
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsLockOwner.java
@@ -0,0 +1,24 @@
+/**
+ * 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.cob.savings;
+
+public enum SavingsLockOwner {
+    SAVINGS_COB_CHUNK_PROCESSING, //
+    SAVINGS_INLINE_COB_PROCESSING; //
+}
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsLockingService.java
 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsLockingService.java
new file mode 100644
index 0000000000..2ec25e701f
--- /dev/null
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsLockingService.java
@@ -0,0 +1,36 @@
+/**
+ * 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.cob.savings;
+
+import java.util.List;
+
+public interface SavingsLockingService {
+
+    void upgradeLock(List<Long> accountsToLock, SavingsLockOwner lockOwner);
+
+    void deleteBySavingsIdInAndLockOwner(List<Long> savingsIds, 
SavingsLockOwner lockOwner);
+
+    List<SavingsAccountLock> findAllBySavingsIdIn(List<Long> savingsIds);
+
+    SavingsAccountLock findBySavingsIdAndLockOwner(Long savingsId, 
SavingsLockOwner lockOwner);
+
+    List<SavingsAccountLock> findAllBySavingsIdInAndLockOwner(List<Long> 
savingsIds, SavingsLockOwner lockOwner);
+
+    void applyLock(List<Long> savingsIds, SavingsLockOwner lockOwner);
+}
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsReadException.java
 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsReadException.java
new file mode 100644
index 0000000000..689a1b4b53
--- /dev/null
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsReadException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.cob.savings;
+
+public class SavingsReadException extends Exception {
+
+    private final Long id;
+
+    public SavingsReadException(Long id, Throwable t) {
+        super(String.format("Savings account is in already locked state! 
savingsId: %d", id), t);
+        this.id = id;
+    }
+
+    public Long getId() {
+        return id;
+    }
+}
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 3d1b500c68..7bad316ec9 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
@@ -341,6 +341,9 @@ public class SavingsAccount extends 
AbstractAuditableWithUTCDateTimeCustom<Long>
     @Column(name = "accrued_till_date")
     private LocalDate accruedTillDate;
 
+    @Column(name = "last_closed_business_date")
+    private LocalDate lastClosedBusinessDate;
+
     @Column(name = "total_savings_amount_on_hold", scale = 6, precision = 19, 
nullable = true)
     private BigDecimal savingsOnHoldAmount;
     @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval 
= true, fetch = FetchType.LAZY)
@@ -3855,6 +3858,14 @@ public class SavingsAccount extends 
AbstractAuditableWithUTCDateTimeCustom<Long>
         this.accruedTillDate = accruedTillDate;
     }
 
+    public LocalDate getLastClosedBusinessDate() {
+        return this.lastClosedBusinessDate;
+    }
+
+    public void setLastClosedBusinessDate(LocalDate lastClosedBusinessDate) {
+        this.lastClosedBusinessDate = lastClosedBusinessDate;
+    }
+
     public List<SavingsAccountTransactionDetailsForPostingPeriod> 
toSavingsAccountTransactionDetailsForPostingPeriodList(
             List<SavingsAccountTransaction> transactions) {
         return transactions.stream()
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java
 
b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java
index 37dd7b949d..07ca0d4c63 100644
--- 
a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java
@@ -20,7 +20,9 @@ package org.apache.fineract.portfolio.savings.domain;
 
 import jakarta.persistence.LockModeType;
 import java.time.LocalDate;
+import java.util.Collection;
 import java.util.List;
+import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.portfolio.savings.data.SavingsAccrualData;
 import org.springframework.data.domain.Page;
@@ -97,4 +99,52 @@ public interface SavingsAccountRepository extends 
JpaRepository<SavingsAccount,
 
     @Query("SELECT sa.id FROM SavingsAccount sa WHERE sa.status = :status")
     List<Long> findSavingsAccountIdsByStatusId(Integer status);
+
+    // COB related queries
+    @Query("""
+            SELECT sa.id FROM SavingsAccount sa
+            WHERE sa.id BETWEEN :minSavingsId AND :maxSavingsId
+            AND sa.status IN :savingsStatuses
+            AND (:cobBusinessDate = sa.lastClosedBusinessDate OR 
sa.lastClosedBusinessDate IS NULL)
+            """)
+    List<Long> 
findAllSavingsByLastClosedBusinessDateAndMinAndMaxSavingsIdAndStatuses(@Param("minSavingsId")
 Long minSavingsId,
+            @Param("maxSavingsId") Long maxSavingsId, 
@Param("cobBusinessDate") LocalDate cobBusinessDate,
+            @Param("savingsStatuses") Collection<Integer> savingsStatuses);
+
+    @Query("""
+            SELECT sa.id FROM SavingsAccount sa
+            WHERE sa.id BETWEEN :minSavingsId AND :maxSavingsId
+            AND sa.status IN :savingsStatuses
+            AND sa.lastClosedBusinessDate = :cobBusinessDate
+            """)
+    List<Long> 
findAllSavingsByLastClosedBusinessDateNotNullAndMinAndMaxSavingsIdAndStatuses(@Param("minSavingsId")
 Long minSavingsId,
+            @Param("maxSavingsId") Long maxSavingsId, 
@Param("cobBusinessDate") LocalDate cobBusinessDate,
+            @Param("savingsStatuses") Collection<Integer> savingsStatuses);
+
+    @Query("""
+            SELECT sa.id, sa.lastClosedBusinessDate
+            FROM SavingsAccount sa
+            WHERE sa.id IN :savingsIds
+            AND (sa.lastClosedBusinessDate < :businessDate OR 
sa.lastClosedBusinessDate IS NULL)
+            """)
+    List<COBIdAndLastClosedBusinessDate> 
findAllSavingsIdsBehindDateOrNull(@Param("businessDate") LocalDate businessDate,
+            @Param("savingsIds") List<Long> savingsIds);
+
+    @Query("""
+            SELECT sa.id, sa.lastClosedBusinessDate
+            FROM SavingsAccount sa
+            WHERE sa.id IN :savingsIds
+            AND sa.lastClosedBusinessDate < :businessDate
+            """)
+    List<COBIdAndLastClosedBusinessDate> 
findAllSavingsIdsBehindDate(@Param("businessDate") LocalDate businessDate,
+            @Param("savingsIds") List<Long> savingsIds);
+
+    @Query("""
+            SELECT sa.id, sa.lastClosedBusinessDate
+            FROM SavingsAccount sa
+            WHERE sa.status IN (100, 200, 300, 303, 304)
+            AND sa.lastClosedBusinessDate IS NOT NULL
+            ORDER BY sa.lastClosedBusinessDate ASC
+            """)
+    List<COBIdAndLastClosedBusinessDate> findAllSavingsIdsOldestCobProcessed();
 }
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java
 
b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java
index 0fca0ace8d..d5e0332fab 100644
--- 
a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java
@@ -20,15 +20,14 @@ package org.apache.fineract.portfolio.savings.domain;
 
 import java.time.LocalDate;
 import java.util.List;
+import lombok.RequiredArgsConstructor;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.portfolio.savings.DepositAccountType;
 import org.apache.fineract.portfolio.savings.data.SavingsAccrualData;
 import 
org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.repository.query.Param;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -44,19 +43,11 @@ import 
org.springframework.transaction.annotation.Transactional;
  * </p>
  */
 @Service
+@RequiredArgsConstructor
 public class SavingsAccountRepositoryWrapper {
 
     private final SavingsAccountRepository repository;
     private final SavingsAccountTransactionRepository 
savingsAccountTransactionRepository;
-    private final JdbcTemplate jdbcTemplate;
-
-    @Autowired
-    public SavingsAccountRepositoryWrapper(final SavingsAccountRepository 
repository,
-            final SavingsAccountTransactionRepository 
savingsAccountTransactionRepository, final JdbcTemplate jdbcTemplate) {
-        this.repository = repository;
-        this.savingsAccountTransactionRepository = 
savingsAccountTransactionRepository;
-        this.jdbcTemplate = jdbcTemplate;
-    }
 
     @Transactional(readOnly = true)
     public SavingsAccount findOneWithNotFoundDetection(final Long savingsId) {
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 e18006b609..b83d996b0a 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
@@ -26,4 +26,5 @@
   <include file="parts/2001_add_savings_accrual_job.xml" 
relativeToChangelogFile="true" />
   <include file="parts/2002_add_savings_accrual_permission.xml" 
relativeToChangelogFile="true" />
   <include file="parts/2003_add_accrued_till_date_to_savings_account.xml" 
relativeToChangelogFile="true" />
+  <include file="parts/2004_add_savings_account_cob_infrastructure.xml" 
relativeToChangelogFile="true" />
 </databaseChangeLog>
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/parts/2004_add_savings_account_cob_infrastructure.xml
similarity index 50%
copy from 
fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/module-changelog-master.xml
copy to 
fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2004_add_savings_account_cob_infrastructure.xml
index e18006b609..99167814ac 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/parts/2004_add_savings_account_cob_infrastructure.xml
@@ -20,10 +20,22 @@
 
 -->
 <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
-  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
-  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
-  <!-- Sequence is starting from 2000 to make it easier to move existing 
liquibase changesets here -->
-  <include file="parts/2001_add_savings_accrual_job.xml" 
relativeToChangelogFile="true" />
-  <include file="parts/2002_add_savings_accrual_permission.xml" 
relativeToChangelogFile="true" />
-  <include file="parts/2003_add_accrued_till_date_to_savings_account.xml" 
relativeToChangelogFile="true" />
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd";>
+    <changeSet author="fineract" id="1">
+        <insert tableName="m_external_event_configuration">
+            <column name="type" 
value="SavingsAccountsStayedLockedBusinessEvent"/>
+            <column name="enabled" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+    <changeSet author="fineract" id="2">
+        <addColumn tableName="m_savings_account">
+            <column name="last_closed_business_date" type="DATE"/>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="3">
+        <createIndex indexName="FK_savings_account_last_closed_business_date" 
tableName="m_savings_account">
+            <column name="last_closed_business_date"/>
+        </createIndex>
+    </changeSet>
 </databaseChangeLog>
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
index 86651e3376..cdd1cdf8ae 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
@@ -662,6 +662,11 @@ public class ExternalEventConfigurationHelper {
         loanApprovedAmountChangedBusinessEvent.put("enabled", false);
         defaults.add(loanApprovedAmountChangedBusinessEvent);
 
+        Map<String, Object> savingsAccountsStayedLockedBusinessEvent = new 
HashMap<>();
+        savingsAccountsStayedLockedBusinessEvent.put("type", 
"SavingsAccountsStayedLockedBusinessEvent");
+        savingsAccountsStayedLockedBusinessEvent.put("enabled", false);
+        defaults.add(savingsAccountsStayedLockedBusinessEvent);
+
         return defaults;
     }
 

Reply via email to