Skip to content

Commit 1b5b3d5

Browse files
authored
Merge pull request #5733
FINERACT-2577: Allow adjust (modify) of Fixed Deposit transactions
2 parents 7216df1 + d4339e2 commit 1b5b3d5

5 files changed

Lines changed: 225 additions & 8 deletions

File tree

fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2629,6 +2629,17 @@ public CommandWrapperBuilder undoFixedDepositAccountTransaction(final Long accou
26292629
return this;
26302630
}
26312631

2632+
public CommandWrapperBuilder adjustFixedDepositAccountTransaction(final Long accountId, final Long transactionId) {
2633+
this.actionName = "ADJUSTTRANSACTION";
2634+
this.entityName = "FIXEDDEPOSITACCOUNT";
2635+
this.savingsId = accountId;
2636+
this.entityId = accountId;
2637+
this.subentityId = transactionId;
2638+
this.transactionId = transactionId.toString();
2639+
this.href = "/fixeddepositaccounts/" + accountId + "/transactions/" + transactionId + "?command=modify";
2640+
return this;
2641+
}
2642+
26322643
public CommandWrapperBuilder fixedDepositAccountActivation(final Long accountId) {
26332644
this.actionName = ACTION_ACTIVATE;
26342645
this.entityName = ENTITY_FIXEDDEPOSITACCOUNT;

fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ public String adjustTransaction(@PathParam("fixedDepositAccountId") final Long f
187187
final CommandWrapper commandRequest = builder.undoFixedDepositAccountTransaction(fixedDepositAccountId, transactionId).build();
188188
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
189189
} else if (is(commandParam, DepositsApiConstants.COMMAND_ADJUST_TRANSACTION)) {
190-
final CommandWrapper commandRequest = builder.adjustSavingsAccountTransaction(fixedDepositAccountId, transactionId).build();
190+
final CommandWrapper commandRequest = builder.adjustFixedDepositAccountTransaction(fixedDepositAccountId, transactionId)
191+
.build();
191192
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
192193
}
193194

fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@
105105
import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
106106
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
107107
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository;
108-
import org.apache.fineract.portfolio.savings.exception.DepositAccountTransactionNotAllowedException;
109108
import org.apache.fineract.portfolio.savings.exception.SavingsAccountTransactionNotFoundException;
110109
import org.apache.fineract.portfolio.savings.exception.TransactionUpdateNotAllowedException;
111110
import org.apache.fineract.useradministration.domain.AppUser;
@@ -693,10 +692,100 @@ public CommandProcessingResult undoRDTransaction(final Long savingsId, final Lon
693692
}
694693

695694
@Override
696-
public CommandProcessingResult adjustFDTransaction(final Long savingsId, @SuppressWarnings("unused") final Long transactionId,
697-
@SuppressWarnings("unused") final JsonCommand command) {
695+
public CommandProcessingResult adjustFDTransaction(final Long savingsId, final Long transactionId, final JsonCommand command) {
696+
context.authenticatedUser();
697+
698+
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
699+
.isSavingsInterestPostingAtCurrentPeriodEnd();
700+
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
701+
final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate();
702+
this.depositAccountTransactionDataValidator.validate(command, DepositAccountType.FIXED_DEPOSIT);
703+
704+
final SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountTransactionRepository
705+
.findOneByIdAndSavingsAccountId(transactionId, savingsId);
706+
if (savingsAccountTransaction == null) {
707+
throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId);
708+
}
709+
710+
if ((!savingsAccountTransaction.isDeposit() && !savingsAccountTransaction.isWithdrawal())
711+
|| savingsAccountTransaction.isReversed()) {
712+
throw new TransactionUpdateNotAllowedException(savingsId, transactionId);
713+
}
714+
715+
if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.SAVINGS)) {
716+
throw new PlatformServiceUnavailableException("error.msg.fixed.deposit.account.transfer.transaction.update.not.allowed",
717+
"Fixed deposit account transaction:" + transactionId + " update not allowed as it involves in account transfer",
718+
transactionId);
719+
}
720+
721+
final LocalDate today = DateUtils.getBusinessLocalDate();
722+
final MathContext mc = MathContext.DECIMAL64;
723+
724+
final FixedDepositAccount account = (FixedDepositAccount) this.depositAccountAssembler.assembleFrom(savingsId,
725+
DepositAccountType.FIXED_DEPOSIT);
726+
if (!account.isTransactionsAllowed()) {
727+
throwValidationForActiveStatus(SavingsApiConstants.adjustTransactionAction);
728+
}
729+
final Set<Long> existingTransactionIds = new HashSet<>();
730+
final Set<Long> existingReversedTransactionIds = new HashSet<>();
731+
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
732+
733+
final Locale locale = command.extractLocale();
734+
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
735+
final LocalDate transactionDate = command.localDateValueOfParameterNamed(SavingsApiConstants.transactionDateParamName);
736+
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(SavingsApiConstants.transactionAmountParamName);
737+
final Map<String, Object> changes = new LinkedHashMap<>();
738+
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
739+
740+
account.undoTransaction(transactionId);
741+
742+
SavingsAccountTransaction transaction = null;
743+
Integer accountType = null;
744+
UUID refNo = UUID.randomUUID();
745+
if (savingsAccountTransaction.isDeposit()) {
746+
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
747+
paymentDetail, null, accountType);
748+
transaction = account.deposit(transactionDTO, false, relaxingDaysConfigForPivotDate, refNo.toString());
749+
} else {
750+
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
751+
paymentDetail, null, accountType);
752+
transaction = account.withdraw(transactionDTO, true, false, relaxingDaysConfigForPivotDate, refNo.toString());
753+
}
754+
final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction);
755+
boolean isInterestTransfer = false;
756+
final LocalDate postInterestOnDate = null;
757+
final boolean backdatedTxnsAllowedTill = false;
758+
final boolean postReversals = false;
759+
checkClientOrGroupActive(account);
760+
if (savingsAccountTransaction.isPostInterestCalculationRequired()
761+
&& account.isBeforeLastPostingPeriod(savingsAccountTransaction.getTransactionDate(), false)) {
762+
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
763+
postInterestOnDate, backdatedTxnsAllowedTill);
764+
} else {
765+
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
766+
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals);
767+
}
768+
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
769+
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
770+
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
771+
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
772+
}
773+
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction, depositAccountOnHoldTransactions,
774+
false);
775+
final boolean isPreMatureClosure = false;
776+
account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
777+
financialYearBeginningMonth);
698778

