From 8d560a1f7c0f23c1fd5099546754973528cc4c68 Mon Sep 17 00:00:00 2001
From: Jose Alberto Hernandez
Date: Tue, 27 Jan 2026 10:00:45 -0500
Subject: [PATCH] FINERACT-2332: SavingCOB infrastructure
---
.../v1/SavingsAccountStayedLockedDataV1.avsc | 27 ++++
.../v1/SavingsAccountsStayedLockedDataV1.avsc | 12 ++
.../savings/RetrieveSavingsIdServiceImpl.java | 117 ++++++++++++++++++
...SavingsAccountsStayedLockedDataMapper.java | 34 +++++
...tsStayedLockedBusinessEventSerializer.java | 54 ++++++++
...entConfigurationValidationServiceTest.java | 7 +-
.../cob/savings/RetrieveSavingsIdService.java | 44 +++++++
.../cob/savings/SavingsAccountLock.java | 80 ++++++++++++
.../savings/SavingsAccountLockRepository.java | 50 ++++++++
...ingsAccountsStayedLockedBusinessEvent.java | 46 +++++++
.../SavingsAccountsStayedLockedData.java | 31 +++++
.../cob/savings/SavingsCOBBusinessStep.java | 26 ++++
.../cob/savings/SavingsCOBConstant.java | 40 ++++++
.../SavingsLockCannotBeAppliedException.java | 26 ++++
.../cob/savings/SavingsLockOwner.java | 24 ++++
.../cob/savings/SavingsLockingService.java | 36 ++++++
.../cob/savings/SavingsReadException.java | 33 +++++
.../savings/domain/SavingsAccount.java | 11 ++
.../domain/SavingsAccountRepository.java | 50 ++++++++
.../SavingsAccountRepositoryWrapper.java | 13 +-
.../savings/parts/module-changelog-master.xml | 1 +
...add_savings_account_cob_infrastructure.xml | 41 ++++++
.../ExternalEventConfigurationHelper.java | 5 +
23 files changed, 795 insertions(+), 13 deletions(-)
create mode 100644 fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountStayedLockedDataV1.avsc
create mode 100644 fineract-avro-schemas/src/main/avro/savings/v1/SavingsAccountsStayedLockedDataV1.avsc
create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/savings/RetrieveSavingsIdServiceImpl.java
create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/savings/SavingsAccountsStayedLockedDataMapper.java
create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/savings/SavingsAccountsStayedLockedBusinessEventSerializer.java
create mode 100644 fineract-savings/src/main/java/org/apache/fineract/cob/savings/RetrieveSavingsIdService.java
create mode 100644 fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountLock.java
create mode 100644 fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountLockRepository.java
create mode 100644 fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountsStayedLockedBusinessEvent.java
create mode 100644 fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsAccountsStayedLockedData.java
create mode 100644 fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsCOBBusinessStep.java
create mode 100644 fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsCOBConstant.java
create mode 100644 fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsLockCannotBeAppliedException.java
create mode 100644 fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsLockOwner.java
create mode 100644 fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsLockingService.java
create mode 100644 fineract-savings/src/main/java/org/apache/fineract/cob/savings/SavingsReadException.java
create mode 100644 fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2004_add_savings_account_cob_infrastructure.xml
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 000000000..bcd6105cb
--- /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 000000000..0d7efc1e5
--- /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 000000000..ba8170138
--- /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 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 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 retrieveSavingsIdsBehindDate(LocalDate businessDate, List savingsIds) {
+ return savingsAccountRepository.findAllSavingsIdsBehindDate(businessDate, savingsIds);
+ }
+
+ @Override
+ public List retrieveSavingsIdsBehindDateOrNull(LocalDate businessDate, List savingsIds) {
+ return savingsAccountRepository.findAllSavingsIdsBehindDateOrNull(businessDate, savingsIds);
+ }
+
+ @Override
+ public List retrieveSavingsIdsOldestCobProcessed(LocalDate businessDate) {
+ return savingsAccountRepository.findAllSavingsIdsOldestCobProcessed();
+ }
+
+ @Override
+ public List 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 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 000000000..fa8388f86
--- /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 000000000..2908b2253
--- /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 boolean canSerialize(BusinessEvent event) {
+ return event instanceof SavingsAccountsStayedLockedBusinessEvent;
+ }
+
+ @Override
+ public ByteBufferSerializable toAvroDTO(BusinessEvent 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 697534d2d..b80cd8b4b 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 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 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 000000000..e116178ac
--- /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 retrieveSavingsCOBPartitions(Long numberOfDays, LocalDate businessDate, boolean isCatchUp, int partitionSize);
+
+ List retrieveSavingsIdsBehindDate(LocalDate businessDate, List savingsIds);
+
+ List retrieveSavingsIdsBehindDateOrNull(LocalDate businessDate, List savingsIds);
+
+ List retrieveSavingsIdsOldestCobProcessed(LocalDate businessDate);
+
+ List retrieveAllNonClosedSavingsByLastClosedBusinessDateAndMinAndMaxSavingsId(COBParameter savingsCOBParameter,
+ boolean isCatchUp);
+
+ List 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 000000000..c03082eb5
--- /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 000000000..0371cbe35
--- /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, JpaSpecificationExecutor {
+
+ Optional findBySavingsIdAndLockOwner(Long savingsId, SavingsLockOwner lockOwner);
+
+ void deleteBySavingsIdInAndLockOwner(List savingsIds, SavingsLockOwner lockOwner);
+
+ List findAllBySavingsIdIn(List 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 findAllBySavingsIdInAndLockOwner(List 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 000000000..4dfd7d13c
--- /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 {
+
+ 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 000000000..708173d27
--- /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 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 000000000..fe925e8fd
--- /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 {
+
+}
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 000000000..daee3a9e6
--- /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 000000000..926df5112
--- /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 000000000..7b41c4dad
--- /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 000000000..2ec25e701
--- /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 accountsToLock, SavingsLockOwner lockOwner);
+
+ void deleteBySavingsIdInAndLockOwner(List savingsIds, SavingsLockOwner lockOwner);
+
+ List findAllBySavingsIdIn(List savingsIds);
+
+ SavingsAccountLock findBySavingsIdAndLockOwner(Long savingsId, SavingsLockOwner lockOwner);
+
+ List findAllBySavingsIdInAndLockOwner(List savingsIds, SavingsLockOwner lockOwner);
+
+ void applyLock(List 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 000000000..689a1b4b5
--- /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 3d1b500c6..7bad316ec 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
@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
this.accruedTillDate = accruedTillDate;
}
+ public LocalDate getLastClosedBusinessDate() {
+ return this.lastClosedBusinessDate;
+ }
+
+ public void setLastClosedBusinessDate(LocalDate lastClosedBusinessDate) {
+ this.lastClosedBusinessDate = lastClosedBusinessDate;
+ }
+
public List toSavingsAccountTransactionDetailsForPostingPeriodList(
List 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 37dd7b949..07ca0d4c6 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 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 findAllSavingsByLastClosedBusinessDateAndMinAndMaxSavingsIdAndStatuses(@Param("minSavingsId") Long minSavingsId,
+ @Param("maxSavingsId") Long maxSavingsId, @Param("cobBusinessDate") LocalDate cobBusinessDate,
+ @Param("savingsStatuses") Collection 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 findAllSavingsByLastClosedBusinessDateNotNullAndMinAndMaxSavingsIdAndStatuses(@Param("minSavingsId") Long minSavingsId,
+ @Param("maxSavingsId") Long maxSavingsId, @Param("cobBusinessDate") LocalDate cobBusinessDate,
+ @Param("savingsStatuses") Collection 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 findAllSavingsIdsBehindDateOrNull(@Param("businessDate") LocalDate businessDate,
+ @Param("savingsIds") List savingsIds);
+
+ @Query("""
+ SELECT sa.id, sa.lastClosedBusinessDate
+ FROM SavingsAccount sa
+ WHERE sa.id IN :savingsIds
+ AND sa.lastClosedBusinessDate < :businessDate
+ """)
+ List findAllSavingsIdsBehindDate(@Param("businessDate") LocalDate businessDate,
+ @Param("savingsIds") List 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 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 0fca0ace8..d5e0332fa 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;
*
*/
@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 e18006b60..b83d996b0 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 @@
+
diff --git a/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2004_add_savings_account_cob_infrastructure.xml b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2004_add_savings_account_cob_infrastructure.xml
new file mode 100644
index 000000000..99167814a
--- /dev/null
+++ b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2004_add_savings_account_cob_infrastructure.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 86651e337..cdd1cdf8a 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 savingsAccountsStayedLockedBusinessEvent = new HashMap<>();
+ savingsAccountsStayedLockedBusinessEvent.put("type", "SavingsAccountsStayedLockedBusinessEvent");
+ savingsAccountsStayedLockedBusinessEvent.put("enabled", false);
+ defaults.add(savingsAccountsStayedLockedBusinessEvent);
+
return defaults;
}