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;
}