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