2828import java .time .OffsetDateTime ;
2929import java .util .ArrayList ;
3030import java .util .Collection ;
31+ import java .util .ConcurrentModificationException ;
3132import java .util .HashMap ;
33+ import java .util .HashSet ;
3234import java .util .List ;
35+ import java .util .Set ;
3336import java .util .UUID ;
3437import lombok .RequiredArgsConstructor ;
3538import lombok .Setter ;
@@ -67,16 +70,10 @@ public class SavingsSchedularInterestPoster {
6770 public void postInterest () throws JobExecutionException {
6871 if (!savingAccounts .isEmpty ()) {
6972 List <Throwable > errors = new ArrayList <>();
70- LocalDate yesterday = DateUtils .getBusinessLocalDate ().minusDays (1 );
7173 for (SavingsAccountData savingsAccountData : savingAccounts ) {
7274 boolean postInterestAsOn = false ;
7375 LocalDate transactionDate = null ;
7476 try {
75- if (isInterestAlreadyPostedForPeriod (savingsAccountData , yesterday )) {
76- log .debug ("Interest already posted for savings account {} up to date {}, skipping" , savingsAccountData .getId (),
77- savingsAccountData .getSummary ().getInterestPostedTillDate ());
78- continue ;
79- }
8077 SavingsAccountData savingsAccountDataRet = savingsAccountWritePlatformService .postInterest (savingsAccountData ,
8178 postInterestAsOn , transactionDate , backdatedTxnsAllowedTill );
8279 savingsAccountDataList .add (savingsAccountDataRet );
@@ -115,6 +112,7 @@ private void batchUpdateJournalEntries(final List<SavingsAccountData> savingsAcc
115112 for (SavingsAccountTransactionData savingsAccountTransactionData : savingsAccountTransactionDataList ) {
116113 if (savingsAccountTransactionData .getId () == null && !MathUtil .isZero (savingsAccountTransactionData .getAmount ())) {
117114 final String key = savingsAccountTransactionData .getRefNo ();
115+ final Boolean isOverdraft = savingsAccountTransactionData .getIsOverdraft ();
118116 final SavingsAccountTransactionData dataFromFetch = savingsAccountTransactionDataHashMap .get (key );
119117 savingsAccountTransactionData .setId (dataFromFetch .getId ());
120118 if (savingsAccountData .getGlAccountIdForSavingsControl () != 0
@@ -177,6 +175,9 @@ private void batchUpdate(final List<SavingsAccountData> savingsAccountDataList)
177175 for (SavingsAccountData savingsAccountData : savingsAccountDataList ) {
178176 OffsetDateTime auditTime = DateUtils .getAuditOffsetDateTime ();
179177 SavingsAccountSummaryData savingsAccountSummaryData = savingsAccountData .getSummary ();
178+
179+ // CHANGE 3: Added savingsAccountData.getVersion() at the end
180+ // Matches the AND version=? in the SQL WHERE clause
180181 paramsForSavingsSummary .add (new Object [] { savingsAccountSummaryData .getTotalDeposits (),
181182 savingsAccountSummaryData .getTotalWithdrawals (), savingsAccountSummaryData .getTotalInterestEarned (),
182183 savingsAccountSummaryData .getTotalInterestPosted (), savingsAccountSummaryData .getTotalWithdrawalFees (),
@@ -186,7 +187,8 @@ private void batchUpdate(final List<SavingsAccountData> savingsAccountDataList)
186187 savingsAccountSummaryData .getLastInterestCalculationDate (),
187188 savingsAccountSummaryData .getInterestPostedTillDate () != null ? savingsAccountSummaryData .getInterestPostedTillDate ()
188189 : savingsAccountSummaryData .getLastInterestCalculationDate (),
189- auditTime , userId , savingsAccountData .getId () });
190+ auditTime , userId , savingsAccountData .getId (), savingsAccountData .getVersion () }); // ← CHANGE 3
191+
190192 List <SavingsAccountTransactionData > savingsAccountTransactionDataList = savingsAccountData .getSavingsAccountTransactionData ();
191193 for (SavingsAccountTransactionData savingsAccountTransactionData : savingsAccountTransactionDataList ) {
192194 if (savingsAccountTransactionData .getId () == null && !MathUtil .isZero (savingsAccountTransactionData .getAmount ())) {
@@ -213,8 +215,24 @@ private void batchUpdate(final List<SavingsAccountData> savingsAccountDataList)
213215 savingsAccountData .setUpdatedTransactions (savingsAccountTransactionDataList );
214216 }
215217
216- if (transRefNo .size () > 0 ) {
217- this .jdbcTemplate .batchUpdate (queryForSavingsUpdate , paramsForSavingsSummary );
218+ if (!transRefNo .isEmpty ()) {
219+ int [] updateCounts = this .jdbcTemplate .batchUpdate (queryForSavingsUpdate , paramsForSavingsSummary );
220+
221+ Set <Long > skippedAccountIds = new HashSet <>();
222+ for (int i = 0 ; i < updateCounts .length ; i ++) {
223+ if (updateCounts [i ] == 0 ) {
224+ Long accountId = savingsAccountDataList .get (i ).getId ();
225+ skippedAccountIds .add (accountId );
226+ log .warn ("Optimistic lock failure for savings account id={}" + " — concurrent modification detected."
227+ + " Rolling back. Will retry on next run." , accountId );
228+ }
229+ }
230+
231+ if (!skippedAccountIds .isEmpty ()) {
232+ throw new ConcurrentModificationException ("Optimistic lock failure for savings account(s): " + skippedAccountIds
233+ + ". Rolling back entire batch." + " All accounts will be retried on next scheduler run." );
234+ }
235+
218236 this .jdbcTemplate .batchUpdate (queryForTransactionInsertion , paramsForTransactionInsertion );
219237 this .jdbcTemplate .batchUpdate (queryForTransactionUpdate , paramsForTransactionUpdate );
220238 log .debug ("`Total No Of Interest Posting:` {}" , transRefNo .size ());
@@ -230,7 +248,6 @@ private void batchUpdate(final List<SavingsAccountData> savingsAccountDataList)
230248 }
231249 batchUpdateJournalEntries (savingsAccountDataList , savingsAccountTransactionMap );
232250 }
233-
234251 }
235252
236253 private String batchQueryForTransactionInsertion () {
@@ -241,24 +258,19 @@ private String batchQueryForTransactionInsertion() {
241258 + "overdraft_amount_derived, submitted_on_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" ;
242259 }
243260
261+ // CHANGE 2: Added version = version + 1 and AND version=? to WHERE clause
262+ // BEFORE: + LAST_MODIFIED_BY_DB_FIELD + " = ? WHERE id=? ";
263+ // AFTER: + LAST_MODIFIED_BY_DB_FIELD + " = ?, version = version + 1 WHERE id=? AND version=?";
244264 private String batchQueryForSavingsSummaryUpdate () {
245265 return "update m_savings_account set total_deposits_derived=?, total_withdrawals_derived=?, total_interest_earned_derived=?, total_interest_posted_derived=?, total_withdrawal_fees_derived=?, "
246266 + "total_fees_charge_derived=?, total_penalty_charge_derived=?, total_annual_fees_derived=?, account_balance_derived=?, total_overdraft_interest_derived=?, total_withhold_tax_derived=?, "
247267 + "last_interest_calculation_date=?, interest_posted_till_date=?, " + LAST_MODIFIED_DATE_DB_FIELD + " = ?, "
248- + LAST_MODIFIED_BY_DB_FIELD + " = ? WHERE id=? " ;
268+ + LAST_MODIFIED_BY_DB_FIELD + " = ?, version = version + 1 WHERE id=? AND version=? " ;
249269 }
250270
251271 private String batchQueryForTransactionsUpdate () {
252272 return "UPDATE m_savings_account_transaction "
253273 + "SET is_reversed=?, amount=?, overdraft_amount_derived=?, balance_end_date_derived=?, balance_number_of_days_derived=?, running_balance_derived=?, cumulative_balance_derived=?, is_reversal=?, "
254274 + LAST_MODIFIED_DATE_DB_FIELD + " = ?, " + LAST_MODIFIED_BY_DB_FIELD + " = ? " + "WHERE id=?" ;
255275 }
256-
257- private boolean isInterestAlreadyPostedForPeriod (SavingsAccountData savingsAccountData , LocalDate yesterday ) {
258- LocalDate interestPostedTillDate = savingsAccountData .getSummary ().getInterestPostedTillDate ();
259- if (interestPostedTillDate == null ) {
260- return false ;
261- }
262- return !interestPostedTillDate .isBefore (yesterday );
263- }
264276}
0 commit comments