charges add module add in the saving account and current account

This commit is contained in:
Nitesh Yadav
2026-05-16 21:20:30 +05:30
parent 28fa6d9615
commit d0c8be4ee9
29 changed files with 91869 additions and 2607 deletions
@@ -65,7 +65,7 @@ public class SavingsAccountConstant extends SavingsApiConstants {
Arrays.asList(idParamName, accountNoParamName));
protected static final Set<String> SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(localeParamName, dateFormatParamName, activatedOnDateParamName));
Arrays.asList(localeParamName, dateFormatParamName, activatedOnDateParamName, "openingBalance"));
protected static final Set<String> SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(localeParamName, dateFormatParamName, closedOnDateParamName, noteParamName, paymentTypeIdParamName,
@@ -0,0 +1,66 @@
/**
* 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.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
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.portfolio.charge.domain.Charge;
@Entity
@Table(name = "m_facility_charge_mapping")
@Getter
@Setter
public class FacilityChargeMapping {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "facility_type", nullable = false, unique = true, length = 60)
private String facilityType;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "charge_id")
private Charge charge;
@Column(name = "charge_name", nullable = false, length = 200)
private String chargeName;
@Column(name = "default_amount", nullable = false, precision = 19, scale = 6)
private BigDecimal defaultAmount;
@Column(name = "charge_time_type", nullable = false)
private Integer chargeTimeType;
@Column(name = "charge_applies_to", nullable = false)
private Integer chargeAppliesTo;
@Column(name = "is_active", nullable = false)
private Boolean isActive = true;
}
@@ -0,0 +1,30 @@
/**
* 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.domain;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface FacilityChargeMappingRepository extends JpaRepository<FacilityChargeMapping, Long> {
Optional<FacilityChargeMapping> findByFacilityType(String facilityType);
List<FacilityChargeMapping> findAllByIsActiveTrue();
}
@@ -3487,6 +3487,11 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom<Long>
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
}
public void recalculateBalancesAfterFacilityCharges(final LocalDate transactionDate) {
recalculateDailyBalances(Money.zero(this.currency), transactionDate, false, false);
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
}
public void setSubStatusDormant() {
this.sub_status = SavingsAccountSubStatusEnum.DORMANT.getValue();
}
@@ -377,7 +377,6 @@ public class SavingsProductAssembler {
facilityConfigJson.get("depositFacilityKyc").getAsBoolean() : false);
config.setVirtualDebitCard(facilityConfigJson.has("virtualDebitCard") ?
facilityConfigJson.get("virtualDebitCard").getAsBoolean() : false);
return config;
}
@@ -95,146 +95,58 @@ public class SavingsProductFacilityConfig extends AbstractPersistableCustom<Long
this.savingsProduct = savingsProduct;
}
// Getters and setters
public SavingsProduct getSavingsProduct() {
return savingsProduct;
}
public SavingsProduct getSavingsProduct() { return savingsProduct; }
public void setSavingsProduct(SavingsProduct savingsProduct) { this.savingsProduct = savingsProduct; }
public void setSavingsProduct(SavingsProduct savingsProduct) {
this.savingsProduct = savingsProduct;
}
public Boolean getAtmFacility() { return atmFacility; }
public void setAtmFacility(Boolean v) { this.atmFacility = v != null ? v : false; }
public Boolean getAtmFacility() {
return atmFacility;
}
public Boolean getAtmWithdrawalFacility() { return atmWithdrawalFacility; }
public void setAtmWithdrawalFacility(Boolean v) { this.atmWithdrawalFacility = v != null ? v : false; }
public void setAtmFacility(Boolean atmFacility) {
this.atmFacility = atmFacility != null ? atmFacility : false;
}
public Boolean getUpiFacility() { return upiFacility; }
public void setUpiFacility(Boolean v) { this.upiFacility = v != null ? v : false; }
public Boolean getAtmWithdrawalFacility() {
return atmWithdrawalFacility;
}
public Boolean getPosTransactionsFacility() { return posTransactionsFacility; }
public void setPosTransactionsFacility(Boolean v) { this.posTransactionsFacility = v != null ? v : false; }
public void setAtmWithdrawalFacility(Boolean atmWithdrawalFacility) {
this.atmWithdrawalFacility = atmWithdrawalFacility != null ? atmWithdrawalFacility : false;
}
public Boolean getChequeBook() { return chequeBook; }
public void setChequeBook(Boolean v) { this.chequeBook = v != null ? v : false; }
public Boolean getUpiFacility() {
return upiFacility;
}
public Boolean getImpsFacility() { return impsFacility; }
public void setImpsFacility(Boolean v) { this.impsFacility = v != null ? v : false; }
public void setUpiFacility(Boolean upiFacility) {
this.upiFacility = upiFacility != null ? upiFacility : false;
}
public Boolean getSmsAlerts() { return smsAlerts; }
public void setSmsAlerts(Boolean v) { this.smsAlerts = v != null ? v : false; }
public Boolean getPosTransactionsFacility() {
return posTransactionsFacility;
}
public Boolean getBbpsFacility() { return bbpsFacility; }
public void setBbpsFacility(Boolean v) { this.bbpsFacility = v != null ? v : false; }
public void setPosTransactionsFacility(Boolean posTransactionsFacility) {
this.posTransactionsFacility = posTransactionsFacility != null ? posTransactionsFacility : false;
}
public Boolean getInsuranceCover() { return insuranceCover; }
public void setInsuranceCover(Boolean v) { this.insuranceCover = v != null ? v : false; }
public Boolean getChequeBook() {
return chequeBook;
}
public Boolean getBepgFacility() { return bepgFacility; }
public void setBepgFacility(Boolean v) { this.bepgFacility = v != null ? v : false; }
public void setChequeBook(Boolean chequeBook) {
this.chequeBook = chequeBook != null ? chequeBook : false;
}
public Boolean getNeftFacility() { return neftFacility; }
public void setNeftFacility(Boolean v) { this.neftFacility = v != null ? v : false; }
public Boolean getImpsFacility() {
return impsFacility;
}
public Boolean getRtgsFacility() { return rtgsFacility; }
public void setRtgsFacility(Boolean v) { this.rtgsFacility = v != null ? v : false; }
public void setImpsFacility(Boolean impsFacility) {
this.impsFacility = impsFacility != null ? impsFacility : false;
}
public Boolean getInterBankTotalCapFacility() { return interBankTotalCapFacility; }
public void setInterBankTotalCapFacility(Boolean v) { this.interBankTotalCapFacility = v != null ? v : false; }
public Boolean getSmsAlerts() {
return smsAlerts;
}
public Boolean getWithdrawalFacility() { return withdrawalFacility; }
public void setWithdrawalFacility(Boolean v) { this.withdrawalFacility = v != null ? v : false; }
public void setSmsAlerts(Boolean smsAlerts) {
this.smsAlerts = smsAlerts != null ? smsAlerts : false;
}
public Boolean getDepositFacilityKyc() { return depositFacilityKyc; }
public void setDepositFacilityKyc(Boolean v) { this.depositFacilityKyc = v != null ? v : false; }
public Boolean getBbpsFacility() {
return bbpsFacility;
}
public Boolean getVirtualDebitCard() { return virtualDebitCard; }
public void setVirtualDebitCard(Boolean v) { this.virtualDebitCard = v != null ? v : false; }
public void setBbpsFacility(Boolean bbpsFacility) {
this.bbpsFacility = bbpsFacility != null ? bbpsFacility : false;
}
public Boolean getInsuranceCover() {
return insuranceCover;
}
public void setInsuranceCover(Boolean insuranceCover) {
this.insuranceCover = insuranceCover != null ? insuranceCover : false;
}
public Boolean getBepgFacility() {
return bepgFacility;
}
public void setBepgFacility(Boolean bepgFacility) {
this.bepgFacility = bepgFacility != null ? bepgFacility : false;
}
public Boolean getNeftFacility() {
return neftFacility;
}
public void setNeftFacility(Boolean neftFacility) {
this.neftFacility = neftFacility != null ? neftFacility : false;
}
public Boolean getRtgsFacility() {
return rtgsFacility;
}
public void setRtgsFacility(Boolean rtgsFacility) {
this.rtgsFacility = rtgsFacility != null ? rtgsFacility : false;
}
public Boolean getInterBankTotalCapFacility() {
return interBankTotalCapFacility;
}
public void setInterBankTotalCapFacility(Boolean interBankTotalCapFacility) {
this.interBankTotalCapFacility = interBankTotalCapFacility != null ? interBankTotalCapFacility : false;
}
public Boolean getWithdrawalFacility() {
return withdrawalFacility;
}
public void setWithdrawalFacility(Boolean withdrawalFacility) {
this.withdrawalFacility = withdrawalFacility != null ? withdrawalFacility : false;
}
public Boolean getDepositFacilityKyc() {
return depositFacilityKyc;
}
public void setDepositFacilityKyc(Boolean depositFacilityKyc) {
this.depositFacilityKyc = depositFacilityKyc != null ? depositFacilityKyc : false;
}
public Boolean getVirtualDebitCard() {
return virtualDebitCard;
}
public void setVirtualDebitCard(Boolean virtualDebitCard) {
this.virtualDebitCard = virtualDebitCard != null ? virtualDebitCard : false;
}
public LocalDateTime getCreatedDate() {
return createdDate;
}
public LocalDateTime getCreatedDate() { return createdDate; }
public void updateFrom(SavingsProductFacilityConfig other) {
this.atmFacility = other.getAtmFacility();
@@ -254,4 +166,4 @@ public class SavingsProductFacilityConfig extends AbstractPersistableCustom<Long
this.depositFacilityKyc = other.getDepositFacilityKyc();
this.virtualDebitCard = other.getVirtualDebitCard();
}
}
}
@@ -0,0 +1,239 @@
/**
* 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 java.math.BigDecimal;
import java.time.LocalDate;
import java.time.MonthDay;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.savings.domain.FacilityChargeMapping;
import org.apache.fineract.portfolio.savings.domain.FacilityChargeMappingRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountCharge;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargeRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountFacilityConfig;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfig;
import org.springframework.stereotype.Service;
/**
* Reads account's facility config (Yes/No per facility) and auto-applies
* the corresponding charge from m_facility_charge_mapping.
* No manual charge selection needed — fully automatic.
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class FacilityChargeAutoApplyService {
private final FacilityChargeMappingRepository mappingRepository;
private final SavingsAccountChargeRepository savingsAccountChargeRepository;
private final SavingsAccountTransactionRepository savingsAccountTransactionRepository;
public void applyFacilityChargesOnActivation(final SavingsAccount account) {
// Use account-level facility config — this reflects what was selected at account opening time
final SavingsAccountFacilityConfig config = account.getFacilityConfig();
if (config == null) {
// Fallback: use product-level config if account config not set
applyFromProductConfig(account);
return;
}
final List<SavingsAccountCharge> chargesToApply = new ArrayList<>();
applyIfEnabled(account, Boolean.TRUE.equals(config.getAtmFacility()), "ATM_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getAtmWithdrawalFacility()), "ATM_WITHDRAWAL", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getUpiFacility()), "UPI_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getPosTransactionsFacility()), "POS_TRANSACTIONS", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getChequeBook()), "CHEQUE_BOOK", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getImpsFacility()), "IMPS_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getSmsAlerts()), "SMS_ALERTS", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getBbpsFacility()), "BBPS_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getInsuranceCover()), "INSURANCE_COVER", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getBepgFacility()), "BEPG_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getNeftFacility()), "NEFT_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getRtgsFacility()), "RTGS_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getInterBankTotalCapFacility()),"INTER_BANK_TOTAL_CAP",chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getWithdrawalFacility()), "WITHDRAWAL_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getDepositFacilityKyc()), "DEPOSIT_FACILITY_KYC",chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getVirtualDebitCard()), "VIRTUAL_DEBIT_CARD", chargesToApply);
if (!chargesToApply.isEmpty()) {
savingsAccountChargeRepository.saveAll(chargesToApply);
log.info("Auto-applied {} facility charge(s) to savings account id={}",
chargesToApply.size(), account.getId());
}
}
/**
* Collects all unpaid SavingsActivation charges on the account.
* Called after facility charges are saved and flushed, so amounts are properly initialized.
*/
public List<SavingsAccountTransaction> collectActivationCharges(final SavingsAccount account) {
final List<SavingsAccountTransaction> transactions = new ArrayList<>();
final LocalDate activationDate = account.getActivationDate();
for (final SavingsAccountCharge charge : account.charges()) {
if (!charge.isSavingsActivation()) continue;
if (charge.isPaid() || charge.isWaived()) continue;
final BigDecimal amount = charge.getAmount(account.getCurrency()).getAmount();
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) continue;
try {
final org.apache.fineract.organisation.monetary.domain.Money amountMoney =
org.apache.fineract.organisation.monetary.domain.Money.of(account.getCurrency(), amount);
final SavingsAccountTransaction txn = account.payCharge(charge, amountMoney, activationDate, false, null);
if (txn != null) {
transactions.add(txn);
log.info("Collected activation charge '{}' ₹{} from savings account id={}",
charge.getCharge().getName(), amount, account.getId());
}
} catch (Exception e) {
log.warn("Could not collect activation charge '{}' from savings account id={}: {}",
charge.getCharge() != null ? charge.getCharge().getName() : "?", account.getId(), e.getMessage());
}
}
if (!transactions.isEmpty()) {
savingsAccountTransactionRepository.saveAll(transactions);
account.recalculateBalancesAfterFacilityCharges(activationDate);
}
return transactions;
}
private void autoCollectSavingsActivationCharges(final SavingsAccount account,
final List<SavingsAccountCharge> charges) {
final LocalDate activationDate = account.getActivationDate();
final List<SavingsAccountTransaction> transactions = new ArrayList<>();
for (final SavingsAccountCharge charge : charges) {
if (!charge.isSavingsActivation()) continue;
final BigDecimal amount = charge.getAmountOutstanding(account.getCurrency()).getAmount();
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) continue;
try {
final Money amountMoney = Money.of(account.getCurrency(), amount);
final SavingsAccountTransaction txn = account.payCharge(charge, amountMoney, activationDate, false, null);
if (txn != null) transactions.add(txn);
log.info("Auto-collected facility charge '{}' ₹{} from account id={}",
charge.getCharge().getName(), amount, account.getId());
} catch (Exception e) {
log.warn("Could not auto-collect facility charge '{}' from account id={}: {}",
charge.getCharge().getName(), account.getId(), e.getMessage());
}
}
if (!transactions.isEmpty()) {
savingsAccountTransactionRepository.saveAll(transactions);
account.recalculateBalancesAfterFacilityCharges(activationDate);
}
}
private void applyIfEnabled(final SavingsAccount account, final boolean facilityEnabled,
final String facilityType, final List<SavingsAccountCharge> collector) {
if (!facilityEnabled) return;
// Skip if this charge is already applied to the account
boolean alreadyApplied = account.charges().stream()
.anyMatch(c -> c.getCharge() != null
&& c.getCharge().getName() != null
&& c.getCharge().getName().equals(getChargeName(facilityType)));
if (alreadyApplied) return;
mappingRepository.findByFacilityType(facilityType).ifPresent(mapping -> {
final Charge charge = mapping.getCharge();
if (charge == null) return;
final MonthDay feeOnMonthDay = resolveFeeOnMonthDay(charge);
final SavingsAccountCharge accountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(
charge,
charge.getAmount(),
null, // chargeTime derived from Charge definition
null, // chargeCalculation derived from Charge definition
resolveDueDate(charge),
true,
feeOnMonthDay,
charge.getFeeInterval());
accountCharge.update(account);
account.charges().add(accountCharge);
collector.add(accountCharge);
});
}
private MonthDay resolveFeeOnMonthDay(final Charge charge) {
if (charge.getFeeOnMonthDay() != null) return charge.getFeeOnMonthDay();
if (charge.isMonthlyFee() || charge.isAnnualFee()) {
// Default: 1st of the month
return MonthDay.of(LocalDate.now().plusMonths(1).getMonth(), 1);
}
return null;
}
private LocalDate resolveDueDate(final Charge charge) {
if (charge.isMonthlyFee()) {
return LocalDate.now().plusMonths(1).withDayOfMonth(1);
} else if (charge.isAnnualFee()) {
return LocalDate.now().plusYears(1);
}
return null;
}
private String getChargeName(final String facilityType) {
return mappingRepository.findByFacilityType(facilityType)
.map(FacilityChargeMapping::getChargeName)
.orElse("");
}
private void applyFromProductConfig(final SavingsAccount account) {
final SavingsProductFacilityConfig config = account.savingsProduct() != null
? account.savingsProduct().getFacilityConfig() : null;
if (config == null) return;
final List<SavingsAccountCharge> chargesToApply = new ArrayList<>();
applyIfEnabled(account, Boolean.TRUE.equals(config.getAtmFacility()), "ATM_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getAtmWithdrawalFacility()), "ATM_WITHDRAWAL", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getUpiFacility()), "UPI_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getPosTransactionsFacility()), "POS_TRANSACTIONS", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getChequeBook()), "CHEQUE_BOOK", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getImpsFacility()), "IMPS_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getSmsAlerts()), "SMS_ALERTS", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getBbpsFacility()), "BBPS_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getInsuranceCover()), "INSURANCE_COVER", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getBepgFacility()), "BEPG_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getNeftFacility()), "NEFT_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getRtgsFacility()), "RTGS_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getInterBankTotalCapFacility()),"INTER_BANK_TOTAL_CAP",chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getWithdrawalFacility()), "WITHDRAWAL_FACILITY", chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getDepositFacilityKyc()), "DEPOSIT_FACILITY_KYC",chargesToApply);
applyIfEnabled(account, Boolean.TRUE.equals(config.getVirtualDebitCard()), "VIRTUAL_DEBIT_CARD", chargesToApply);
if (!chargesToApply.isEmpty()) {
savingsAccountChargeRepository.saveAll(chargesToApply);
}
}
}
@@ -0,0 +1,160 @@
/**
* 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 jakarta.annotation.PostConstruct;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* Seeds default facility charges on service startup via JDBC.
* For each facility type (ATM, UPI, NEFT etc.), ensures a charge exists in m_charge
* and a mapping exists in m_facility_charge_mapping.
* Bank staff can change amounts from the Charges admin screen later.
* Seeder only creates records that are missing — safe to run repeatedly.
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class FacilityChargeSeederService {
private final JdbcTemplate jdbcTemplate;
// facilityType → [chargeName, defaultAmount, chargeTimeType, chargeAppliesTo]
// chargeTimeType: 3=SAVINGS_ACTIVATION, 6=ANNUAL_FEE, 7=MONTHLY_FEE
// chargeAppliesTo: 2=SAVINGS, 5=CURRENT_ACCOUNT
// chargeCalculation: 1=FLAT
private static final Map<String, Object[]> FACILITY_DEFAULTS = new LinkedHashMap<>();
static {
FACILITY_DEFAULTS.put("ATM_FACILITY", new Object[]{"ATM Facility Charge", 0.00, 3, 2});
FACILITY_DEFAULTS.put("ATM_WITHDRAWAL", new Object[]{"ATM Withdrawal Charge", 20.00, 3, 2});
FACILITY_DEFAULTS.put("UPI_FACILITY", new Object[]{"UPI Facility Charge", 0.00, 3, 2});
FACILITY_DEFAULTS.put("POS_TRANSACTIONS", new Object[]{"POS Transaction Charge", 0.00, 3, 2});
FACILITY_DEFAULTS.put("CHEQUE_BOOK", new Object[]{"Cheque Book Charge", 300.00, 3, 2});
FACILITY_DEFAULTS.put("IMPS_FACILITY", new Object[]{"IMPS Facility Charge", 5.00, 3, 2});
FACILITY_DEFAULTS.put("SMS_ALERTS", new Object[]{"SMS Alert Monthly Charge", 15.00, 7, 2});
FACILITY_DEFAULTS.put("BBPS_FACILITY", new Object[]{"BBPS Facility Charge", 2.00, 3, 2});
FACILITY_DEFAULTS.put("INSURANCE_COVER", new Object[]{"Insurance Cover Charge", 500.00, 6, 2});
FACILITY_DEFAULTS.put("BEPG_FACILITY", new Object[]{"BEPG Facility Charge", 5.00, 3, 2});
FACILITY_DEFAULTS.put("NEFT_FACILITY", new Object[]{"NEFT Facility Charge", 5.00, 3, 2});
FACILITY_DEFAULTS.put("RTGS_FACILITY", new Object[]{"RTGS Facility Charge", 25.00, 3, 2});
FACILITY_DEFAULTS.put("INTER_BANK_TOTAL_CAP", new Object[]{"Inter-Bank Cap Charge", 0.00, 3, 2});
FACILITY_DEFAULTS.put("WITHDRAWAL_FACILITY", new Object[]{"Withdrawal Facility Charge", 0.00, 3, 2});
FACILITY_DEFAULTS.put("DEPOSIT_FACILITY_KYC", new Object[]{"Deposit KYC Charge", 0.00, 3, 2});
FACILITY_DEFAULTS.put("VIRTUAL_DEBIT_CARD", new Object[]{"Virtual Debit Card Charge", 199.00, 3, 2});
}
@PostConstruct
@Transactional
public void seedFacilityCharges() {
try {
// Find an income GL account to link charges (prefer DETAIL type income account)
Long incomeGlAccountId = findIncomeGlAccountId();
for (Map.Entry<String, Object[]> entry : FACILITY_DEFAULTS.entrySet()) {
final String facilityType = entry.getKey();
final Object[] def = entry.getValue();
final String chargeName = (String) def[0];
final double amount = (double) def[1];
final int chargeTimeType = (int) def[2];
final int chargeAppliesTo = (int) def[3];
// 1. Check if mapping row exists and already has a charge linked
List<Long> existingChargeId = jdbcTemplate.queryForList(
"SELECT charge_id FROM m_facility_charge_mapping WHERE facility_type = ? AND charge_id IS NOT NULL",
Long.class, facilityType);
if (!existingChargeId.isEmpty()) {
continue; // Already seeded
}
// 2. Check if charge with this name already exists in m_charge
List<Long> chargeIds = jdbcTemplate.queryForList(
"SELECT id FROM m_charge WHERE name = ? AND is_deleted = false LIMIT 1",
Long.class, chargeName);
Long chargeId;
if (chargeIds.isEmpty()) {
// 3. Insert charge into m_charge
jdbcTemplate.update(
"INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, "
+ "charge_time_enum, charge_calculation_enum, is_penalty, is_active, is_deleted, "
+ "is_free_withdrawal, is_payment_type, income_or_liability_account_id) "
+ "VALUES (?, 'INR', ?, ?, ?, true, false, true, false, false, false, ?)",
chargeName, amount, chargeAppliesTo, chargeTimeType, incomeGlAccountId);
chargeId = jdbcTemplate.queryForObject(
"SELECT id FROM m_charge WHERE name = ? AND is_deleted = false ORDER BY id DESC LIMIT 1",
Long.class, chargeName);
log.info("FacilityChargeSeeder: inserted charge '{}' id={}", chargeName, chargeId);
} else {
chargeId = chargeIds.get(0);
}
// 4. Update or insert mapping row
int updated = jdbcTemplate.update(
"UPDATE m_facility_charge_mapping SET charge_id = ? WHERE facility_type = ?",
chargeId, facilityType);
if (updated == 0) {
jdbcTemplate.update(
"INSERT INTO m_facility_charge_mapping "
+ "(facility_type, charge_id, charge_name, default_amount, charge_time_type, charge_applies_to, is_active) "
+ "VALUES (?, ?, ?, ?, ?, ?, 1)",
facilityType, chargeId, chargeName, amount, chargeTimeType, chargeAppliesTo);
}
log.info("FacilityChargeSeeder: mapped facility '{}' → charge id={}", facilityType, chargeId);
}
log.info("FacilityChargeSeeder: seeding complete.");
} catch (Exception e) {
log.warn("FacilityChargeSeeder: skipped — {}", e.getMessage());
}
}
private Long findIncomeGlAccountId() {
try {
// Prefer "INCOME FROM ALL" GL account for facility charges
List<Long> ids = jdbcTemplate.queryForList(
"SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 "
+ "AND disabled = false AND name = 'INCOME FROM ALL' LIMIT 1",
Long.class);
if (!ids.isEmpty()) return ids.get(0);
// Fallback: first available income detail account
ids = jdbcTemplate.queryForList(
"SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 "
+ "AND disabled = false ORDER BY id ASC LIMIT 1",
Long.class);
return ids.isEmpty() ? null : ids.get(0);
} catch (Exception e) {
return null;
}
}
}
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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.1.xsd">
<changeSet id="2012-current-account-permissions" author="lincpay">
<comment>Seed all current account permissions and assign to super-admin role</comment>
<sql>
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker) VALUES
('currentaccount', 'READ_CURRENTACCOUNT', 'CURRENTACCOUNT', 'READ', false),
('currentaccount', 'CREATE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'CREATE', false),
('currentaccount', 'UPDATE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'UPDATE', false),
('currentaccount', 'DELETE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'DELETE', false),
('currentaccount', 'APPROVE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'APPROVE', true),
('currentaccount', 'APPROVALUNDO_CURRENTACCOUNT', 'CURRENTACCOUNT', 'APPROVALUNDO', true),
('currentaccount', 'REJECT_CURRENTACCOUNT', 'CURRENTACCOUNT', 'REJECT', true),
('currentaccount', 'WITHDRAW_CURRENTACCOUNT', 'CURRENTACCOUNT', 'WITHDRAW', true),
('currentaccount', 'ACTIVATE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'ACTIVATE', true),
('currentaccount', 'CLOSE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'CLOSE', true),
('currentaccount', 'DEPOSIT_CURRENTACCOUNT', 'CURRENTACCOUNT', 'DEPOSIT', true),
('currentaccount', 'WITHDRAWAL_CURRENTACCOUNT', 'CURRENTACCOUNT', 'WITHDRAWAL', true),
('currentaccount', 'POSTINTEREST_CURRENTACCOUNT', 'CURRENTACCOUNT', 'POSTINTEREST', true),
('currentaccount', 'CALCULATEINTEREST_CURRENTACCOUNT', 'CURRENTACCOUNT', 'CALCULATEINTEREST', false),
('currentaccount', 'BLOCK_CURRENTACCOUNT', 'CURRENTACCOUNT', 'BLOCK', true),
('currentaccount', 'UNBLOCK_CURRENTACCOUNT', 'CURRENTACCOUNT', 'UNBLOCK', true),
('currentaccount', 'BLOCKDEPOSIT_CURRENTACCOUNT', 'CURRENTACCOUNT', 'BLOCKDEPOSIT', true),
('currentaccount', 'UNBLOCKDEPOSIT_CURRENTACCOUNT', 'CURRENTACCOUNT', 'UNBLOCKDEPOSIT', true),
('currentaccount', 'BLOCKWITHDRAWAL_CURRENTACCOUNT', 'CURRENTACCOUNT', 'BLOCKWITHDRAWAL', true),
('currentaccount', 'UNBLOCKWITHDRAWAL_CURRENTACCOUNT', 'CURRENTACCOUNT', 'UNBLOCKWITHDRAWAL', true),
('currentaccount', 'HOLDAMOUNT_CURRENTACCOUNT', 'CURRENTACCOUNT', 'HOLDAMOUNT', true),
('currentaccount', 'UPDATESAVINGSOFFICER_CURRENTACCOUNT','CURRENTACCOUNT', 'UPDATESAVINGSOFFICER',false),
('currentaccount', 'REMOVESAVINGSOFFICER_CURRENTACCOUNT','CURRENTACCOUNT', 'REMOVESAVINGSOFFICER',false),
('currentaccount', 'APPLYANNUALFEE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'APPLYANNUALFEE', true),
('currentaccount', 'UPDATEWITHHOLDTAX_CURRENTACCOUNT', 'CURRENTACCOUNT', 'UPDATEWITHHOLDTAX', true),
('currentaccount', 'CREATE_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'CREATE', true),
('currentaccount', 'READ_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'READ', false),
('currentaccount', 'UPDATE_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'UPDATE', true),
('currentaccount', 'DELETE_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'DELETE', true),
('currentaccount', 'PAY_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'PAY', true),
('currentaccount', 'WAIVE_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'WAIVE', true),
('currentaccount', 'INACTIVATE_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'INACTIVATE', true),
('currentaccount', 'READ_CURRENTACCOUNTNOTE', 'CURRENTACCOUNTNOTE', 'READ', false),
('currentaccount', 'CREATE_CURRENTACCOUNTNOTE', 'CURRENTACCOUNTNOTE', 'CREATE', false),
('currentaccount', 'UPDATE_CURRENTACCOUNTNOTE', 'CURRENTACCOUNTNOTE', 'UPDATE', false),
('currentaccount', 'DELETE_CURRENTACCOUNTNOTE', 'CURRENTACCOUNTNOTE', 'DELETE', false)
ON CONFLICT (code) DO NOTHING;
</sql>
<sql>
INSERT INTO m_role_permission (role_id, permission_id)
SELECT 1, id FROM m_permission
WHERE grouping = 'currentaccount'
ON CONFLICT DO NOTHING;
</sql>
<sql>
INSERT INTO m_role_permission (role_id, permission_id)
SELECT DISTINCT rp.role_id, p2.id
FROM m_role_permission rp
JOIN m_permission p1 ON rp.permission_id = p1.id AND p1.code = 'READ_SAVINGSACCOUNT'
JOIN m_permission p2 ON p2.grouping = 'currentaccount'
WHERE rp.role_id != 1
ON CONFLICT DO NOTHING;
</sql>
</changeSet>
</databaseChangeLog>
@@ -33,4 +33,7 @@
<include file="parts/2008_add_other_gl_mappings_to_savings_product.xml" relativeToChangelogFile="true" />
<include file="parts/2009_add_gl_account_id_to_savings_product.xml" relativeToChangelogFile="true" />
<include file="parts/2010_add_savings_account_cbs_fields.xml" relativeToChangelogFile="true" />
<include file="parts/2011_add_facility_charge_mapping.xml" relativeToChangelogFile="true" />
<include file="parts/2012_add_current_account_permissions.xml" relativeToChangelogFile="true" />
<include file="parts/2013_add_missing_current_account_columns.xml" relativeToChangelogFile="true" />
</databaseChangeLog>
@@ -0,0 +1,296 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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.1.xsd">
<!-- Step 1: Create facility charge mapping table -->
<changeSet id="2011-1" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_facility_charge_mapping"/></not>
</preConditions>
<createTable tableName="m_facility_charge_mapping">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="facility_type" type="VARCHAR(60)">
<constraints nullable="false" unique="true"/>
</column>
<column name="charge_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_facility_charge" references="m_charge(id)"/>
</column>
<column name="charge_name" type="VARCHAR(200)">
<constraints nullable="false"/>
</column>
<column name="default_amount" type="DECIMAL(19,6)" defaultValueNumeric="0.000000">
<constraints nullable="false"/>
</column>
<column name="charge_time_type" type="INT" defaultValueNumeric="3">
<constraints nullable="false"/>
</column>
<column name="charge_applies_to" type="INT" defaultValueNumeric="2">
<constraints nullable="false"/>
</column>
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<!-- Step 2: Insert facility charges into m_charge (only if not already present) -->
<!-- Note: PostgreSQL uses false/true for boolean columns, not 0/1 -->
<changeSet id="2011-2" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'ATM Facility Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'ATM Facility Charge', 'INR', 100.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-3" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'ATM Withdrawal Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'ATM Withdrawal Charge', 'INR', 20.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-4" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'UPI Facility Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'UPI Facility Charge', 'INR', 100.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-5" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'POS Transaction Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'POS Transaction Charge', 'INR', 100.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-6" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'Cheque Book Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'Cheque Book Charge', 'INR', 300.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-7" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'IMPS Facility Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'IMPS Facility Charge', 'INR', 5.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-8" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'SMS Alert Monthly Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'SMS Alert Monthly Charge', 'INR', 15.00, 2, 7, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-9" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'BBPS Facility Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'BBPS Facility Charge', 'INR', 2.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-10" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'Insurance Cover Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'Insurance Cover Charge', 'INR', 500.00, 2, 6, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-11" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'BEPG Facility Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'BEPG Facility Charge', 'INR', 5.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-12" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'NEFT Facility Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'NEFT Facility Charge', 'INR', 5.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-13" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'RTGS Facility Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'RTGS Facility Charge', 'INR', 25.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-14" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'Inter-Bank Cap Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'Inter-Bank Cap Charge', 'INR', 0.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-15" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'Withdrawal Facility Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'Withdrawal Facility Charge', 'INR', 5.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-16" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'Deposit KYC Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'Deposit KYC Charge', 'INR', 0.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<changeSet id="2011-17" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_charge WHERE name = 'Virtual Debit Card Charge' AND is_deleted = false</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_charge (name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted, is_free_withdrawal, is_payment_type,
income_or_liability_account_id)
SELECT 'Virtual Debit Card Charge', 'INR', 199.00, 2, 3, 1, false, true, false, false, false,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND account_usage = 1 AND disabled = false ORDER BY id LIMIT 1);
</sql>
</changeSet>
<!-- Step 3: Insert mapping rows linking facility_type → charge_id -->
<changeSet id="2011-18" author="cbs-nitesh">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_facility_charge_mapping WHERE facility_type = 'ATM_FACILITY'</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_facility_charge_mapping (facility_type, charge_name, default_amount, charge_time_type, charge_applies_to, charge_id, is_active)
VALUES
('ATM_FACILITY', 'ATM Facility Charge', 100.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'ATM Facility Charge' AND is_deleted = false LIMIT 1), true),
('ATM_WITHDRAWAL', 'ATM Withdrawal Charge', 20.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'ATM Withdrawal Charge' AND is_deleted = false LIMIT 1), true),
('UPI_FACILITY', 'UPI Facility Charge', 100.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'UPI Facility Charge' AND is_deleted = false LIMIT 1), true),
('POS_TRANSACTIONS', 'POS Transaction Charge', 100.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'POS Transaction Charge' AND is_deleted = false LIMIT 1), true),
('CHEQUE_BOOK', 'Cheque Book Charge', 300.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Cheque Book Charge' AND is_deleted = false LIMIT 1), true),
('IMPS_FACILITY', 'IMPS Facility Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'IMPS Facility Charge' AND is_deleted = false LIMIT 1), true),
('SMS_ALERTS', 'SMS Alert Monthly Charge', 15.00, 7, 2, (SELECT id FROM m_charge WHERE name = 'SMS Alert Monthly Charge' AND is_deleted = false LIMIT 1), true),
('BBPS_FACILITY', 'BBPS Facility Charge', 2.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'BBPS Facility Charge' AND is_deleted = false LIMIT 1), true),
('INSURANCE_COVER', 'Insurance Cover Charge', 500.00, 6, 2, (SELECT id FROM m_charge WHERE name = 'Insurance Cover Charge' AND is_deleted = false LIMIT 1), true),
('BEPG_FACILITY', 'BEPG Facility Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'BEPG Facility Charge' AND is_deleted = false LIMIT 1), true),
('NEFT_FACILITY', 'NEFT Facility Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'NEFT Facility Charge' AND is_deleted = false LIMIT 1), true),
('RTGS_FACILITY', 'RTGS Facility Charge', 25.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'RTGS Facility Charge' AND is_deleted = false LIMIT 1), true),
('INTER_BANK_TOTAL_CAP','Inter-Bank Cap Charge', 0.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Inter-Bank Cap Charge' AND is_deleted = false LIMIT 1), true),
('WITHDRAWAL_FACILITY', 'Withdrawal Facility Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Withdrawal Facility Charge' AND is_deleted = false LIMIT 1), true),
('DEPOSIT_FACILITY_KYC','Deposit KYC Charge', 0.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Deposit KYC Charge' AND is_deleted = false LIMIT 1), true),
('VIRTUAL_DEBIT_CARD', 'Virtual Debit Card Charge', 199.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Virtual Debit Card Charge' AND is_deleted = false LIMIT 1), true);
</sql>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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.1.xsd">
<changeSet id="2012-current-account-permissions" author="lincpay">
<comment>Seed all current account permissions and assign to super-admin role</comment>
<sql>
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker) VALUES
('currentaccount', 'READ_CURRENTACCOUNT', 'CURRENTACCOUNT', 'READ', false),
('currentaccount', 'CREATE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'CREATE', false),
('currentaccount', 'UPDATE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'UPDATE', false),
('currentaccount', 'DELETE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'DELETE', false),
('currentaccount', 'APPROVE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'APPROVE', true),
('currentaccount', 'APPROVALUNDO_CURRENTACCOUNT', 'CURRENTACCOUNT', 'APPROVALUNDO', true),
('currentaccount', 'REJECT_CURRENTACCOUNT', 'CURRENTACCOUNT', 'REJECT', true),
('currentaccount', 'WITHDRAW_CURRENTACCOUNT', 'CURRENTACCOUNT', 'WITHDRAW', true),
('currentaccount', 'ACTIVATE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'ACTIVATE', true),
('currentaccount', 'CLOSE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'CLOSE', true),
('currentaccount', 'DEPOSIT_CURRENTACCOUNT', 'CURRENTACCOUNT', 'DEPOSIT', true),
('currentaccount', 'WITHDRAWAL_CURRENTACCOUNT', 'CURRENTACCOUNT', 'WITHDRAWAL', true),
('currentaccount', 'POSTINTEREST_CURRENTACCOUNT', 'CURRENTACCOUNT', 'POSTINTEREST', true),
('currentaccount', 'CALCULATEINTEREST_CURRENTACCOUNT', 'CURRENTACCOUNT', 'CALCULATEINTEREST', false),
('currentaccount', 'BLOCK_CURRENTACCOUNT', 'CURRENTACCOUNT', 'BLOCK', true),
('currentaccount', 'UNBLOCK_CURRENTACCOUNT', 'CURRENTACCOUNT', 'UNBLOCK', true),
('currentaccount', 'BLOCKDEPOSIT_CURRENTACCOUNT', 'CURRENTACCOUNT', 'BLOCKDEPOSIT', true),
('currentaccount', 'UNBLOCKDEPOSIT_CURRENTACCOUNT', 'CURRENTACCOUNT', 'UNBLOCKDEPOSIT', true),
('currentaccount', 'BLOCKWITHDRAWAL_CURRENTACCOUNT', 'CURRENTACCOUNT', 'BLOCKWITHDRAWAL', true),
('currentaccount', 'UNBLOCKWITHDRAWAL_CURRENTACCOUNT', 'CURRENTACCOUNT', 'UNBLOCKWITHDRAWAL', true),
('currentaccount', 'HOLDAMOUNT_CURRENTACCOUNT', 'CURRENTACCOUNT', 'HOLDAMOUNT', true),
('currentaccount', 'UPDATESAVINGSOFFICER_CURRENTACCOUNT','CURRENTACCOUNT', 'UPDATESAVINGSOFFICER',false),
('currentaccount', 'REMOVESAVINGSOFFICER_CURRENTACCOUNT','CURRENTACCOUNT', 'REMOVESAVINGSOFFICER',false),
('currentaccount', 'APPLYANNUALFEE_CURRENTACCOUNT', 'CURRENTACCOUNT', 'APPLYANNUALFEE', true),
('currentaccount', 'UPDATEWITHHOLDTAX_CURRENTACCOUNT', 'CURRENTACCOUNT', 'UPDATEWITHHOLDTAX', true),
('currentaccount', 'CREATE_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'CREATE', true),
('currentaccount', 'READ_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'READ', false),
('currentaccount', 'UPDATE_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'UPDATE', true),
('currentaccount', 'DELETE_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'DELETE', true),
('currentaccount', 'PAY_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'PAY', true),
('currentaccount', 'WAIVE_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'WAIVE', true),
('currentaccount', 'INACTIVATE_CURRENTACCOUNTCHARGE', 'CURRENTACCOUNTCHARGE', 'INACTIVATE', true),
('currentaccount', 'READ_CURRENTACCOUNTNOTE', 'CURRENTACCOUNTNOTE', 'READ', false),
('currentaccount', 'CREATE_CURRENTACCOUNTNOTE', 'CURRENTACCOUNTNOTE', 'CREATE', false),
('currentaccount', 'UPDATE_CURRENTACCOUNTNOTE', 'CURRENTACCOUNTNOTE', 'UPDATE', false),
('currentaccount', 'DELETE_CURRENTACCOUNTNOTE', 'CURRENTACCOUNTNOTE', 'DELETE', false)
ON CONFLICT (code) DO NOTHING;
</sql>
<sql>
INSERT INTO m_role_permission (role_id, permission_id)
SELECT 1, id FROM m_permission
WHERE grouping = 'currentaccount'
ON CONFLICT DO NOTHING;
</sql>
<sql>
INSERT INTO m_role_permission (role_id, permission_id)
SELECT DISTINCT rp.role_id, p2.id
FROM m_role_permission rp
JOIN m_permission p1 ON rp.permission_id = p1.id AND p1.code = 'READ_SAVINGSACCOUNT'
JOIN m_permission p2 ON p2.grouping = 'currentaccount'
WHERE rp.role_id != 1
ON CONFLICT DO NOTHING;
</sql>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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.1.xsd">
<changeSet id="2013-add-missing-current-account-columns" author="fineract">
<preConditions onFail="MARK_RAN">
<not>
<columnExists tableName="m_current_account" columnName="account_closed_zero_balance_in_day_closing"/>
</not>
</preConditions>
<addColumn tableName="m_current_account">
<column name="account_closed_zero_balance_in_day_closing" type="boolean" defaultValueBoolean="false">
<constraints nullable="true"/>
</column>
<column name="valid_for_period" type="varchar(10)">
<constraints nullable="true"/>
</column>
<column name="od_account_closed_zero_balance_in_day_closing" type="boolean" defaultValueBoolean="false">
<constraints nullable="true"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>