699-
throw new DepositAccountTransactionNotAllowedException(savingsId, "modify", DepositAccountType.FIXED_DEPOSIT);
779+
this.savingAccountRepositoryWrapper.saveAndFlush(account);
780+
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
781+
return new CommandProcessingResultBuilder() //
782+
.withEntityId(newtransactionId) //
783+
.withOfficeId(account.officeId()) //
784+
.withClientId(account.clientId()) //
785+
.withGroupId(account.groupId()) //
786+
.withSavingsId(savingsId) //
787+
.with(changes) //
788+
.build();
700789
}
701790

702791
@Override

integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3071,6 +3071,112 @@ public void testFixedDepositAccountUndoTransaction() {
30713071
new JournalEntry[] { new JournalEntry(totalInterestPostedBeforeUndo, JournalEntry.TransactionType.DEBIT) });
30723072
}
30733073

3074+
@Test
3075+
public void testFixedDepositAccountAdjustTransaction() {
3076+
this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec);
3077+
this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
3078+
this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
3079+
this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
3080+
3081+
final Account assetAccount = this.accountHelper.createAssetAccount();
3082+
final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
3083+
final Account incomeAccount = this.accountHelper.createIncomeAccount();
3084+
final Account expenseAccount = this.accountHelper.createExpenseAccount();
3085+
3086+
DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
3087+
3088+
Calendar todaysDate = Calendar.getInstance();
3089+
todaysDate.add(Calendar.MONTH, -3);
3090+
final String VALID_FROM = dateFormat.format(todaysDate.getTime());
3091+
todaysDate.add(Calendar.YEAR, 10);
3092+
final String VALID_TO = dateFormat.format(todaysDate.getTime());
3093+
3094+
todaysDate = Calendar.getInstance();
3095+
todaysDate.add(Calendar.MONTH, -1);
3096+
final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime());
3097+
final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime());
3098+
final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
3099+
3100+
Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
3101+
Assertions.assertNotNull(clientId);
3102+
3103+
Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, CASH_BASED, assetAccount, liabilityAccount,
3104+
incomeAccount, expenseAccount);
3105+
Assertions.assertNotNull(fixedDepositProductId);
3106+
3107+
Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(),
3108+
SUBMITTED_ON_DATE, WHOLE_TERM);
3109+
Assertions.assertNotNull(fixedDepositAccountId);
3110+
3111+
HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
3112+
this.responseSpec, fixedDepositAccountId.toString());
3113+
FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap);
3114+
3115+
fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE);
3116+
FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap);
3117+
3118+
fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE);
3119+
FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
3120+
3121+
// Find the deposit transaction created on activation
3122+
List<GetFixedDepositAccountsAccountIdTransactionsResponse> transactions = this.fixedDepositAccountHelper
3123+
.getFixedDepositTransactions(fixedDepositAccountId);
3124+
Assertions.assertNotNull(transactions);
3125+
Integer depositTransactionId = null;
3126+
for (GetFixedDepositAccountsAccountIdTransactionsResponse txn : transactions) {
3127+
if (txn.getTransactionType() != null && Boolean.TRUE.equals(txn.getTransactionType().getDeposit())) {
3128+
depositTransactionId = txn.getId() == null ? null : txn.getId().intValue();
3129+
break;
3130+
}
3131+
}
3132+
Assertions.assertNotNull(depositTransactionId);
3133+
3134+
// Capture deposit amount before adjust for journal entry assertions
3135+
HashMap accountSummaryBeforeAdjust = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId);
3136+
Float totalDepositsBeforeAdjust = (Float) accountSummaryBeforeAdjust.get("totalDeposits");
3137+
Assertions.assertNotNull(totalDepositsBeforeAdjust);
3138+
Assertions.assertTrue(totalDepositsBeforeAdjust > 0f, "Expected totalDeposits > 0 before adjust");
3139+
3140+
// Verify original deposit journal entries exist
3141+
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, ACTIVATION_DATE,
3142+
new JournalEntry[] { new JournalEntry(totalDepositsBeforeAdjust, JournalEntry.TransactionType.DEBIT) });
3143+
this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, ACTIVATION_DATE,
3144+
new JournalEntry[] { new JournalEntry(totalDepositsBeforeAdjust, JournalEntry.TransactionType.CREDIT) });
3145+
3146+
// Adjust the deposit to a new amount
3147+
final double NEW_DEPOSIT_AMOUNT = 200000.0;
3148+
Long adjustResult = this.fixedDepositAccountHelper.adjustFixedDepositTransaction(fixedDepositAccountId, depositTransactionId,
3149+
ACTIVATION_DATE, NEW_DEPOSIT_AMOUNT);
3150+
Assertions.assertNotNull(adjustResult);
3151+
3152+
// 1. Verify original deposit transaction is marked reversed
3153+
List<GetFixedDepositAccountsAccountIdTransactionsResponse> transactionsAfterAdjust = this.fixedDepositAccountHelper
3154+
.getFixedDepositTransactions(fixedDepositAccountId);
3155+
boolean foundReversed = false;
3156+
for (GetFixedDepositAccountsAccountIdTransactionsResponse txn : transactionsAfterAdjust) {
3157+
if (txn.getId() != null && txn.getId().intValue() == depositTransactionId) {
3158+
Assertions.assertTrue(Boolean.TRUE.equals(txn.getReversed()),
3159+
"Original deposit transaction must be marked reversed after adjust");
3160+
foundReversed = true;
3161+
break;
3162+
}
3163+
}
3164+
Assertions.assertTrue(foundReversed, "Original deposit transaction must still be present after adjust");
3165+
3166+
// 2. Verify new deposit amount is reflected in account summary
3167+
HashMap accountSummaryAfterAdjust = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId);
3168+
Float totalDepositsAfterAdjust = (Float) accountSummaryAfterAdjust.get("totalDeposits");
3169+
Assertions.assertNotNull(totalDepositsAfterAdjust);
3170+
Assertions.assertEquals((float) NEW_DEPOSIT_AMOUNT, totalDepositsAfterAdjust, 0.01f,
3171+
"totalDeposits must reflect new amount after adjust");
3172+
3173+
// 3. Verify reversal journal entries (opposite of original) and new deposit entries
3174+
this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, ACTIVATION_DATE,
3175+
new JournalEntry[] { new JournalEntry(totalDepositsBeforeAdjust, JournalEntry.TransactionType.CREDIT) });
3176+
this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, ACTIVATION_DATE,
3177+
new JournalEntry[] { new JournalEntry(totalDepositsBeforeAdjust, JournalEntry.TransactionType.DEBIT) });
3178+
}
3179+
30743180
@AfterEach
30753181
public void tearDown() {
30763182
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();

integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.HashMap;
2727
import java.util.List;
2828
import org.apache.fineract.client.models.GetFixedDepositAccountsAccountIdTransactionsResponse;
29+
import org.apache.fineract.client.models.PostFixedDepositAccountsFixedDepositAccountIdTransactionsRequest;
2930
import org.apache.fineract.client.util.Calls;
3031
import org.apache.fineract.integrationtests.common.CommonConstants;
3132
import org.apache.fineract.integrationtests.common.FineractClientHelper;
@@ -648,10 +649,19 @@ public List<GetFixedDepositAccountsAccountIdTransactionsResponse> getFixedDeposi
648649

649650
public Long undoFixedDepositTransaction(final Integer fixedDepositAccountId, final Integer transactionId) {
650651
LOG.info("--------------------------------- UNDO FIXED DEPOSIT TRANSACTION --------------------------------");
652+
return Calls.ok(FineractClientHelper.getFineractClient().fixedDepositAccountTransactions.adjustTransaction(
653+
fixedDepositAccountId.longValue(), transactionId.longValue(),
654+
new PostFixedDepositAccountsFixedDepositAccountIdTransactionsRequest(), "undo")).getResourceId();
655+
}
656+
657+
public Long adjustFixedDepositTransaction(final Integer fixedDepositAccountId, final Integer transactionId,
658+
final String transactionDate, final Double transactionAmount) {
659+
LOG.info("--------------------------------- ADJUST FIXED DEPOSIT TRANSACTION --------------------------------");
660+
final PostFixedDepositAccountsFixedDepositAccountIdTransactionsRequest request = new PostFixedDepositAccountsFixedDepositAccountIdTransactionsRequest()
661+
.dateFormat("dd MMMM yyyy").locale("en").transactionDate(transactionDate).transactionAmount(transactionAmount);
651662
return Calls
652-
.ok(FineractClientHelper.getFineractClient().fixedDepositAccountTransactions.adjustTransaction(
653-
fixedDepositAccountId.longValue(), transactionId.longValue(),
654-
new org.apache.fineract.client.models.PostFixedDepositAccountsFixedDepositAccountIdTransactionsRequest(), "undo"))
663+
.ok(FineractClientHelper.getFineractClient().fixedDepositAccountTransactions
664+
.adjustTransaction(fixedDepositAccountId.longValue(), transactionId.longValue(), request, "modify"))
655665
.getResourceId();
656666
}
657667

0 commit comments

Comments
 (0)