current account save api update

This commit is contained in:
Nitesh Yadav
2026-04-29 15:18:45 +05:30
parent cbdcee690d
commit a7965f8792
21 changed files with 25453 additions and 9573 deletions
@@ -0,0 +1,386 @@
/**
* 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.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.portfolio.client.domain.Client;
@Entity
@Table(name = "m_current_account")
@Getter
@Setter
public class CurrentAccount extends AbstractAuditableCustom {
@Column(name = "account_no", nullable = false, length = 20, unique = true)
private String accountNo;
@Column(name = "external_id", length = 100)
private String externalId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "client_id")
private Client client;
// Store product_id as plain column to avoid cross-module EclipseLink entity dependency
@Column(name = "product_id", nullable = false)
private Long productId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "office_id", nullable = false)
private Office office;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "staff_id")
private Staff staff;
// Status: 100=Submitted, 200=Approved, 300=Active, 400=Withdrawn, 500=Rejected, 600=Closed
@Column(name = "status_enum", nullable = false)
private Integer statusEnum = 100;
// SubStatus: 0=None, 100=Inactive, 200=Dormant, 300=Block, 400=BlockDebit, 500=BlockCredit
@Column(name = "sub_status_enum", nullable = false)
private Integer subStatusEnum = 0;
// 1=Individual, 2=Group, 3=JLG
@Column(name = "account_type_enum", nullable = false)
private Integer accountTypeEnum = 1;
// Lifecycle dates
@Column(name = "submittedon_date")
private LocalDate submittedOnDate;
@Column(name = "approvedon_date")
private LocalDate approvedOnDate;
@Column(name = "activatedon_date")
private LocalDate activatedOnDate;
@Column(name = "rejectedon_date")
private LocalDate rejectedOnDate;
@Column(name = "withdrawnon_date")
private LocalDate withdrawnOnDate;
@Column(name = "closedon_date")
private LocalDate closedOnDate;
// Currency (copied from product at account creation)
@Column(name = "currency_code", nullable = false, length = 3)
private String currencyCode;
@Column(name = "currency_digits", nullable = false)
private Integer currencyDigits;
@Column(name = "currency_multiplesof")
private Integer currencyMultiplesOf;
// Interest
@Column(name = "nominal_annual_interest_rate", nullable = false, precision = 19, scale = 6)
private BigDecimal nominalAnnualInterestRate = BigDecimal.ZERO;
@Column(name = "interest_compounding_period_enum", nullable = false)
private Integer interestCompoundingPeriodEnum;
@Column(name = "interest_posting_period_enum", nullable = false)
private Integer interestPostingPeriodEnum;
@Column(name = "interest_calculation_type_enum", nullable = false)
private Integer interestCalculationTypeEnum;
@Column(name = "interest_calculation_days_in_year_type_enum", nullable = false)
private Integer interestCalculationDaysInYearTypeEnum;
@Column(name = "min_balance_for_interest_calculation", precision = 19, scale = 6)
private BigDecimal minBalanceForInterestCalculation;
@Column(name = "start_interest_calculation_date")
private LocalDate startInterestCalculationDate;
@Column(name = "accrued_till_date")
private LocalDate accruedTillDate;
@Column(name = "last_interest_calculation_date")
private LocalDate lastInterestCalculationDate;
@Column(name = "last_closed_business_date")
private LocalDate lastClosedBusinessDate;
// Tax
@Column(name = "withhold_tax", nullable = false)
private boolean withholdTax = false;
// Balance rules
@Column(name = "min_required_opening_balance", precision = 19, scale = 6)
private BigDecimal minRequiredOpeningBalance;
@Column(name = "lockin_period_frequency")
private Integer lockinPeriodFrequency;
@Column(name = "lockin_period_frequency_enum")
private Integer lockinPeriodFrequencyEnum;
@Column(name = "lockedin_until_date_derived")
private LocalDate lockedInUntilDate;
@Column(name = "enforce_min_required_balance", nullable = false)
private boolean enforceMinRequiredBalance = false;
@Column(name = "min_required_balance", precision = 19, scale = 6)
private BigDecimal minRequiredBalance;
// Overdraft
@Column(name = "allow_overdraft", nullable = false)
private boolean allowOverdraft = false;
@Column(name = "overdraft_limit", precision = 19, scale = 6)
private BigDecimal overdraftLimit;
@Column(name = "nominal_annual_interest_rate_overdraft", precision = 19, scale = 6)
private BigDecimal nominalAnnualInterestRateOverdraft;
@Column(name = "min_overdraft_for_interest_calculation", precision = 19, scale = 6)
private BigDecimal minOverdraftForInterestCalculation;
// Lien
@Column(name = "is_lien_allowed", nullable = false)
private boolean lienAllowed = false;
@Column(name = "max_allowed_lien_limit", precision = 19, scale = 6)
private BigDecimal maxAllowedLienLimit;
@Column(name = "on_hold_funds_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal onHoldFundsDerived = BigDecimal.ZERO;
// Running balance summary
@Column(name = "account_balance_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal accountBalanceDerived = BigDecimal.ZERO;
@Column(name = "total_deposits_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal totalDepositsDerived = BigDecimal.ZERO;
@Column(name = "total_withdrawals_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal totalWithdrawalsDerived = BigDecimal.ZERO;
@Column(name = "total_interest_earned_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal totalInterestEarnedDerived = BigDecimal.ZERO;
@Column(name = "total_interest_posted_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal totalInterestPostedDerived = BigDecimal.ZERO;
@Column(name = "total_overdraft_interest_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal totalOverdraftInterestDerived = BigDecimal.ZERO;
@Column(name = "total_withhold_tax_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal totalWithholdTaxDerived = BigDecimal.ZERO;
@Column(name = "total_savings_amount_on_hold", nullable = false, precision = 19, scale = 6)
private BigDecimal totalSavingsAmountOnHold = BigDecimal.ZERO;
@Column(name = "withdrawal_fee_for_transfer", nullable = false)
private boolean withdrawalFeeForTransfer = false;
@Column(name = "reason_for_block", length = 500)
private String reasonForBlock;
// Current-account-specific fields
@Column(name = "msme", nullable = false)
private boolean msme = false;
@Column(name = "auto_debit_facility", nullable = false)
private boolean autoDebitFacility = false;
@Column(name = "balance_swap_option", nullable = false)
private boolean balanceSwapOption = false;
@Column(name = "establishment_date")
private LocalDate establishmentDate;
@Column(name = "start_operation_date")
private LocalDate startOperationDate;
@Column(name = "is_non_individual_client", nullable = false)
private boolean nonIndividualClient = false;
@Column(name = "signatory_rights", length = 100)
private String signatoryRights;
// CBS classification
@Column(name = "cbs_account_type", length = 100)
private String cbsAccountType;
@Column(name = "customer_segment", length = 100)
private String customerSegment;
@Column(name = "account_status", length = 50)
private String accountStatus;
@Column(name = "mode_of_operation", length = 50)
private String modeOfOperation;
@Column(name = "special_category", length = 100)
private String specialCategory;
// Introducer
@Column(name = "introducer_client_id")
private Long introducerClientId;
@Column(name = "introducer_name", length = 200)
private String introducerName;
@Column(name = "introducer_cif_number", length = 50)
private String introducerCifNumber;
// Nominee JSON
@Column(name = "nominee_data", columnDefinition = "TEXT")
private String nomineeData;
// Interest calculation settings
@Column(name = "interest_calculate_selector")
private Integer interestCalculateSelector;
@Column(name = "average_period", length = 20)
private String averagePeriod;
@Column(name = "auto_post", nullable = false)
private boolean autoPost = false;
@Column(name = "frequency_fix_day")
private Integer frequencyFixDay;
@Column(name = "enable_lockin_period", nullable = false)
private boolean enableLockinPeriod = false;
@Column(name = "balance_frequency", length = 50)
private String balanceFrequency;
@Column(name = "avg_balance_calc_period", length = 10)
private String avgBalanceCalcPeriod;
// Relationships
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<CurrentAccountTransaction> transactions = new ArrayList<>();
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<CurrentAccountCharge> charges = new HashSet<>();
@OneToOne(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private CurrentAccountFacilityConfig facilityConfig;
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<CurrentAccountJointHolder> jointHolders = new ArrayList<>();
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<CurrentAccountPartner> partners = new ArrayList<>();
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<CurrentAccountOfficerHistory> officerHistory = new ArrayList<>();
protected CurrentAccount() {}
public static CurrentAccount createNew(Client client, Long productId, Office office,
Staff staff, String accountNo, LocalDate submittedOnDate, Integer accountTypeEnum,
String currencyCode, Integer currencyDigits, Integer currencyMultiplesOf,
BigDecimal nominalAnnualInterestRate, Integer interestCompoundingPeriodEnum,
Integer interestPostingPeriodEnum, Integer interestCalculationTypeEnum,
Integer interestCalculationDaysInYearTypeEnum, boolean allowOverdraft,
BigDecimal overdraftLimit, boolean msme, boolean autoDebitFacility,
boolean balanceSwapOption, String customerSegment, boolean autoPost,
Integer interestCalculateSelector, String averagePeriod, String avgBalanceCalcPeriod) {
CurrentAccount account = new CurrentAccount();
account.client = client;
account.productId = productId;
account.office = office;
account.staff = staff;
account.accountNo = accountNo;
account.submittedOnDate = submittedOnDate;
account.statusEnum = 100;
account.subStatusEnum = 0;
account.accountTypeEnum = accountTypeEnum != null ? accountTypeEnum : 1;
account.currencyCode = currencyCode;
account.currencyDigits = currencyDigits;
account.currencyMultiplesOf = currencyMultiplesOf;
account.nominalAnnualInterestRate = nominalAnnualInterestRate != null ? nominalAnnualInterestRate : BigDecimal.ZERO;
account.interestCompoundingPeriodEnum = interestCompoundingPeriodEnum;
account.interestPostingPeriodEnum = interestPostingPeriodEnum;
account.interestCalculationTypeEnum = interestCalculationTypeEnum;
account.interestCalculationDaysInYearTypeEnum = interestCalculationDaysInYearTypeEnum;
account.allowOverdraft = allowOverdraft;
account.overdraftLimit = overdraftLimit;
account.msme = msme;
account.autoDebitFacility = autoDebitFacility;
account.balanceSwapOption = balanceSwapOption;
account.customerSegment = customerSegment;
account.autoPost = autoPost;
account.interestCalculateSelector = interestCalculateSelector;
account.averagePeriod = averagePeriod;
account.avgBalanceCalcPeriod = avgBalanceCalcPeriod;
return account;
}
public boolean isActive() { return this.statusEnum != null && this.statusEnum == 300; }
public boolean isSubmittedAndPendingApproval() { return this.statusEnum != null && this.statusEnum == 100; }
public boolean isApproved() { return this.statusEnum != null && this.statusEnum == 200; }
public boolean isClosed() { return this.statusEnum != null && this.statusEnum == 600; }
public void updateAccountNo(String accountNo) { this.accountNo = accountNo; }
public void approve(LocalDate approvedOnDate) {
this.statusEnum = 200;
this.approvedOnDate = approvedOnDate;
}
public void activate(LocalDate activatedOnDate) {
this.statusEnum = 300;
this.activatedOnDate = activatedOnDate;
}
public void reject(LocalDate rejectedOnDate) {
this.statusEnum = 500;
this.rejectedOnDate = rejectedOnDate;
}
public void withdraw(LocalDate withdrawnOnDate) {
this.statusEnum = 400;
this.withdrawnOnDate = withdrawnOnDate;
}
public void close(LocalDate closedOnDate) {
this.statusEnum = 600;
this.closedOnDate = closedOnDate;
}
}
@@ -0,0 +1,109 @@
/**
* 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 lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.charge.domain.Charge;
@Entity
@Table(name = "m_current_account_charge")
@Getter
@Setter
public class CurrentAccountCharge extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false)
private CurrentAccount currentAccount;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "charge_id", nullable = false)
private Charge charge;
@Column(name = "charge_time_enum", nullable = false)
private Integer chargeTimeEnum;
@Column(name = "charge_due_date")
private LocalDate chargeDueDate;
@Column(name = "fee_on_month")
private Integer feeOnMonth;
@Column(name = "fee_on_day")
private Integer feeOnDay;
@Column(name = "fee_interval")
private Integer feeInterval;
@Column(name = "charge_calculation_enum", nullable = false)
private Integer chargeCalculationEnum;
@Column(name = "calculation_percentage", precision = 19, scale = 6)
private BigDecimal calculationPercentage;
@Column(name = "calculation_on_amount", precision = 19, scale = 6)
private BigDecimal calculationOnAmount;
@Column(name = "amount", nullable = false, precision = 19, scale = 6)
private BigDecimal amount;
@Column(name = "amount_paid_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal amountPaidDerived = BigDecimal.ZERO;
@Column(name = "amount_waived_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal amountWaivedDerived = BigDecimal.ZERO;
@Column(name = "amount_writtenoff_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal amountWrittenOffDerived = BigDecimal.ZERO;
@Column(name = "amount_outstanding_derived", nullable = false, precision = 19, scale = 6)
private BigDecimal amountOutstandingDerived;
@Column(name = "is_paid_derived", nullable = false)
private boolean paid = false;
@Column(name = "waived", nullable = false)
private boolean waived = false;
@Column(name = "is_penalty", nullable = false)
private boolean penalty = false;
@Column(name = "is_active", nullable = false)
private boolean active = true;
@Column(name = "inactivated_on_date")
private LocalDate inactivatedOnDate;
@Column(name = "free_withdrawal_count")
private Integer freeWithdrawalCount;
@Column(name = "charge_reset_date")
private LocalDate chargeResetDate;
protected CurrentAccountCharge() {}
}
@@ -0,0 +1,102 @@
/**
* 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.OneToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_current_account_facility_config")
@Getter
@Setter
public class CurrentAccountFacilityConfig extends AbstractPersistableCustom<Long> {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false, unique = true)
private CurrentAccount currentAccount;
@Column(name = "atm_facility", nullable = false)
private boolean atmFacility = false;
@Column(name = "atm_withdrawal_facility", nullable = false)
private boolean atmWithdrawalFacility = false;
@Column(name = "upi_facility", nullable = false)
private boolean upiFacility = false;
@Column(name = "pos_transactions_facility", nullable = false)
private boolean posTransactionsFacility = false;
@Column(name = "cheque_book", nullable = false)
private boolean chequeBook = false;
@Column(name = "imps_facility", nullable = false)
private boolean impsFacility = false;
@Column(name = "sms_alerts", nullable = false)
private boolean smsAlerts = false;
@Column(name = "bbps_facility", nullable = false)
private boolean bbpsFacility = false;
@Column(name = "insurance_cover", nullable = false)
private boolean insuranceCover = false;
@Column(name = "bepg_facility", nullable = false)
private boolean bepgFacility = false;
@Column(name = "neft_facility", nullable = false)
private boolean neftFacility = false;
@Column(name = "rtgs_facility", nullable = false)
private boolean rtgsFacility = false;
@Column(name = "inter_bank_total_cap_facility", nullable = false)
private boolean interBankTotalCapFacility = false;
@Column(name = "withdrawal_facility", nullable = false)
private boolean withdrawalFacility = false;
@Column(name = "deposit_facility_kyc", nullable = false)
private boolean depositFacilityKyc = false;
@Column(name = "virtual_debit_card", nullable = false)
private boolean virtualDebitCard = false;
@Column(name = "internet_banking", nullable = false)
private boolean internetBanking = false;
@Column(name = "mobile_banking", nullable = false)
private boolean mobileBanking = false;
protected CurrentAccountFacilityConfig() {}
public static CurrentAccountFacilityConfig createFor(CurrentAccount account) {
CurrentAccountFacilityConfig cfg = new CurrentAccountFacilityConfig();
cfg.currentAccount = account;
return cfg;
}
}
@@ -0,0 +1,64 @@
/**
* 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 lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_current_account_joint_holder")
@Getter
@Setter
public class CurrentAccountJointHolder extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false)
private CurrentAccount currentAccount;
@Column(name = "client_id")
private Long clientId;
@Column(name = "cif_number", length = 50)
private String cifNumber;
@Column(name = "display_name", length = 200)
private String displayName;
@Column(name = "share_percentage", precision = 5, scale = 2)
private BigDecimal sharePercentage;
@Column(name = "signing_authority", length = 100)
private String signingAuthority;
@Column(name = "role", length = 100)
private String role;
@Column(name = "is_primary", nullable = false)
private boolean primary = false;
public CurrentAccountJointHolder() {}
}
@@ -0,0 +1,57 @@
/**
* 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.time.LocalDate;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.staff.domain.Staff;
@Entity
@Table(name = "m_current_account_officer_history")
@Getter
@Setter
public class CurrentAccountOfficerHistory extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false)
private CurrentAccount currentAccount;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "staff_id")
private Staff staff;
@Column(name = "start_date", nullable = false)
private LocalDate startDate;
@Column(name = "end_date")
private LocalDate endDate;
@Column(name = "created_by_id")
private Long createdById;
protected CurrentAccountOfficerHistory() {}
}
@@ -0,0 +1,58 @@
/**
* 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 lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_current_account_partner")
@Getter
@Setter
public class CurrentAccountPartner extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_account_id", nullable = false)
private CurrentAccount currentAccount;
@Column(name = "client_id")
private Long clientId;
@Column(name = "client_cif", length = 50)
private String clientCif;
@Column(name = "client_name", length = 200)
private String clientName;
@Column(name = "share_percentage", precision = 5, scale = 2)
private BigDecimal sharePercentage;
@Column(name = "is_mandatory", nullable = false)
private boolean mandatory = false;
public CurrentAccountPartner() {}
}
@@ -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.portfolio.currentaccount.domain;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface CurrentAccountRepository extends JpaRepository<CurrentAccount, Long> {
Optional<CurrentAccount> findByAccountNo(String accountNo);
Optional<CurrentAccount> findByExternalId(String externalId);
List<CurrentAccount> findByClientId(Long clientId);
@Query("SELECT MAX(ca.id) FROM CurrentAccount ca")
Optional<Long> findMaxId();
@Query("SELECT ca FROM CurrentAccount ca WHERE ca.client.id = :clientId AND ca.statusEnum != 600")
List<CurrentAccount> findActiveByClientId(@Param("clientId") Long clientId);
}
@@ -0,0 +1,137 @@
/**
* 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 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;
}
}
@@ -0,0 +1,27 @@
/**
* 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 java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CurrentAccountTransactionRepository extends JpaRepository<CurrentAccountTransaction, Long> {
List<CurrentAccountTransaction> findByCurrentAccountIdOrderByTransactionDateDescIdDesc(Long currentAccountId);
}
@@ -0,0 +1,92 @@
/**
* 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 lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
/**
* Generates CBS account numbers for current accounts.
* Format: 00 + GL_CODE(6 digits) + OFFICE_ID(2 digits) + ACCOUNT_ID(5 digits) = 15 digits
* GL code is fetched from m_current_product.gl_account_id → m_acc_gl_account.gl_code
*/
@Component
@RequiredArgsConstructor
public class CurrentAccountNumberGenerator {
private static final String CBS_PREFIX = "00";
private final JdbcTemplate jdbcTemplate;
public String generate(CurrentAccount account) {
Long productId = account.getProductId();
// Fetch gl_code directly from current product → gl account
String glCode = jdbcTemplate.query(
"SELECT ga.gl_code FROM m_current_product cp "
+ "JOIN m_acc_gl_account ga ON ga.id = cp.gl_account_id "
+ "WHERE cp.id = ?",
rs -> rs.next() ? rs.getString("gl_code") : null,
productId);
if (glCode == null || glCode.isBlank()) glCode = "000000";
// Pad/trim GL code to 6 digits
glCode = glCode.length() > 6
? glCode.substring(glCode.length() - 6)
: StringUtils.leftPad(glCode, 6, '0');
// Office ID — 2 digits
String officeId = StringUtils.leftPad(account.getOffice().getId().toString(), 2, '0');
if (officeId.length() > 2) officeId = officeId.substring(officeId.length() - 2);
// Account ID — 5 digits
String accountId = StringUtils.leftPad(account.getId().toString(), 5, '0');
if (accountId.length() > 5) accountId = accountId.substring(accountId.length() - 5);
return CBS_PREFIX + glCode + officeId + accountId;
}
public String preview(Long productId, Long officeId) {
String glCode = jdbcTemplate.query(
"SELECT ga.gl_code FROM m_current_product cp "
+ "JOIN m_acc_gl_account ga ON ga.id = cp.gl_account_id "
+ "WHERE cp.id = ?",
rs -> rs.next() ? rs.getString("gl_code") : null,
productId);
if (glCode == null || glCode.isBlank()) glCode = "000000";
glCode = glCode.length() > 6 ? glCode.substring(glCode.length() - 6) : StringUtils.leftPad(glCode, 6, '0');
String officeStr = StringUtils.leftPad(officeId != null ? officeId.toString() : "0", 2, '0');
if (officeStr.length() > 2) officeStr = officeStr.substring(officeStr.length() - 2);
// Next account ID estimate from m_current_account
Long maxId = jdbcTemplate.query(
"SELECT MAX(id) FROM m_current_account",
rs -> rs.next() ? rs.getLong(1) : 0L);
String nextSeq = StringUtils.leftPad(String.valueOf((maxId != null ? maxId : 0L) + 1), 5, '0');
if (nextSeq.length() > 5) nextSeq = nextSeq.substring(nextSeq.length() - 5);
return CBS_PREFIX + glCode + officeStr + nextSeq;
}
}
@@ -0,0 +1,43 @@
/**
* 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 CurrentAccountWritePlatformService {
CommandProcessingResult submitApplication(JsonCommand command);
CommandProcessingResult approveApplication(Long accountId, JsonCommand command);
CommandProcessingResult undoApproval(Long accountId, JsonCommand command);
CommandProcessingResult activate(Long accountId, JsonCommand command);
CommandProcessingResult rejectApplication(Long accountId, JsonCommand command);
CommandProcessingResult withdrawApplication(Long accountId, JsonCommand command);
CommandProcessingResult closeAccount(Long accountId, JsonCommand command);
CommandProcessingResult modifyApplication(Long accountId, JsonCommand command);
CommandProcessingResult deleteApplication(Long accountId);
}
@@ -817,6 +817,15 @@ public class CurrentProduct extends AbstractPersistableCustom<Long> {
return actualChanges;
}
public String getName() { return name; }
public String getShortName() { return shortName; }
public boolean isAllowOverdraft() { return allowOverdraft; }
public BigDecimal getOverdraftLimit() { return overdraftLimit; }
public Integer getInterestCompoundingPeriodType() { return interestCompoundingPeriodType; }
public Integer getInterestPostingPeriodType() { return interestPostingPeriodType; }
public Integer getInterestCalculationType() { return interestCalculationType; }
public Integer getInterestCalculationDaysInYearType() { return interestCalculationDaysInYearType; }
// Dormancy/misc getters needed by validator
public Boolean isDormancyTrackingActive() { return this.isDormancyTrackingActive; }
public Long getDaysToInactive() { return this.daysToInactive; }
File diff suppressed because it is too large Load Diff
@@ -36,10 +36,12 @@ import org.apache.fineract.portfolio.client.domain.ClientRepository;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
/**
@@ -69,6 +71,7 @@ public class AccountNumberGenerator {
private final SavingsAccountRepository savingsAccountRepository;
private final GLAccountRepository glAccountRepository;
private final SavingsProductRepository savingsProductRepository;
private final JdbcTemplate jdbcTemplate;
public String generate(Client client, AccountNumberFormat accountNumberFormat) {
Map<String, String> propertyMap = new HashMap<>();
@@ -119,6 +122,35 @@ public class AccountNumberGenerator {
return CBS_FIXED_PREFIX + glCode + officeId + accountId;
}
/**
* Generate CBS account number for a current account.
* GL code is fetched directly from m_current_product using the current account's product_id.
* Format: 00 + GL_CODE(6) + OFFICE_ID(2) + ACCOUNT_ID(5) = 15 digits
*/
public String generateForCurrentAccount(CurrentAccount currentAccount) {
Long productId = currentAccount.getProductId();
Long glAccountId = jdbcTemplate.query(
"SELECT gl_account_id FROM m_current_product WHERE id = ?",
rs -> rs.next() ? rs.getLong("gl_account_id") : null,
productId);
String glCode = "000000";
if (glAccountId != null && glAccountId > 0) {
glCode = glAccountRepository.findById(glAccountId)
.map(gl -> StringUtils.leftPad(gl.getGlCode(), 6, '0'))
.orElse("000000");
}
glCode = glCode.length() > 6 ? glCode.substring(glCode.length() - 6) : StringUtils.leftPad(glCode, 6, '0');
String officeId = StringUtils.leftPad(currentAccount.getOffice().getId().toString(), 2, '0');
if (officeId.length() > 2) officeId = officeId.substring(officeId.length() - 2);
String accountId = StringUtils.leftPad(currentAccount.getId().toString(), 5, '0');
if (accountId.length() > 5) accountId = accountId.substring(accountId.length() - 5);
return CBS_FIXED_PREFIX + glCode + officeId + accountId;
}
public String previewAccountNumber(Long productId, Long officeId) {
String glCode = "000000";
if (productId != null) {
@@ -40,9 +40,6 @@ import java.util.Collection;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
@@ -59,6 +56,7 @@ import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWritePlatformService;
import org.apache.fineract.portfolio.savings.service.CurrentAccountTemplateReadPlatformService;
import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
@@ -75,10 +73,10 @@ public class CurrentAccountsApiResource {
private final CurrentAccountTemplateReadPlatformService currentAccountTemplateReadPlatformService;
private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<SavingsAccountData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
private final SqlValidator sqlValidator;
private final CurrentAccountWritePlatformService currentAccountWritePlatformService;
@GET
@Path("template")
@@ -128,8 +126,9 @@ public class CurrentAccountsApiResource {
@Operation(summary = "Submit new current account application")
@ApiResponse(responseCode = "200", description = "OK")
public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createCurrentAccount().withJson(apiRequestBodyAsJson).build();
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
final org.apache.fineract.infrastructure.core.api.JsonCommand command =
org.apache.fineract.infrastructure.core.api.JsonCommand.from(apiRequestBodyAsJson);
final CommandProcessingResult result = currentAccountWritePlatformService.submitApplication(command);
return toApiJsonSerializer.serialize(result);
}
@@ -164,16 +163,9 @@ public class CurrentAccountsApiResource {
@Parameter(hidden = true) final String apiRequestBodyAsJson,
@QueryParam("command") final String commandParam) {
if (is(commandParam, "updateWithHoldTax")) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson)
.updateWithHoldTaxCurrentAccount(accountId).build();
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
return toApiJsonSerializer.serialize(result);
}
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCurrentAccount(accountId).withJson(apiRequestBodyAsJson)
.build();
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
final org.apache.fineract.infrastructure.core.api.JsonCommand command =
org.apache.fineract.infrastructure.core.api.JsonCommand.from(apiRequestBodyAsJson);
final CommandProcessingResult result = currentAccountWritePlatformService.modifyApplication(accountId, command);
return toApiJsonSerializer.serialize(result);
}
@@ -192,53 +184,27 @@ public class CurrentAccountsApiResource {
jsonApiRequest = "{}";
}
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest);
final org.apache.fineract.infrastructure.core.api.JsonCommand command =
org.apache.fineract.infrastructure.core.api.JsonCommand.from(jsonApiRequest);
CommandProcessingResult result = null;
if (is(commandParam, "reject")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.rejectCurrentAccountApplication(accountId).build());
result = currentAccountWritePlatformService.rejectApplication(accountId, command);
} else if (is(commandParam, "withdrawnByApplicant")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.withdrawCurrentAccountApplication(accountId).build());
result = currentAccountWritePlatformService.withdrawApplication(accountId, command);
} else if (is(commandParam, "approve")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.approveCurrentAccountApplication(accountId).build());
result = currentAccountWritePlatformService.approveApplication(accountId, command);
} else if (is(commandParam, "undoapproval")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.undoCurrentAccountApplication(accountId).build());
result = currentAccountWritePlatformService.undoApproval(accountId, command);
} else if (is(commandParam, "activate")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.currentAccountActivation(accountId).build());
} else if (is(commandParam, "calculateInterest")) {
result = commandsSourceWritePlatformService
.logCommandSource(builder.withNoJsonBody().currentAccountInterestCalculation(accountId).build());
} else if (is(commandParam, "postInterest")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.currentAccountInterestPosting(accountId).build());
} else if (is(commandParam, "applyAnnualFees")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.currentAccountApplyAnnualFees(accountId).build());
result = currentAccountWritePlatformService.activate(accountId, command);
} else if (is(commandParam, "close")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.closeCurrentAccountApplication(accountId).build());
} else if (is(commandParam, "assignSavingsOfficer")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.assignCurrentAccountOfficer(accountId).build());
} else if (is(commandParam, "unassignSavingsOfficer")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.unassignCurrentAccountOfficer(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_DEBIT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.blockDebitsFromCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.unblockDebitsFromCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_CREDIT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.blockCreditsToCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.unblockCreditsToCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_ACCOUNT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.blockCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.unblockCurrentAccount(accountId).build());
result = currentAccountWritePlatformService.closeAccount(accountId, command);
}
if (result == null) {
throw new UnrecognizedQueryParamException("command", commandParam,
new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest",
"postInterest", "close", "assignSavingsOfficer", "unassignSavingsOfficer",
SavingsApiConstants.COMMAND_BLOCK_DEBIT, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT,
SavingsApiConstants.COMMAND_BLOCK_CREDIT, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT,
SavingsApiConstants.COMMAND_BLOCK_ACCOUNT, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT });
new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "close" });
}
return toApiJsonSerializer.serialize(result);
@@ -251,8 +217,7 @@ public class CurrentAccountsApiResource {
@Operation(summary = "Delete a current account application")
@ApiResponse(responseCode = "200", description = "OK")
public String delete(@PathParam("accountId") final Long accountId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteCurrentAccount(accountId).build();
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
final CommandProcessingResult result = currentAccountWritePlatformService.deleteApplication(accountId);
return toApiJsonSerializer.serialize(result);
}
@@ -0,0 +1,370 @@
/**
* 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.savings.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountJointHolder;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountPartner;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountRepository;
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWritePlatformService;
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountNumberGenerator;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@RequiredArgsConstructor
public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWritePlatformService {
private final PlatformSecurityContext context;
private final CurrentAccountRepository currentAccountRepository;
private final CurrentProductRepository currentProductRepository;
private final ClientRepositoryWrapper clientRepository;
private final OfficeRepositoryWrapper officeRepository;
private final CurrentAccountNumberGenerator accountNumberGenerator;
@Override
@Transactional
public CommandProcessingResult submitApplication(final JsonCommand command) {
context.authenticatedUser();
final Long clientId = command.longValueOfParameterNamed("clientId");
final Long productId = command.longValueOfParameterNamed("productId");
final Long officeId = command.parameterExists("officeId") ? command.longValueOfParameterNamed("officeId") : null;
final Long staffId = command.parameterExists("fieldOfficerId")
&& !isBlankString(command.parsedJson().getAsJsonObject(), "fieldOfficerId")
? command.longValueOfParameterNamed("fieldOfficerId") : null;
final Client client = clientRepository.findOneWithNotFoundDetection(clientId);
final CurrentProduct product = currentProductRepository.findById(productId)
.orElseThrow(() -> new SavingsProductNotFoundException(productId));
final Office office = officeId != null
? officeRepository.findOneWithNotFoundDetection(officeId)
: client.getOffice();
Staff staff = null;
// staff loaded lazily if needed — keep null for now if not in scope
LocalDate submittedOnDate = command.localDateValueOfParameterNamed("submittedOnDate");
if (submittedOnDate == null) submittedOnDate = LocalDate.now();
// accountType may arrive as integer or string — store null if absent/unparseable
Integer accountTypeEnum = null;
if (command.parameterExists("accountType")) {
try {
accountTypeEnum = command.integerValueOfParameterNamed("accountType");
} catch (Exception ignored) {
// string value like "Standard Account" — ignore, store null
}
}
// Override from request if provided, else use product defaults
boolean msme = command.parameterExists("msme")
? command.booleanPrimitiveValueOfParameterNamed("msme") : product.isMsme();
boolean autoDebitFacility = command.parameterExists("autoDebitFacility")
? command.booleanPrimitiveValueOfParameterNamed("autoDebitFacility") : product.isAutoDebitFacility();
boolean balanceSwapOption = command.parameterExists("balanceSwapOption")
? command.booleanPrimitiveValueOfParameterNamed("balanceSwapOption") : product.isBalanceSwapOption();
final CurrentAccount account = CurrentAccount.createNew(
client, productId, office, staff, "TEMP", submittedOnDate, accountTypeEnum,
product.currency().getCode(), product.currency().getDigitsAfterDecimal(),
product.currency().getInMultiplesOf(),
product.nominalAnnualInterestRate(),
product.getInterestCompoundingPeriodType(), product.getInterestPostingPeriodType(),
product.getInterestCalculationType(), product.getInterestCalculationDaysInYearType(),
product.isAllowOverdraft(), product.getOverdraftLimit(),
msme, autoDebitFacility, balanceSwapOption,
product.getCustomerSegment(), product.isAutoPost(),
product.getInterestCalculateSelector(), product.getAveragePeriod(),
product.getAvgBalanceCalcPeriod());
if (command.parameterExists("establishmentDate")) {
account.setEstablishmentDate(command.localDateValueOfParameterNamed("establishmentDate"));
}
if (command.parameterExists("startOperationDate")) {
account.setStartOperationDate(command.localDateValueOfParameterNamed("startOperationDate"));
}
if (command.parameterExists("isNonIndividualClient")) {
account.setNonIndividualClient(command.booleanPrimitiveValueOfParameterNamed("isNonIndividualClient"));
}
if (command.parameterExists("signatoryRights")) {
account.setSignatoryRights(command.stringValueOfParameterNamed("signatoryRights"));
}
if (command.parameterExists("modeOfOperation")) {
account.setModeOfOperation(command.stringValueOfParameterNamed("modeOfOperation"));
}
if (command.parameterExists("introducerName")) {
account.setIntroducerName(command.stringValueOfParameterNamed("introducerName"));
}
if (command.parameterExists("introducerClientId")) {
account.setIntroducerClientId(command.longValueOfParameterNamed("introducerClientId"));
} else if (command.parameterExists("introducerId")) {
account.setIntroducerClientId(command.longValueOfParameterNamed("introducerId"));
}
if (command.parameterExists("externalId")) {
account.setExternalId(command.stringValueOfParameterNamed("externalId"));
}
if (command.parameterExists("accountStatus")) {
account.setAccountStatus(command.stringValueOfParameterNamed("accountStatus"));
}
if (command.parameterExists("customerSegment")) {
account.setCustomerSegment(command.stringValueOfParameterNamed("customerSegment"));
}
// Save first to get ID
currentAccountRepository.saveAndFlush(account);
// Generate account number: 00 + GL_CODE(6) + OFFICE_ID(2) + ACCOUNT_ID(5)
final String accountNo = accountNumberGenerator.generate(account);
account.updateAccountNo(accountNo);
// Facility config — UI sends "accountFacilityConfig", internal uses "facilityConfig"
if (command.parameterExists("accountFacilityConfig") || command.parameterExists("facilityConfig")) {
final CurrentAccountFacilityConfig facilityConfig = assembleFacilityConfig(account, command);
account.setFacilityConfig(facilityConfig);
}
// Joint holders
if (command.parameterExists("jointHolders")) {
assembleJointHolders(account, command);
}
// Partners (non-individual)
if (command.parameterExists("partners")) {
assemblePartners(account, command);
}
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder()
.withEntityId(account.getId())
.withClientId(clientId)
.withOfficeId(office.getId())
.build();
}
@Override
@Transactional
public CommandProcessingResult approveApplication(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate approvedOnDate = command.localDateValueOfParameterNamed("approvedOnDate");
if (approvedOnDate == null) approvedOnDate = LocalDate.now();
account.approve(approvedOnDate);
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult undoApproval(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
account.setStatusEnum(100);
account.setApprovedOnDate(null);
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult activate(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate activatedOnDate = command.localDateValueOfParameterNamed("activatedOnDate");
if (activatedOnDate == null) activatedOnDate = LocalDate.now();
account.activate(activatedOnDate);
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult rejectApplication(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate rejectedOnDate = command.localDateValueOfParameterNamed("rejectedOnDate");
if (rejectedOnDate == null) rejectedOnDate = LocalDate.now();
account.reject(rejectedOnDate);
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult withdrawApplication(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate withdrawnOnDate = command.localDateValueOfParameterNamed("withdrawnOnDate");
if (withdrawnOnDate == null) withdrawnOnDate = LocalDate.now();
account.withdraw(withdrawnOnDate);
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult closeAccount(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate closedOnDate = command.localDateValueOfParameterNamed("closedOnDate");
if (closedOnDate == null) closedOnDate = LocalDate.now();
account.close(closedOnDate);
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult modifyApplication(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
if (command.parameterExists("msme")) {
account.setMsme(command.booleanPrimitiveValueOfParameterNamed("msme"));
}
if (command.parameterExists("signatoryRights")) {
account.setSignatoryRights(command.stringValueOfParameterNamed("signatoryRights"));
}
if (command.parameterExists("modeOfOperation")) {
account.setModeOfOperation(command.stringValueOfParameterNamed("modeOfOperation"));
}
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult deleteApplication(final Long accountId) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
currentAccountRepository.delete(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
// ─── helpers ──────────────────────────────────────────────────────────────
private CurrentAccount findAccount(Long accountId) {
return currentAccountRepository.findById(accountId)
.orElseThrow(() -> new SavingsAccountNotFoundException(accountId));
}
private CurrentAccountFacilityConfig assembleFacilityConfig(CurrentAccount account, JsonCommand command) {
final CurrentAccountFacilityConfig cfg = CurrentAccountFacilityConfig.createFor(account);
final JsonObject root = command.parsedJson().getAsJsonObject();
JsonObject fcJson = root.getAsJsonObject("accountFacilityConfig");
if (fcJson == null) fcJson = root.getAsJsonObject("facilityConfig");
if (fcJson == null) return cfg;
cfg.setAtmFacility(getBool(fcJson, "atmFacility"));
cfg.setAtmWithdrawalFacility(getBool(fcJson, "atmWithdrawalFacility"));
cfg.setUpiFacility(getBool(fcJson, "upiFacility"));
cfg.setPosTransactionsFacility(getBool(fcJson, "posTransactionsFacility"));
cfg.setChequeBook(getBool(fcJson, "chequeBook"));
cfg.setImpsFacility(getBool(fcJson, "impsFacility"));
cfg.setSmsAlerts(getBool(fcJson, "smsAlerts"));
cfg.setBbpsFacility(getBool(fcJson, "bbpsFacility"));
cfg.setInsuranceCover(getBool(fcJson, "insuranceCover"));
cfg.setBepgFacility(getBool(fcJson, "bepgFacility"));
cfg.setNeftFacility(getBool(fcJson, "neftFacility"));
cfg.setRtgsFacility(getBool(fcJson, "rtgsFacility"));
cfg.setInterBankTotalCapFacility(getBool(fcJson, "interBankTotalCapFacility"));
cfg.setWithdrawalFacility(getBool(fcJson, "withdrawalFacility"));
cfg.setDepositFacilityKyc(getBool(fcJson, "depositFacilityKyc"));
cfg.setVirtualDebitCard(getBool(fcJson, "virtualDebitCard"));
cfg.setInternetBanking(getBool(fcJson, "internetBanking"));
cfg.setMobileBanking(getBool(fcJson, "mobileBanking"));
return cfg;
}
private void assembleJointHolders(CurrentAccount account, JsonCommand command) {
final JsonArray arr = command.arrayOfParameterNamed("jointHolders");
if (arr == null) return;
for (JsonElement el : arr) {
final JsonObject jo = el.getAsJsonObject();
final CurrentAccountJointHolder holder = new CurrentAccountJointHolder();
holder.setCurrentAccount(account);
if (jo.has("clientId") && !jo.get("clientId").isJsonNull()) {
holder.setClientId(jo.get("clientId").getAsLong());
}
if (jo.has("cifNumber") && !jo.get("cifNumber").isJsonNull()) {
holder.setCifNumber(jo.get("cifNumber").getAsString());
}
if (jo.has("displayName") && !jo.get("displayName").isJsonNull()) {
holder.setDisplayName(jo.get("displayName").getAsString());
}
if (jo.has("signingAuthority") && !jo.get("signingAuthority").isJsonNull()) {
holder.setSigningAuthority(jo.get("signingAuthority").getAsString());
}
if (jo.has("role") && !jo.get("role").isJsonNull()) {
holder.setRole(jo.get("role").getAsString());
}
account.getJointHolders().add(holder);
}
}
private void assemblePartners(CurrentAccount account, JsonCommand command) {
final JsonArray arr = command.arrayOfParameterNamed("partners");
if (arr == null) return;
for (JsonElement el : arr) {
final JsonObject jo = el.getAsJsonObject();
final CurrentAccountPartner partner = new CurrentAccountPartner();
partner.setCurrentAccount(account);
if (jo.has("clientId") && !jo.get("clientId").isJsonNull()) {
partner.setClientId(jo.get("clientId").getAsLong());
}
if (jo.has("clientCif") && !jo.get("clientCif").isJsonNull()) {
partner.setClientCif(jo.get("clientCif").getAsString());
}
if (jo.has("clientName") && !jo.get("clientName").isJsonNull()) {
partner.setClientName(jo.get("clientName").getAsString());
}
account.getPartners().add(partner);
}
}
private boolean getBool(JsonObject obj, String key) {
return obj.has(key) && !obj.get(key).isJsonNull() && obj.get(key).getAsBoolean();
}
private boolean isBlankString(JsonObject obj, String key) {
if (!obj.has(key) || obj.get(key).isJsonNull()) return true;
return obj.get(key).getAsString().isBlank();
}
}
@@ -277,4 +277,5 @@
<include file="parts/0271_create_rd_base_interest_rate_tables.xml" relativeToChangelogFile="true"/>
<include file="parts/0272_seed_rd_base_interest_rate_data.xml" relativeToChangelogFile="true"/>
<include file="parts/0273_add_gl_account_id_to_current_product.xml" relativeToChangelogFile="true"/>
<include file="parts/0276_create_current_account_tables.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,393 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
<!-- =========================================================
m_current_account — main current account table
(fully separate from m_savings_account)
========================================================= -->
<changeSet id="0276_create_current_account_tables" author="system">
<createTable tableName="m_current_account">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="account_no" type="varchar(20)">
<constraints nullable="false" unique="true" uniqueConstraintName="ca_unq_account_no"/>
</column>
<column name="external_id" type="varchar(100)">
<constraints nullable="true" unique="true" uniqueConstraintName="ca_unq_external_id"/>
</column>
<!-- Ownership -->
<column name="client_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_client" referencedTableName="m_client" referencedColumnNames="id"/>
</column>
<column name="group_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_group" referencedTableName="m_group" referencedColumnNames="id"/>
</column>
<column name="product_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_ca_product" referencedTableName="m_current_product" referencedColumnNames="id"/>
</column>
<column name="office_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_ca_office" referencedTableName="m_office" referencedColumnNames="id"/>
</column>
<column name="staff_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_staff" referencedTableName="m_staff" referencedColumnNames="id"/>
</column>
<!-- Status -->
<column name="status_enum" type="smallint" defaultValue="100">
<constraints nullable="false"/>
</column>
<!-- 100=Submitted, 200=Approved, 300=Active, 400=Withdrawn, 500=Rejected, 600=Closed -->
<column name="sub_status_enum" type="smallint" defaultValue="0">
<constraints nullable="false"/>
</column>
<!-- 0=None, 100=Inactive, 200=Dormant, 300=Escheat, 400=Block, 500=BlockDebit, 600=BlockCredit -->
<column name="account_type_enum" type="smallint" defaultValue="1">
<constraints nullable="false"/>
</column>
<!-- 1=Individual, 2=Group, 3=JLG -->
<!-- Lifecycle dates -->
<column name="submittedon_date" type="date"><constraints nullable="true"/></column>
<column name="submittedon_userid" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_submit_user" referencedTableName="m_appuser" referencedColumnNames="id"/>
</column>
<column name="approvedon_date" type="date"><constraints nullable="true"/></column>
<column name="approvedon_userid" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_approve_user" referencedTableName="m_appuser" referencedColumnNames="id"/>
</column>
<column name="activatedon_date" type="date"><constraints nullable="true"/></column>
<column name="activatedon_userid" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_activate_user" referencedTableName="m_appuser" referencedColumnNames="id"/>
</column>
<column name="rejectedon_date" type="date"><constraints nullable="true"/></column>
<column name="rejectedon_userid" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_reject_user" referencedTableName="m_appuser" referencedColumnNames="id"/>
</column>
<column name="withdrawnon_date" type="date"><constraints nullable="true"/></column>
<column name="withdrawnon_userid" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_withdraw_user" referencedTableName="m_appuser" referencedColumnNames="id"/>
</column>
<column name="closedon_date" type="date"><constraints nullable="true"/></column>
<column name="closedon_userid" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_close_user" referencedTableName="m_appuser" referencedColumnNames="id"/>
</column>
<!-- Currency (copied from product at account creation) -->
<column name="currency_code" type="varchar(3)"><constraints nullable="false"/></column>
<column name="currency_digits" type="smallint"><constraints nullable="false"/></column>
<column name="currency_multiplesof" type="smallint"><constraints nullable="true"/></column>
<!-- Interest settings (copied from product) -->
<column name="nominal_annual_interest_rate" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="interest_compounding_period_enum" type="smallint"><constraints nullable="false"/></column>
<column name="interest_posting_period_enum" type="smallint"><constraints nullable="false"/></column>
<column name="interest_calculation_type_enum" type="smallint"><constraints nullable="false"/></column>
<column name="interest_calculation_days_in_year_type_enum" type="smallint"><constraints nullable="false"/></column>
<column name="min_balance_for_interest_calculation" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="start_interest_calculation_date" type="date"><constraints nullable="true"/></column>
<column name="accrued_till_date" type="date"><constraints nullable="true"/></column>
<column name="last_interest_calculation_date" type="date"><constraints nullable="true"/></column>
<column name="last_closed_business_date" type="date"><constraints nullable="true"/></column>
<!-- Tax -->
<column name="withhold_tax" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="tax_group_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_tax_group" referencedTableName="m_tax_group" referencedColumnNames="id"/>
</column>
<!-- Balance rules (copied from product) -->
<column name="min_required_opening_balance" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="lockin_period_frequency" type="smallint"><constraints nullable="true"/></column>
<column name="lockin_period_frequency_enum" type="smallint"><constraints nullable="true"/></column>
<column name="lockedin_until_date_derived" type="date"><constraints nullable="true"/></column>
<column name="enforce_min_required_balance" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="min_required_balance" type="decimal(19,6)"><constraints nullable="true"/></column>
<!-- Overdraft (copied from product) -->
<column name="allow_overdraft" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="overdraft_limit" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="nominal_annual_interest_rate_overdraft" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="min_overdraft_for_interest_calculation" type="decimal(19,6)"><constraints nullable="true"/></column>
<!-- Lien -->
<column name="is_lien_allowed" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="max_allowed_lien_limit" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="on_hold_funds_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<!-- Running balance summary -->
<column name="account_balance_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_deposits_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_withdrawals_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_interest_earned_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_interest_posted_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_overdraft_interest_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_withhold_tax_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_savings_amount_on_hold" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="withdrawal_fee_for_transfer" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<!-- Block reason -->
<column name="reason_for_block" type="varchar(500)"><constraints nullable="true"/></column>
<!-- Current-account-specific fields -->
<column name="msme" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="auto_debit_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="balance_swap_option" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="establishment_date" type="date"><constraints nullable="true"/></column>
<column name="start_operation_date" type="date"><constraints nullable="true"/></column>
<column name="is_non_individual_client" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="signatory_rights" type="varchar(100)"><constraints nullable="true"/></column>
<!-- CBS account type / segment -->
<column name="cbs_account_type" type="varchar(100)"><constraints nullable="true"/></column>
<column name="customer_segment" type="varchar(100)"><constraints nullable="true"/></column>
<column name="account_status" type="varchar(50)"><constraints nullable="true"/></column>
<column name="mode_of_operation" type="varchar(50)"><constraints nullable="true"/></column>
<column name="special_category" type="varchar(100)"><constraints nullable="true"/></column>
<!-- Introducer -->
<column name="introducer_client_id" type="bigint"><constraints nullable="true"/></column>
<column name="introducer_name" type="varchar(200)"><constraints nullable="true"/></column>
<column name="introducer_cif_number" type="varchar(50)"><constraints nullable="true"/></column>
<!-- Nominee (JSON) -->
<column name="nominee_data" type="text"><constraints nullable="true"/></column>
<!-- Interest calculation settings from product -->
<column name="interest_calculate_selector" type="smallint"><constraints nullable="true"/></column>
<column name="average_period" type="varchar(20)"><constraints nullable="true"/></column>
<column name="auto_post" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="frequency_fix_day" type="smallint"><constraints nullable="true"/></column>
<column name="enable_lockin_period" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="balance_frequency" type="varchar(50)"><constraints nullable="true"/></column>
<column name="avg_balance_calc_period" type="varchar(10)"><constraints nullable="true"/></column>
<!-- Audit -->
<column name="created_by" type="bigint"><constraints nullable="true"/></column>
<column name="created_date" type="datetime"><constraints nullable="true"/></column>
<column name="last_modified_by" type="bigint"><constraints nullable="true"/></column>
<column name="last_modified_date" type="datetime"><constraints nullable="true"/></column>
</createTable>
<!-- =========================================================
m_current_account_transaction
========================================================= -->
<createTable tableName="m_current_account_transaction">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_cat_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="office_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_cat_office" referencedTableName="m_office" referencedColumnNames="id"/>
</column>
<column name="payment_detail_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_cat_payment" referencedTableName="m_payment_detail" referencedColumnNames="id"/>
</column>
<!-- Type: 1=Deposit, 2=Withdrawal, 3=Interest, 4=Charge, 5=ChargeWaiver, 6=Transfer, 7=Reversal -->
<column name="transaction_type_enum" type="smallint"><constraints nullable="false"/></column>
<column name="transaction_date" type="date"><constraints nullable="false"/></column>
<column name="amount" type="decimal(19,6)"><constraints nullable="false"/></column>
<column name="ref_no" type="varchar(100)"><constraints nullable="true"/></column>
<column name="remarks" type="varchar(500)"><constraints nullable="true"/></column>
<!-- State -->
<column name="is_reversed" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="is_reversal" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="original_transaction_id" type="bigint"><constraints nullable="true"/></column>
<column name="is_manual" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<!-- Balance -->
<column name="running_balance_derived" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="cumulative_balance_derived" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="balance_end_date_derived" type="date"><constraints nullable="true"/></column>
<column name="balance_number_of_days_derived" type="integer"><constraints nullable="true"/></column>
<column name="overdraft_amount_derived" type="decimal(19,6)"><constraints nullable="true"/></column>
<!-- Lien / Hold -->
<column name="is_lien_transaction" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="release_id_of_hold_amount_transaction" type="bigint"><constraints nullable="true"/></column>
<column name="reason_for_block" type="varchar(200)"><constraints nullable="true"/></column>
<column name="submitted_on_date" type="date"><constraints nullable="true"/></column>
<column name="created_date" type="datetime"><constraints nullable="true"/></column>
<column name="created_by" type="bigint"><constraints nullable="true"/></column>
</createTable>
<!-- =========================================================
m_current_account_charge
========================================================= -->
<createTable tableName="m_current_account_charge">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_cac_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="charge_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_cac_charge" referencedTableName="m_charge" referencedColumnNames="id"/>
</column>
<column name="charge_time_enum" type="smallint"><constraints nullable="false"/></column>
<column name="charge_due_date" type="date"><constraints nullable="true"/></column>
<column name="fee_on_month" type="smallint"><constraints nullable="true"/></column>
<column name="fee_on_day" type="smallint"><constraints nullable="true"/></column>
<column name="fee_interval" type="smallint"><constraints nullable="true"/></column>
<column name="charge_calculation_enum" type="smallint"><constraints nullable="false"/></column>
<column name="calculation_percentage" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="calculation_on_amount" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="amount" type="decimal(19,6)"><constraints nullable="false"/></column>
<column name="amount_paid_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="amount_waived_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="amount_writtenoff_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="amount_outstanding_derived" type="decimal(19,6)"><constraints nullable="false"/></column>
<column name="is_paid_derived" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="waived" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="is_penalty" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="is_active" type="boolean" defaultValueBoolean="true"><constraints nullable="false"/></column>
<column name="inactivated_on_date" type="date"><constraints nullable="true"/></column>
<column name="free_withdrawal_count" type="integer"><constraints nullable="true"/></column>
<column name="charge_reset_date" type="date"><constraints nullable="true"/></column>
</createTable>
<!-- =========================================================
m_current_account_facility_config
ATM / NEFT / RTGS / Cheque Book etc.
========================================================= -->
<createTable tableName="m_current_account_facility_config">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" unique="true" uniqueConstraintName="ca_fac_unq_account"
foreignKeyName="fk_cafac_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="atm_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="atm_withdrawal_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="upi_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="pos_transactions_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="cheque_book" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="imps_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="sms_alerts" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="bbps_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="insurance_cover" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="bepg_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="neft_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="rtgs_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="inter_bank_total_cap_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="withdrawal_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="deposit_facility_kyc" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="virtual_debit_card" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="internet_banking" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="mobile_banking" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
</createTable>
<!-- =========================================================
m_current_account_joint_holder
Joint account holders
========================================================= -->
<createTable tableName="m_current_account_joint_holder">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_cajh_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="client_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_cajh_client" referencedTableName="m_client" referencedColumnNames="id"/>
</column>
<column name="cif_number" type="varchar(50)"><constraints nullable="true"/></column>
<column name="display_name" type="varchar(200)"><constraints nullable="true"/></column>
<column name="share_percentage" type="decimal(5,2)"><constraints nullable="true"/></column>
<column name="signing_authority" type="varchar(100)"><constraints nullable="true"/></column>
<column name="role" type="varchar(100)"><constraints nullable="true"/></column>
<column name="is_primary" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
</createTable>
<!-- =========================================================
m_current_account_partner
Partners for non-individual (business) current accounts
========================================================= -->
<createTable tableName="m_current_account_partner">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_cap_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="client_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_cap_client" referencedTableName="m_client" referencedColumnNames="id"/>
</column>
<column name="client_cif" type="varchar(50)"><constraints nullable="true"/></column>
<column name="client_name" type="varchar(200)"><constraints nullable="true"/></column>
<column name="share_percentage" type="decimal(5,2)"><constraints nullable="true"/></column>
<column name="is_mandatory" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
</createTable>
<!-- =========================================================
m_current_account_officer_history
Officer assignment history
========================================================= -->
<createTable tableName="m_current_account_officer_history">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_caoh_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="staff_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_caoh_staff" referencedTableName="m_staff" referencedColumnNames="id"/>
</column>
<column name="start_date" type="date"><constraints nullable="false"/></column>
<column name="end_date" type="date"><constraints nullable="true"/></column>
<column name="created_by_id" type="bigint"><constraints nullable="true"/></column>
</createTable>
<!-- =========================================================
m_current_account_on_hold_transaction
On-hold / lien funds
========================================================= -->
<createTable tableName="m_current_account_on_hold_transaction">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_caoht_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="amount" type="decimal(19,6)"><constraints nullable="false"/></column>
<!-- 1=Hold, 2=Release -->
<column name="transaction_type_enum" type="smallint"><constraints nullable="false"/></column>
<column name="transaction_date" type="date"><constraints nullable="false"/></column>
<column name="is_reversed" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
</createTable>
<!-- Indexes for fast lookup -->
<createIndex indexName="idx_ca_client_id" tableName="m_current_account">
<column name="client_id"/>
</createIndex>
<createIndex indexName="idx_ca_product_id" tableName="m_current_account">
<column name="product_id"/>
</createIndex>
<createIndex indexName="idx_ca_status" tableName="m_current_account">
<column name="status_enum"/>
</createIndex>
<createIndex indexName="idx_cat_account_id" tableName="m_current_account_transaction">
<column name="current_account_id"/>
</createIndex>
<createIndex indexName="idx_cat_date" tableName="m_current_account_transaction">
<column name="transaction_date"/>
</createIndex>
</changeSet>
</databaseChangeLog>
@@ -151,6 +151,15 @@
<class>org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMapping</class>
<class>org.apache.fineract.portfolio.self.registration.domain.SelfServiceRegistration</class>
<!-- Current Account Entities -->
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountCharge</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountJointHolder</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountPartner</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountOfficerHistory</class>
<!-- Charge Module Entities -->
<class>org.apache.fineract.portfolio.charge.domain.Charge</class>
@@ -21,7 +21,7 @@ package org.apache.fineract.portfolio.savings.domain;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
@Entity
@Entity(name = "SavingsCurrentAccount")
@DiscriminatorValue("400")
public class CurrentAccount extends SavingsAccount {
@@ -115,6 +115,15 @@
<class>org.apache.fineract.portfolio.savings.domain.SavingsAccountFacilityConfig</class>
<class>org.apache.fineract.portfolio.savings.domain.SavingsAccountJointHolder</class>
<!-- Current Account Entities -->
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountCharge</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountJointHolder</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountPartner</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountOfficerHistory</class>
<!-- Charge Module Entities -->
<class>org.apache.fineract.portfolio.charge.domain.Charge</class>