feat: implement full current account transaction write service

- New CurrentAccountTransactionWritePlatformService interface with deposit,
  withdrawal, holdAmount, releaseAmount, undoTransaction, reverseTransaction,
  and adjustTransaction operations
- New CurrentAccountTransactionWritePlatformServiceImpl using m_current_account
  tables directly (not m_savings_account), with GL journal entry posting
- Add holdAmount, releaseAmount, reversal factory methods to CurrentAccountTransaction
- Wire all 7 current account transaction command handlers to new write service
- Fix CurrentAccountTransactionsApiResource template SQL column: currency_multiplesof
- Fix account number search in SearchParameters

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-05-21 11:20:30 +05:30
co-authored by Claude Sonnet 4.6
parent b25d5a30d2
commit ce0fb18eeb
21 changed files with 6961 additions and 6252 deletions
@@ -1,149 +1,193 @@
/**
* 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.portfolio.currentaccount.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.office.domain.Office;
@Entity
@Table(name = "m_current_account_transaction")
@Getter
@Setter
public class CurrentAccountTransaction extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false)
private CurrentAccount currentAccount;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "office_id", nullable = false)
private Office office;
// 1=Deposit, 2=Withdrawal, 3=InterestPosting, 4=Charge, 5=ChargeWaiver, 6=Transfer, 7=Reversal
@Column(name = "transaction_type_enum", nullable = false)
private Integer transactionTypeEnum;
@Column(name = "transaction_date", nullable = false)
private LocalDate transactionDate;
@Column(name = "amount", nullable = false, precision = 19, scale = 6)
private BigDecimal amount;
@Column(name = "ref_no", length = 100)
private String refNo;
@Column(name = "remarks", length = 500)
private String remarks;
@Column(name = "is_reversed", nullable = false)
private boolean reversed = false;
@Column(name = "is_reversal", nullable = false)
private boolean reversal = false;
@Column(name = "original_transaction_id")
private Long originalTransactionId;
@Column(name = "is_manual", nullable = false)
private boolean manual = false;
@Column(name = "running_balance_derived", precision = 19, scale = 6)
private BigDecimal runningBalanceDerived;
@Column(name = "cumulative_balance_derived", precision = 19, scale = 6)
private BigDecimal cumulativeBalanceDerived;
@Column(name = "balance_end_date_derived")
private LocalDate balanceEndDateDerived;
@Column(name = "balance_number_of_days_derived")
private Integer balanceNumberOfDaysDerived;
@Column(name = "overdraft_amount_derived", precision = 19, scale = 6)
private BigDecimal overdraftAmountDerived;
@Column(name = "is_lien_transaction", nullable = false)
private boolean lienTransaction = false;
@Column(name = "release_id_of_hold_amount_transaction")
private Long releaseIdOfHoldAmountTransaction;
@Column(name = "reason_for_block", length = 200)
private String reasonForBlock;
@Column(name = "submitted_on_date")
private LocalDate submittedOnDate;
@Column(name = "created_date")
private LocalDateTime createdDate;
@Column(name = "created_by")
private Long createdBy;
protected CurrentAccountTransaction() {}
public static CurrentAccountTransaction deposit(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate, String refNo) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
txn.currentAccount = account;
txn.office = office;
txn.transactionTypeEnum = 1;
txn.amount = amount;
txn.transactionDate = transactionDate;
txn.refNo = refNo;
txn.submittedOnDate = transactionDate;
return txn;
}
public static CurrentAccountTransaction chargeDeduction(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
txn.currentAccount = account;
txn.office = office;
txn.transactionTypeEnum = 7; // PAY_CHARGE
txn.amount = amount;
txn.transactionDate = transactionDate;
txn.submittedOnDate = transactionDate;
return txn;
}
public static CurrentAccountTransaction withdrawal(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate, String refNo) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
txn.currentAccount = account;
txn.office = office;
txn.transactionTypeEnum = 2;
txn.amount = amount;
txn.transactionDate = transactionDate;
txn.refNo = refNo;
txn.submittedOnDate = transactionDate;
return txn;
}
}
/**
* 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.portfolio.currentaccount.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.office.domain.Office;
@Entity
@Table(name = "m_current_account_transaction")
@Getter
@Setter
public class CurrentAccountTransaction extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false)
private CurrentAccount currentAccount;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "office_id", nullable = false)
private Office office;
// 1=Deposit, 2=Withdrawal, 3=InterestPosting, 4=Charge, 5=ChargeWaiver, 6=Transfer, 7=Reversal
@Column(name = "transaction_type_enum", nullable = false)
private Integer transactionTypeEnum;
@Column(name = "transaction_date", nullable = false)
private LocalDate transactionDate;
@Column(name = "amount", nullable = false, precision = 19, scale = 6)
private BigDecimal amount;
@Column(name = "ref_no", length = 100)
private String refNo;
@Column(name = "remarks", length = 500)
private String remarks;
@Column(name = "is_reversed", nullable = false)
private boolean reversed = false;
@Column(name = "is_reversal", nullable = false)
private boolean reversal = false;
@Column(name = "original_transaction_id")
private Long originalTransactionId;
@Column(name = "is_manual", nullable = false)
private boolean manual = false;
@Column(name = "running_balance_derived", precision = 19, scale = 6)
private BigDecimal runningBalanceDerived;
@Column(name = "cumulative_balance_derived", precision = 19, scale = 6)
private BigDecimal cumulativeBalanceDerived;
@Column(name = "balance_end_date_derived")
private LocalDate balanceEndDateDerived;
@Column(name = "balance_number_of_days_derived")
private Integer balanceNumberOfDaysDerived;
@Column(name = "overdraft_amount_derived", precision = 19, scale = 6)
private BigDecimal overdraftAmountDerived;
@Column(name = "is_lien_transaction", nullable = false)
private boolean lienTransaction = false;
@Column(name = "release_id_of_hold_amount_transaction")
private Long releaseIdOfHoldAmountTransaction;
@Column(name = "reason_for_block", length = 200)
private String reasonForBlock;
@Column(name = "submitted_on_date")
private LocalDate submittedOnDate;
@Column(name = "created_date")
private LocalDateTime createdDate;
@Column(name = "created_by")
private Long createdBy;
protected CurrentAccountTransaction() {}
public static CurrentAccountTransaction deposit(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate, String refNo) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
txn.currentAccount = account;
txn.office = office;
txn.transactionTypeEnum = 1;
txn.amount = amount;
txn.transactionDate = transactionDate;
txn.refNo = refNo;
txn.submittedOnDate = transactionDate;
return txn;
}
public static CurrentAccountTransaction chargeDeduction(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
txn.currentAccount = account;
txn.office = office;
txn.transactionTypeEnum = 7; // PAY_CHARGE
txn.amount = amount;
txn.transactionDate = transactionDate;
txn.submittedOnDate = transactionDate;
return txn;
}
public static CurrentAccountTransaction withdrawal(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate, String refNo) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
txn.currentAccount = account;
txn.office = office;
txn.transactionTypeEnum = 2;
txn.amount = amount;
txn.transactionDate = transactionDate;
txn.refNo = refNo;
txn.submittedOnDate = transactionDate;
return txn;
}
// transactionTypeEnum = 8 (Hold Amount)
public static CurrentAccountTransaction holdAmount(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate, String reasonForBlock) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
txn.currentAccount = account;
txn.office = office;
txn.transactionTypeEnum = 8;
txn.amount = amount;
txn.transactionDate = transactionDate;
txn.lienTransaction = true;
txn.reasonForBlock = reasonForBlock;
txn.submittedOnDate = transactionDate;
return txn;
}
// transactionTypeEnum = 9 (Release Amount)
public static CurrentAccountTransaction releaseAmount(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate, Long holdTransactionId) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
txn.currentAccount = account;
txn.office = office;
txn.transactionTypeEnum = 9;
txn.amount = amount;
txn.transactionDate = transactionDate;
txn.releaseIdOfHoldAmountTransaction = holdTransactionId;
txn.submittedOnDate = transactionDate;
return txn;
}
// transactionTypeEnum = 7 (Reversal / Undo)
public static CurrentAccountTransaction reversal(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate, Long originalTransactionId) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
txn.currentAccount = account;
txn.office = office;
txn.transactionTypeEnum = 7;
txn.amount = amount;
txn.transactionDate = transactionDate;
txn.reversal = true;
txn.originalTransactionId = originalTransactionId;
txn.submittedOnDate = transactionDate;
return txn;
}
}
@@ -1,29 +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.portfolio.currentaccount.service;
import java.util.List;
import java.util.Map;
public interface CurrentAccountReadPlatformService {
Map<String, Object> retrieveOne(Long accountId);
List<Map<String, Object>> retrieveAll();
}
/**
* 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.portfolio.currentaccount.service;
import java.util.List;
import java.util.Map;
public interface CurrentAccountReadPlatformService {
Map<String, Object> retrieveOne(Long accountId);
List<Map<String, Object>> retrieveAll();
List<Map<String, Object>> retrieveAll(Integer offset, Integer limit, String accountNo, String clientName);
}
@@ -0,0 +1,39 @@
/**
* 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.portfolio.currentaccount.service;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
public interface CurrentAccountTransactionWritePlatformService {
CommandProcessingResult deposit(Long accountId, JsonCommand command);
CommandProcessingResult withdrawal(Long accountId, JsonCommand command);
CommandProcessingResult holdAmount(Long accountId, JsonCommand command);
CommandProcessingResult releaseAmount(Long accountId, Long transactionId);
CommandProcessingResult undoTransaction(Long accountId, Long transactionId, boolean isReversal);
CommandProcessingResult reverseTransaction(Long accountId, Long transactionId, boolean isReversal, JsonCommand command);
CommandProcessingResult adjustTransaction(Long accountId, Long transactionId, JsonCommand command);
}