2929import java .util .ArrayList ;
3030import java .util .Collection ;
3131import java .util .HashMap ;
32+ import java .util .HashSet ;
3233import java .util .List ;
34+ import java .util .Set ;
3335import java .util .UUID ;
3436import lombok .RequiredArgsConstructor ;
3537import lombok .Setter ;
4345import org .apache .fineract .portfolio .savings .data .SavingsAccountSummaryData ;
4446import org .apache .fineract .portfolio .savings .data .SavingsAccountTransactionData ;
4547import org .springframework .dao .DataAccessException ;
48+ import org .springframework .dao .OptimisticLockingFailureException ;
4649import org .springframework .jdbc .core .JdbcTemplate ;
4750import org .springframework .transaction .annotation .Isolation ;
4851import org .springframework .transaction .annotation .Transactional ;
@@ -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,7 @@ private void batchUpdate(final List<SavingsAccountData> savingsAccountDataList)
177175 for (SavingsAccountData savingsAccountData : savingsAccountDataList ) {
178176 OffsetDateTime auditTime = DateUtils .getAuditOffsetDateTime ();
179177 SavingsAccountSummaryData savingsAccountSummaryData = savingsAccountData .getSummary ();
178+
180179 paramsForSavingsSummary .add (new Object [] { savingsAccountSummaryData .getTotalDeposits (),
181180 savingsAccountSummaryData .getTotalWithdrawals (), savingsAccountSummaryData .getTotalInterestEarned (),
182181 savingsAccountSummaryData .getTotalInterestPosted (), savingsAccountSummaryData .getTotalWithdrawalFees (),
@@ -186,7 +185,8 @@ private void batchUpdate(final List<SavingsAccountData> savingsAccountDataList)
186185 savingsAccountSummaryData .getLastInterestCalculationDate (),
187186 savingsAccountSummaryData .getInterestPostedTillDate () != null ? savingsAccountSummaryData .getInterestPostedTillDate ()
188187 : savingsAccountSummaryData .getLastInterestCalculationDate (),
189- auditTime , userId , savingsAccountData .getId () });
188+ auditTime , userId , savingsAccountData .getId (), savingsAccountData .getVersion () });
189+
190190 List <SavingsAccountTransactionData > savingsAccountTransactionDataList = savingsAccountData .getSavingsAccountTransactionData ();
191191 for (SavingsAccountTransactionData savingsAccountTransactionData : savingsAccountTransactionDataList ) {
192192 if (savingsAccountTransactionData .getId () == null && !MathUtil .isZero (savingsAccountTransactionData .getAmount ())) {
@@ -213,8 +213,24 @@ private void batchUpdate(final List<SavingsAccountData> savingsAccountDataList)
213213 savingsAccountData .setUpdatedTransactions (savingsAccountTransactionDataList );
214214 }
215215
216- if (transRefNo .size () > 0 ) {
217- this .jdbcTemplate .batchUpdate (queryForSavingsUpdate , paramsForSavingsSummary );
216+ if (!transRefNo .isEmpty ()) {
217+ int [] updateCounts = this .jdbcTemplate .batchUpdate (queryForSavingsUpdate , paramsForSavingsSummary );
218+
219+ Set <Long > skippedAccountIds = new HashSet <>();
220+ for (int i = 0 ; i < updateCounts .length ; i ++) {
221+ if (updateCounts [i ] == 0 ) {
222+ Long accountId = savingsAccountDataList .get (i ).getId ();
223+ skippedAccountIds .add (accountId );
224+ log .warn ("Optimistic lock failure for savings account id={} — concurrent modification detected."
225+ + " Rolling back. Will retry on next run." , accountId );
226+ }
227+ }
228+
229+ if (!skippedAccountIds .isEmpty ()) {
230+ throw new OptimisticLockingFailureException ("Optimistic lock failure for savings account(s): " + skippedAccountIds
231+ + ". Rolling back entire batch. All accounts will be retried on next scheduler run." );
232+ }
233+
218234 this .jdbcTemplate .batchUpdate (queryForTransactionInsertion , paramsForTransactionInsertion );
219235 this .jdbcTemplate .batchUpdate (queryForTransactionUpdate , paramsForTransactionUpdate );
220236 log .debug ("`Total No Of Interest Posting:` {}" , transRefNo .size ());
@@ -230,7 +246,6 @@ private void batchUpdate(final List<SavingsAccountData> savingsAccountDataList)
230246 }
231247 batchUpdateJournalEntries (savingsAccountDataList , savingsAccountTransactionMap );
232248 }
233-
234249 }
235250
236251 private String batchQueryForTransactionInsertion () {
@@ -245,20 +260,12 @@ private String batchQueryForSavingsSummaryUpdate() {
245260 return "update m_savings_account set total_deposits_derived=?, total_withdrawals_derived=?, total_interest_earned_derived=?, total_interest_posted_derived=?, total_withdrawal_fees_derived=?, "
246261 + "total_fees_charge_derived=?, total_penalty_charge_derived=?, total_annual_fees_derived=?, account_balance_derived=?, total_overdraft_interest_derived=?, total_withhold_tax_derived=?, "
247262 + "last_interest_calculation_date=?, interest_posted_till_date=?, " + LAST_MODIFIED_DATE_DB_FIELD + " = ?, "
248- + LAST_MODIFIED_BY_DB_FIELD + " = ? WHERE id=? " ;
263+ + LAST_MODIFIED_BY_DB_FIELD + " = ?, version = version + 1 WHERE id=? AND version=? " ;
249264 }
250265
251266 private String batchQueryForTransactionsUpdate () {
252267 return "UPDATE m_savings_account_transaction "
253268 + "SET is_reversed=?, amount=?, overdraft_amount_derived=?, balance_end_date_derived=?, balance_number_of_days_derived=?, running_balance_derived=?, cumulative_balance_derived=?, is_reversal=?, "
254269 + LAST_MODIFIED_DATE_DB_FIELD + " = ?, " + LAST_MODIFIED_BY_DB_FIELD + " = ? " + "WHERE id=?" ;
255270 }
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- }
264271}
0 commit comments