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
@@ -0,0 +1,173 @@
-- ============================================================
-- Facility Charge Auto-Seed Migration
-- Step 1: Create mapping table
-- Step 2: Insert charges into m_charge
-- Step 3: Insert mapping with charge IDs
-- charge_applies_to_enum: 2=SAVINGS, 5=CURRENT_ACCOUNT
-- charge_time_enum: 3=SAVINGS_ACTIVATION, 6=ANNUAL_FEE, 7=MONTHLY_FEE
-- charge_calculation_enum: 1=FLAT
-- ============================================================
-- Step 1: Mapping table
CREATE TABLE IF NOT EXISTS m_facility_charge_mapping (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
facility_type VARCHAR(60) NOT NULL UNIQUE,
charge_id BIGINT,
charge_name VARCHAR(200) NOT NULL,
default_amount DECIMAL(19,6) NOT NULL DEFAULT 0.000000,
charge_time_type INT NOT NULL DEFAULT 3,
charge_applies_to INT NOT NULL DEFAULT 2,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
FOREIGN KEY (charge_id) REFERENCES m_charge(id)
);
-- Step 2: Insert charges into m_charge (only if not already exists)
-- income_or_liability_account_id = first available INCOME DETAIL GL account
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'ATM Facility Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'ATM Withdrawal Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'UPI Facility Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'POS Transaction Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'Cheque Book Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'IMPS Facility Charge' AND is_deleted = 0);
INSERT IGNORE 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 Charge', 'INR', 15.00, 2, 7, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'SMS Alert Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'BBPS Facility Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'Insurance Cover Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'BEPG Facility Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'NEFT Facility Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'RTGS Facility Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'Inter-Bank Cap Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'Withdrawal Facility Charge' AND is_deleted = 0);
INSERT IGNORE 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', 5.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'Deposit KYC Charge' AND is_deleted = 0);
INSERT IGNORE 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, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'Virtual Debit Card Charge' AND is_deleted = 0);
-- Step 3: Insert mapping linking facility_type → charge_id
INSERT IGNORE INTO m_facility_charge_mapping
(facility_type, charge_name, default_amount, charge_time_type, charge_applies_to, charge_id)
VALUES
('ATM_FACILITY', 'ATM Facility Charge', 100.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'ATM Facility Charge' AND is_deleted = 0 LIMIT 1)),
('ATM_WITHDRAWAL', 'ATM Withdrawal Charge', 20.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'ATM Withdrawal Charge' AND is_deleted = 0 LIMIT 1)),
('UPI_FACILITY', 'UPI Facility Charge', 100.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'UPI Facility Charge' AND is_deleted = 0 LIMIT 1)),
('POS_TRANSACTIONS', 'POS Transaction Charge', 100.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'POS Transaction Charge' AND is_deleted = 0 LIMIT 1)),
('CHEQUE_BOOK', 'Cheque Book Charge', 300.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Cheque Book Charge' AND is_deleted = 0 LIMIT 1)),
('IMPS_FACILITY', 'IMPS Facility Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'IMPS Facility Charge' AND is_deleted = 0 LIMIT 1)),
('SMS_ALERTS', 'SMS Alert Charge', 15.00, 7, 2, (SELECT id FROM m_charge WHERE name = 'SMS Alert Charge' AND is_deleted = 0 LIMIT 1)),
('BBPS_FACILITY', 'BBPS Facility Charge', 2.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'BBPS Facility Charge' AND is_deleted = 0 LIMIT 1)),
('INSURANCE_COVER', 'Insurance Cover Charge', 500.00, 6, 2, (SELECT id FROM m_charge WHERE name = 'Insurance Cover Charge' AND is_deleted = 0 LIMIT 1)),
('BEPG_FACILITY', 'BEPG Facility Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'BEPG Facility Charge' AND is_deleted = 0 LIMIT 1)),
('NEFT_FACILITY', 'NEFT Facility Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'NEFT Facility Charge' AND is_deleted = 0 LIMIT 1)),
('RTGS_FACILITY', 'RTGS Facility Charge', 25.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'RTGS Facility Charge' AND is_deleted = 0 LIMIT 1)),
('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 = 0 LIMIT 1)),
('WITHDRAWAL_FACILITY', 'Withdrawal Facility Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Withdrawal Facility Charge' AND is_deleted = 0 LIMIT 1)),
('DEPOSIT_FACILITY_KYC','Deposit KYC Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Deposit KYC Charge' AND is_deleted = 0 LIMIT 1)),
('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 = 0 LIMIT 1));
+79
View File
@@ -0,0 +1,79 @@
-- Migration: Add facility charge columns to product facility config tables
-- Each facility (ATM, UPI, IMPS, etc.) links to an existing m_charge entry
-- When account is activated, enabled facilities auto-apply their linked charge
-- Current Product Facility Config
ALTER TABLE m_current_product_facility_config
ADD COLUMN IF NOT EXISTS atm_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS atm_withdrawal_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS upi_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS pos_transactions_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS cheque_book_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS imps_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS sms_alerts_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS bbps_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS insurance_cover_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS bepg_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS neft_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS rtgs_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS inter_bank_total_cap_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS withdrawal_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS deposit_facility_kyc_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS virtual_debit_card_charge_id BIGINT NULL;
-- Foreign key constraints for current product
ALTER TABLE m_current_product_facility_config
ADD CONSTRAINT fk_cpfc_atm_charge FOREIGN KEY (atm_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_atm_wd_charge FOREIGN KEY (atm_withdrawal_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_upi_charge FOREIGN KEY (upi_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_pos_charge FOREIGN KEY (pos_transactions_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_chq_charge FOREIGN KEY (cheque_book_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_imps_charge FOREIGN KEY (imps_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_sms_charge FOREIGN KEY (sms_alerts_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_bbps_charge FOREIGN KEY (bbps_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_ins_charge FOREIGN KEY (insurance_cover_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_bepg_charge FOREIGN KEY (bepg_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_neft_charge FOREIGN KEY (neft_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_rtgs_charge FOREIGN KEY (rtgs_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_ibtc_charge FOREIGN KEY (inter_bank_total_cap_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_wd_charge FOREIGN KEY (withdrawal_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_dep_charge FOREIGN KEY (deposit_facility_kyc_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_vdc_charge FOREIGN KEY (virtual_debit_card_charge_id) REFERENCES m_charge(id);
-- Savings Product Facility Config
ALTER TABLE m_savings_product_facility_config
ADD COLUMN IF NOT EXISTS atm_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS atm_withdrawal_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS upi_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS pos_transactions_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS cheque_book_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS imps_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS sms_alerts_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS bbps_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS insurance_cover_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS bepg_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS neft_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS rtgs_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS inter_bank_total_cap_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS withdrawal_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS deposit_facility_kyc_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS virtual_debit_card_charge_id BIGINT NULL;
-- Foreign key constraints for savings product
ALTER TABLE m_savings_product_facility_config
ADD CONSTRAINT fk_spfc_atm_charge FOREIGN KEY (atm_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_atm_wd_charge FOREIGN KEY (atm_withdrawal_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_upi_charge FOREIGN KEY (upi_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_pos_charge FOREIGN KEY (pos_transactions_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_chq_charge FOREIGN KEY (cheque_book_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_imps_charge FOREIGN KEY (imps_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_sms_charge FOREIGN KEY (sms_alerts_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_bbps_charge FOREIGN KEY (bbps_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_ins_charge FOREIGN KEY (insurance_cover_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_bepg_charge FOREIGN KEY (bepg_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_neft_charge FOREIGN KEY (neft_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_rtgs_charge FOREIGN KEY (rtgs_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_ibtc_charge FOREIGN KEY (inter_bank_total_cap_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_wd_charge FOREIGN KEY (withdrawal_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_dep_charge FOREIGN KEY (deposit_facility_kyc_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_vdc_charge FOREIGN KEY (virtual_debit_card_charge_id) REFERENCES m_charge(id);
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.accounting.journalentry;
import java.time.OffsetDateTime;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
@@ -45,7 +46,7 @@ public interface JournalEntryMapper {
@Mapping(target = "amount", source = "amount")
@Mapping(target = "entityType", source = "entityType", qualifiedByName = "entityType")
@Mapping(target = "entityId", source = "entityId")
@Mapping(target = "submittedOnDate", source = "submittedOnDate")
@Mapping(target = "submittedOnDate", expression = "java(journalEntry.getCreatedDate().orElse(null))")
@Mapping(target = "transactionId", source = "transactionId")
@Mapping(target = "currency", source = "currencyCode")
@Mapping(target = "manualEntry", source = "manualEntry")
@@ -20,6 +20,7 @@ package org.apache.fineract.accounting.journalentry.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.List;
import lombok.Data;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
@@ -60,7 +61,7 @@ public class JournalEntryData {
@SuppressWarnings("unused")
private Long createdByUserId;
@SuppressWarnings("unused")
private LocalDate createdDate;
private OffsetDateTime createdDate;
@SuppressWarnings("unused")
private String createdByUserName;
@SuppressWarnings("unused")
@@ -79,7 +80,7 @@ public class JournalEntryData {
@SuppressWarnings("unused")
private TransactionDetailData transactionDetails;
@SuppressWarnings("unused")
private LocalDate submittedOnDate;
private OffsetDateTime submittedOnDate;
// import fields
private transient Integer rowIndex;
@@ -189,7 +190,7 @@ public class JournalEntryData {
public JournalEntryData(final Long id, final Long officeId, final String officeName, final String glAccountName, final Long glAccountId,
final String glAccountCode, final EnumOptionData glAccountClassification, final LocalDate transactionDate,
final EnumOptionData entryType, final BigDecimal amount, final String transactionId, final Boolean manualEntry,
final EnumOptionData entityType, final Long entityId, final Long createdByUserId, final LocalDate submittedOnDate,
final EnumOptionData entityType, final Long entityId, final Long createdByUserId, final OffsetDateTime submittedOnDate,
final String createdByUserName, final String comments, final Boolean reversed, final String referenceNumber,
final BigDecimal officeRunningBalance, final BigDecimal organizationRunningBalance, final Boolean runningBalanceComputed,
final TransactionDetailData transactionDetailData, final CurrencyData currency, final String externalAssetOwner) {
@@ -251,7 +252,7 @@ public class JournalEntryData {
final EnumOptionData entityType = null;
final Long entityId = null;
final Long createdByUserId = null;
final LocalDate submittedOnDate = null;
final OffsetDateTime submittedOnDate = null;
final String createdByUserName = null;
final String comments = null;
final Boolean reversed = null;
@@ -291,6 +291,17 @@ public class CurrentAccount extends AbstractAuditableCustom {
@Column(name = "avg_balance_calc_period", length = 10)
private String avgBalanceCalcPeriod;
// Extra fields copied from product (not in m_savings_account dormancy set)
@Column(name = "account_closed_zero_balance_in_day_closing")
private Boolean accountClosedZeroBalanceInDayClosing = false;
@Column(name = "valid_for_period", length = 10)
private String validForPeriod;
@Column(name = "od_account_closed_zero_balance_in_day_closing")
private boolean odAccountClosedZeroBalanceInDayClosing = false;
// Relationships
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<CurrentAccountTransaction> transactions = new ArrayList<>();
@@ -383,4 +394,13 @@ public class CurrentAccount extends AbstractAuditableCustom {
this.statusEnum = 600;
this.closedOnDate = closedOnDate;
}
public void setMinRequiredOpeningBalance(java.math.BigDecimal val) { this.minRequiredOpeningBalance = val; }
public void setMinRequiredBalance(java.math.BigDecimal val) { this.minRequiredBalance = val; }
public void setEnforceMinRequiredBalance(boolean val) { this.enforceMinRequiredBalance = val; }
public void setLockinPeriodFrequency(Integer val) { this.lockinPeriodFrequency = val; }
public void setLockinPeriodFrequencyEnum(Integer val) { this.lockinPeriodFrequencyEnum = val; }
public void setNominalAnnualInterestRateOverdraft(java.math.BigDecimal val) { this.nominalAnnualInterestRateOverdraft = val; }
public void setWithdrawalFeeForTransfer(boolean val) { this.withdrawalFeeForTransfer = val; }
public void setMinOverdraftForInterestCalculation(java.math.BigDecimal val) { this.minOverdraftForInterestCalculation = val; }
}
@@ -106,4 +106,22 @@ public class CurrentAccountCharge extends AbstractPersistableCustom<Long> {
private LocalDate chargeResetDate;
protected CurrentAccountCharge() {}
public static CurrentAccountCharge createFromFacility(final CurrentAccount account, final Charge charge) {
final CurrentAccountCharge c = new CurrentAccountCharge();
c.currentAccount = account;
c.charge = charge;
c.chargeTimeEnum = charge.getChargeTimeType();
c.chargeCalculationEnum = charge.getChargeCalculation();
c.amount = charge.getAmount();
c.amountOutstandingDerived = charge.getAmount();
c.penalty = charge.isPenalty();
c.active = true;
if (charge.getFeeOnMonthDay() != null) {
c.feeOnMonth = charge.getFeeOnMonthDay().getMonthValue();
c.feeOnDay = charge.getFeeOnMonthDay().getDayOfMonth();
}
c.feeInterval = charge.getFeeInterval();
return c;
}
}
@@ -92,7 +92,7 @@ public class CurrentAccountFacilityConfig extends AbstractPersistableCustom<Long
@Column(name = "mobile_banking", nullable = false)
private boolean mobileBanking = false;
protected CurrentAccountFacilityConfig() {}
public CurrentAccountFacilityConfig() {}
public static CurrentAccountFacilityConfig createFor(CurrentAccount account) {
CurrentAccountFacilityConfig cfg = new CurrentAccountFacilityConfig();
@@ -122,6 +122,18 @@ public class CurrentAccountTransaction extends AbstractPersistableCustom<Long> {
return txn;
}
public static CurrentAccountTransaction chargeDeduction(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
txn.currentAccount = account;
txn.office = office;
txn.transactionTypeEnum = 7; // PAY_CHARGE
txn.amount = amount;
txn.transactionDate = transactionDate;
txn.submittedOnDate = transactionDate;
return txn;
}
public static CurrentAccountTransaction withdrawal(CurrentAccount account, Office office,
BigDecimal amount, LocalDate transactionDate, String refNo) {
CurrentAccountTransaction txn = new CurrentAccountTransaction();
@@ -447,6 +447,10 @@ public class CurrentProduct extends AbstractPersistableCustom<Long> {
return this.withHoldTax;
}
public boolean isLienAllowed() { return this.lienAllowed; }
public BigDecimal getMaxAllowedLienLimit() { return this.maxAllowedLienLimit; }
public BigDecimal getMinBalanceForInterestCalculation() { return this.minBalanceForInterestCalculation; }
public TaxGroup getTaxGroup() {
return this.taxGroup;
}
@@ -867,6 +871,14 @@ public class CurrentProduct extends AbstractPersistableCustom<Long> {
public void setLastInterestDate(LocalDate lastInterestDate) { this.lastInterestDate = lastInterestDate; }
public boolean isEnableLockinPeriod() { return enableLockinPeriod; }
public void setEnableLockinPeriod(boolean enableLockinPeriod) { this.enableLockinPeriod = enableLockinPeriod; }
public BigDecimal getMinRequiredOpeningBalance() { return minRequiredOpeningBalance; }
public BigDecimal getMinRequiredBalance() { return minRequiredBalance; }
public boolean isEnforceMinRequiredBalance() { return enforceMinRequiredBalance; }
public Integer getLockinPeriodFrequency() { return lockinPeriodFrequency; }
public Integer getLockinPeriodFrequencyType() { return lockinPeriodFrequencyType; }
public BigDecimal getNominalAnnualInterestRateOverdraft() { return nominalAnnualInterestRateOverdraft; }
public boolean isWithdrawalFeeApplicableForTransfer() { return withdrawalFeeApplicableForTransfer; }
public BigDecimal getMinOverdraftForInterestCalculation() { return minOverdraftForInterestCalculation; }
public String getBalanceFrequency() { return balanceFrequency; }
public void setBalanceFrequency(String balanceFrequency) { this.balanceFrequency = balanceFrequency; }
public Boolean getAccountClosedZeroBalanceInDayClosing() { return accountClosedZeroBalanceInDayClosing; }
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -24,6 +24,7 @@ import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -97,7 +98,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
.append(" journalEntry.type_enum as entryType,journalEntry.amount as amount, journalEntry.transaction_id as transactionId,")
.append(" journalEntry.entity_type_enum as entityType, journalEntry.entity_id as entityId, creatingUser.id as createdByUserId, ")
.append(" creatingUser.username as createdByUserName, journalEntry.description as comments, ")
.append(" journalEntry.submitted_on_date as submittedOnDate, journalEntry.reversed as reversed, ")
.append(" journalEntry.created_on_utc as submittedOnDate, journalEntry.reversed as reversed, ")
.append(" journalEntry.currency_code as currencyCode, curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ")
.append(" curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ")
.append(" eao.external_id as externalAssetOwner ");
@@ -157,7 +158,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
final Long entityId = JdbcSupport.getLong(rs, "entityId");
final Long createdByUserId = rs.getLong("createdByUserId");
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
final OffsetDateTime submittedOnDate = rs.getObject("submittedOnDate", OffsetDateTime.class);
final String createdByUserName = rs.getString("createdByUserName");
final String comments = rs.getString("comments");
final Boolean reversed = rs.getBoolean("reversed");
@@ -63,7 +63,154 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
if (rows.isEmpty()) {
throw new CurrentAccountNotFoundException(accountId);
}
return buildStructuredResponse(rows.get(0));
final Map<String, Object> response = buildStructuredResponse(rows.get(0));
response.put("charges", retrieveCharges(accountId));
response.put("transactions", retrieveTransactions(accountId));
return response;
}
private List<Map<String, Object>> retrieveTransactions(Long accountId) {
final String sql =
"SELECT t.id, t.transaction_type_enum, t.transaction_date, t.amount, "
+ "t.running_balance_derived, t.is_reversed, t.is_reversal, t.ref_no, t.remarks, "
+ "t.submitted_on_date "
+ "FROM m_current_account_transaction t "
+ "WHERE t.current_account_id = ? "
+ "ORDER BY t.transaction_date ASC, t.id ASC";
return jdbcTemplate.query(sql, (rs, rowNum) -> {
final Map<String, Object> t = new LinkedHashMap<>();
t.put("id", rs.getLong("id"));
final int txnTypeEnum = rs.getInt("transaction_type_enum");
t.put("transactionType", buildTransactionType(txnTypeEnum));
final java.time.LocalDate txnDate = rs.getObject("transaction_date", java.time.LocalDate.class);
if (txnDate != null) {
t.put("date", new int[]{txnDate.getYear(), txnDate.getMonthValue(), txnDate.getDayOfMonth()});
} else {
t.put("date", null);
}
t.put("amount", rs.getBigDecimal("amount"));
t.put("runningBalance", rs.getBigDecimal("running_balance_derived"));
t.put("reversed", rs.getBoolean("is_reversed"));
t.put("isReversal", rs.getBoolean("is_reversal"));
t.put("refNo", rs.getString("ref_no"));
t.put("remarks", rs.getString("remarks"));
final java.time.LocalDate submittedOn = rs.getObject("submitted_on_date", java.time.LocalDate.class);
if (submittedOn != null) {
t.put("submittedOnDate", new int[]{submittedOn.getYear(), submittedOn.getMonthValue(), submittedOn.getDayOfMonth()});
} else {
t.put("submittedOnDate", null);
}
return t;
}, accountId);
}
private Map<String, Object> buildTransactionType(int typeEnum) {
final Map<String, Object> tt = new LinkedHashMap<>();
tt.put("id", typeEnum);
tt.put("deposit", typeEnum == 1);
tt.put("withdrawal", typeEnum == 2);
tt.put("interestPosting", typeEnum == 3);
tt.put("feeDeduction", typeEnum == 4 || typeEnum == 7);
tt.put("overdraftInterest", false);
tt.put("withholdTax", false);
tt.put("initiateTransfer", false);
tt.put("approveTransfer", false);
tt.put("withdrawTransfer", false);
tt.put("rejectTransfer", false);
tt.put("writtenoff", false);
tt.put("overdraftFee", false);
tt.put("escheat", false);
tt.put("amountHold", false);
tt.put("amountRelease", false);
tt.put("accrual", false);
switch (typeEnum) {
case 1 -> { tt.put("code", "savingsTransactionType.deposit"); tt.put("value", "Deposit"); }
case 2 -> { tt.put("code", "savingsTransactionType.withdrawal"); tt.put("value", "Withdrawal"); }
case 3 -> { tt.put("code", "savingsTransactionType.interestPosting"); tt.put("value", "Interest Posting"); }
case 4 -> { tt.put("code", "savingsTransactionType.feeDeduction"); tt.put("value", "Fee Deduction"); }
case 7 -> { tt.put("code", "savingsTransactionType.payCharge"); tt.put("value", "Charge Deduction"); }
default -> { tt.put("code", "savingsTransactionType.deposit"); tt.put("value", "Deposit"); }
}
return tt;
}
private List<Map<String, Object>> retrieveCharges(Long accountId) {
final String sql =
"SELECT cac.id, cac.charge_id, ch.name AS charge_name, "
+ "cac.charge_time_enum, cac.charge_due_date, cac.fee_on_month, cac.fee_on_day, "
+ "cac.fee_interval, cac.charge_calculation_enum, "
+ "cac.amount, cac.amount_paid_derived, cac.amount_waived_derived, "
+ "cac.amount_writtenoff_derived, cac.amount_outstanding_derived, "
+ "cac.is_paid_derived, cac.waived, cac.is_penalty, cac.is_active "
+ "FROM m_current_account_charge cac "
+ "JOIN m_charge ch ON ch.id = cac.charge_id "
+ "WHERE cac.current_account_id = ? AND cac.is_active = true "
+ "ORDER BY cac.id";
return jdbcTemplate.query(sql, (rs, rowNum) -> {
final Map<String, Object> c = new LinkedHashMap<>();
c.put("id", rs.getLong("id"));
c.put("chargeId", rs.getLong("charge_id"));
c.put("name", rs.getString("charge_name"));
final int chargeTimeEnum = rs.getInt("charge_time_enum");
c.put("chargeTimeType", buildEnumOption(chargeTimeEnum, chargeTimeValue(chargeTimeEnum)));
c.put("chargeDueDate", rs.getDate("charge_due_date") != null ? rs.getDate("charge_due_date").toLocalDate().toString() : null);
c.put("feeOnMonthDay", buildFeeOnMonthDay(rs.getObject("fee_on_month"), rs.getObject("fee_on_day")));
c.put("feeInterval", rs.getObject("fee_interval"));
final int chargeCalcEnum = rs.getInt("charge_calculation_enum");
c.put("chargeCalculationType", buildEnumOption(chargeCalcEnum, chargeCalcValue(chargeCalcEnum)));
c.put("amount", rs.getBigDecimal("amount"));
c.put("amountPaid", rs.getBigDecimal("amount_paid_derived"));
c.put("amountWaived", rs.getBigDecimal("amount_waived_derived"));
c.put("amountWrittenOff", rs.getBigDecimal("amount_writtenoff_derived"));
c.put("amountOutstanding", rs.getBigDecimal("amount_outstanding_derived"));
c.put("paid", rs.getBoolean("is_paid_derived"));
c.put("waived", rs.getBoolean("waived"));
c.put("penalty", rs.getBoolean("is_penalty"));
c.put("isActive", rs.getBoolean("is_active"));
return c;
}, accountId);
}
private Map<String, Object> buildFeeOnMonthDay(Object month, Object day) {
if (month == null || day == null) return null;
final Map<String, Object> m = new LinkedHashMap<>();
m.put("month", month);
m.put("day", day);
return m;
}
private String chargeTimeValue(int id) {
return switch (id) {
case 1 -> "Disbursement";
case 2 -> "Specified due date";
case 3 -> "Savings Activation";
case 4 -> "Withdrawal Fee";
case 5 -> "Annual Fee";
case 6 -> "Annual Fee";
case 7 -> "Monthly Fee";
case 8 -> "Overdue Fee";
default -> "Specified due date";
};
}
private String chargeCalcValue(int id) {
return switch (id) {
case 1 -> "Flat";
case 2 -> "% Amount";
case 3 -> "% Loan Amount + Interest";
case 4 -> "% Interest";
default -> "Flat";
};
}
@Override
@@ -71,7 +218,10 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
List<Map<String, Object>> rows = jdbcTemplate.query(BASE_SQL, new FlatRowMapper());
List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, Object> row : rows) {
result.add(buildStructuredResponse(row));
final Map<String, Object> response = buildStructuredResponse(row);
final Long id = ((Number) row.get("id")).longValue();
response.put("charges", retrieveCharges(id));
result.add(response);
}
return result;
}
@@ -44,6 +44,14 @@ import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWriteP
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.accounting.common.AccountingConstants.CashAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
import org.apache.fineract.portfolio.PortfolioProductType;
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
import org.springframework.stereotype.Service;
@@ -61,6 +69,10 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
private final OfficeRepositoryWrapper officeRepository;
private final CurrentAccountNumberGenerator accountNumberGenerator;
private final ClientNomineeWritePlatformService clientNomineeWritePlatformService;
private final org.apache.fineract.portfolio.savings.domain.FacilityChargeMappingRepository facilityChargeMappingRepository;
private final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransactionRepository currentAccountTransactionRepository;
private final JournalEntryRepository journalEntryRepository;
private final ProductToGLAccountMappingRepository productToGLAccountMappingRepository;
@Override
@Transactional
@@ -114,6 +126,26 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
product.getInterestCalculateSelector(), product.getAveragePeriod(),
product.getAvgBalanceCalcPeriod());
// Copy product-level fields that are not in createNew()
account.setMinRequiredOpeningBalance(product.getMinRequiredOpeningBalance());
account.setMinRequiredBalance(product.getMinRequiredBalance());
account.setEnforceMinRequiredBalance(product.isEnforceMinRequiredBalance());
account.setLockinPeriodFrequency(product.getLockinPeriodFrequency());
account.setLockinPeriodFrequencyEnum(product.getLockinPeriodFrequencyType());
account.setNominalAnnualInterestRateOverdraft(product.getNominalAnnualInterestRateOverdraft());
account.setWithdrawalFeeForTransfer(product.isWithdrawalFeeApplicableForTransfer());
account.setMinOverdraftForInterestCalculation(product.getMinOverdraftForInterestCalculation());
account.setFrequencyFixDay(product.getFrequencyFixDay());
account.setEnableLockinPeriod(product.isEnableLockinPeriod());
account.setBalanceFrequency(product.getBalanceFrequency());
account.setAccountClosedZeroBalanceInDayClosing(product.getAccountClosedZeroBalanceInDayClosing());
account.setValidForPeriod(product.getValidForPeriod());
account.setOdAccountClosedZeroBalanceInDayClosing(product.isOdAccountClosedZeroBalanceInDayClosing());
account.setLienAllowed(product.isLienAllowed());
account.setMaxAllowedLienLimit(product.getMaxAllowedLienLimit());
account.setMinBalanceForInterestCalculation(product.getMinBalanceForInterestCalculation());
account.setWithholdTax(product.withHoldTax());
if (json.has("establishmentDate") && !json.get("establishmentDate").isJsonNull()) {
account.setEstablishmentDate(parseDate(json.get("establishmentDate").getAsString(), dateFormat, locale));
}
@@ -141,7 +173,8 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
if (introducerClientId != null) account.setIntroducerClientId(introducerClientId);
if (json.has("externalId") && !json.get("externalId").isJsonNull()) {
account.setExternalId(json.get("externalId").getAsString());
final String extId = json.get("externalId").getAsString().trim();
account.setExternalId(extId.isEmpty() ? null : extId);
}
if (json.has("accountStatus") && !json.get("accountStatus").isJsonNull()) {
account.setAccountStatus(json.get("accountStatus").getAsString());
@@ -221,8 +254,73 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
final JsonObject json = command.parsedJson().getAsJsonObject();
LocalDate activatedOnDate = parseDate(getString(json, "activatedOnDate"), getString(json, "dateFormat"), getString(json, "locale"));
if (activatedOnDate == null) activatedOnDate = LocalDate.now();
// Validate minimum opening balance before activation
java.math.BigDecimal openingBalanceValue = java.math.BigDecimal.ZERO;
if (json.has("openingBalance") && !json.get("openingBalance").isJsonNull()) {
try { openingBalanceValue = json.get("openingBalance").getAsBigDecimal(); } catch (Exception ignored) {}
}
final java.math.BigDecimal minRequired = account.getMinRequiredOpeningBalance();
if (minRequired != null && minRequired.compareTo(java.math.BigDecimal.ZERO) > 0
&& openingBalanceValue.compareTo(minRequired) < 0) {
throw new org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException(
java.util.List.of(org.apache.fineract.infrastructure.core.data.ApiParameterError.parameterError(
"openingBalance",
"Opening balance (" + openingBalanceValue + ") is less than minimum required opening balance (" + minRequired + ")",
"validation.msg.currentaccount.openingBalance.less.than.min.required",
openingBalanceValue, minRequired)));
}
account.activate(activatedOnDate);
applyFacilityChargesOnActivation(account);
java.math.BigDecimal currentBalance = java.math.BigDecimal.ZERO;
final java.util.List<org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction> depositTxns = new java.util.ArrayList<>();
final java.util.List<ChargeGlEntry> chargeEntries = new java.util.ArrayList<>();
// Opening balance deposit transaction
if (openingBalanceValue.compareTo(java.math.BigDecimal.ZERO) > 0) {
try {
final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction deposit =
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction
.deposit(account, account.getOffice(), openingBalanceValue, activatedOnDate, "OPENING-BALANCE");
deposit.setRunningBalanceDerived(openingBalanceValue);
account.setTotalDepositsDerived(openingBalanceValue);
currentAccountTransactionRepository.saveAndFlush(deposit);
currentBalance = openingBalanceValue;
depositTxns.add(deposit);
log.info("Opening balance deposit {} created for account id={}", openingBalanceValue, accountId);
} catch (Exception e) {
log.warn("Could not process openingBalance for account id={}: {}", accountId, e.getMessage());
}
}
// Auto-deduct activation charges (chargeTimeEnum=3) from opening balance
for (final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountCharge charge : account.getCharges()) {
if (charge.getChargeTimeEnum() != null && charge.getChargeTimeEnum() == 3
&& charge.getAmount() != null
&& charge.getAmount().compareTo(java.math.BigDecimal.ZERO) > 0
&& charge.getCharge() != null) {
charge.setAmountPaidDerived(charge.getAmount());
charge.setAmountOutstandingDerived(java.math.BigDecimal.ZERO);
charge.setPaid(true);
currentBalance = currentBalance.subtract(charge.getAmount());
final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction chargeTxn =
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction
.chargeDeduction(account, account.getOffice(), charge.getAmount(), activatedOnDate);
chargeTxn.setRunningBalanceDerived(currentBalance);
currentAccountTransactionRepository.saveAndFlush(chargeTxn);
chargeEntries.add(new ChargeGlEntry(chargeTxn, charge));
log.info("Auto-deducted activation charge '{}' {} from account id={}, running balance={}",
charge.getCharge().getName(), charge.getAmount(), accountId, currentBalance);
}
}
account.setAccountBalanceDerived(currentBalance);
currentAccountRepository.saveAndFlush(account);
// Post GL journal entries — deposits in batch, each charge individually
postGlEntries(account, depositTxns, chargeEntries);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@@ -396,4 +494,172 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
}
}
}
private void applyFacilityChargesOnActivation(final CurrentAccount account) {
// Use account-level facility config — reflects what was selected at account opening time
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig config =
account.getFacilityConfig();
log.info("[FacilityCharge] Activating account id={}, facilityConfig={}",
account.getId(), config != null ? "FOUND" : "NULL");
if (config == null) {
// Fallback: use product-level facility config
log.info("[FacilityCharge] No account-level config, falling back to product id={}", account.getProductId());
config = currentProductRepository.findById(account.getProductId())
.map(p -> {
final var pc = p.getFacilityConfig();
if (pc == null) {
log.info("[FacilityCharge] Product has no facility config either");
return null;
}
// Map product config to account config fields inline
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig ac =
new org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig();
ac.setAtmFacility(Boolean.TRUE.equals(pc.getAtmFacility()));
ac.setAtmWithdrawalFacility(Boolean.TRUE.equals(pc.getAtmWithdrawalFacility()));
ac.setUpiFacility(Boolean.TRUE.equals(pc.getUpiFacility()));
ac.setPosTransactionsFacility(Boolean.TRUE.equals(pc.getPosTransactionsFacility()));
ac.setChequeBook(Boolean.TRUE.equals(pc.getChequeBook()));
ac.setImpsFacility(Boolean.TRUE.equals(pc.getImpsFacility()));
ac.setSmsAlerts(Boolean.TRUE.equals(pc.getSmsAlerts()));
ac.setBbpsFacility(Boolean.TRUE.equals(pc.getBbpsFacility()));
ac.setInsuranceCover(Boolean.TRUE.equals(pc.getInsuranceCover()));
ac.setBepgFacility(Boolean.TRUE.equals(pc.getBepgFacility()));
ac.setNeftFacility(Boolean.TRUE.equals(pc.getNeftFacility()));
ac.setRtgsFacility(Boolean.TRUE.equals(pc.getRtgsFacility()));
ac.setInterBankTotalCapFacility(Boolean.TRUE.equals(pc.getInterBankTotalCapFacility()));
ac.setWithdrawalFacility(Boolean.TRUE.equals(pc.getWithdrawalFacility()));
ac.setDepositFacilityKyc(Boolean.TRUE.equals(pc.getDepositFacilityKyc()));
ac.setVirtualDebitCard(Boolean.TRUE.equals(pc.getVirtualDebitCard()));
return ac;
}).orElse(null);
}
if (config == null) {
log.warn("[FacilityCharge] No facility config found for account id={} — no charges applied", account.getId());
return;
}
final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig fc = config;
log.info("[FacilityCharge] Config found — ATM={} UPI={} NEFT={} RTGS={} CHEQUE={} SMS={} IMPs={} POS={} BBPS={} INS={} BEPG={} IBCAP={} WD={} KYC={} VCARD={} ATM_WD={}",
fc.isAtmFacility(), fc.isUpiFacility(), fc.isNeftFacility(), fc.isRtgsFacility(),
fc.isChequeBook(), fc.isSmsAlerts(), fc.isImpsFacility(), fc.isPosTransactionsFacility(),
fc.isBbpsFacility(), fc.isInsuranceCover(), fc.isBepgFacility(), fc.isInterBankTotalCapFacility(),
fc.isWithdrawalFacility(), fc.isDepositFacilityKyc(), fc.isVirtualDebitCard(), fc.isAtmWithdrawalFacility());
applyMapped(account, fc.isAtmFacility(), "ATM_FACILITY");
applyMapped(account, fc.isAtmWithdrawalFacility(), "ATM_WITHDRAWAL");
applyMapped(account, fc.isUpiFacility(), "UPI_FACILITY");
applyMapped(account, fc.isPosTransactionsFacility(), "POS_TRANSACTIONS");
applyMapped(account, fc.isChequeBook(), "CHEQUE_BOOK");
applyMapped(account, fc.isImpsFacility(), "IMPS_FACILITY");
applyMapped(account, fc.isSmsAlerts(), "SMS_ALERTS");
applyMapped(account, fc.isBbpsFacility(), "BBPS_FACILITY");
applyMapped(account, fc.isInsuranceCover(), "INSURANCE_COVER");
applyMapped(account, fc.isBepgFacility(), "BEPG_FACILITY");
applyMapped(account, fc.isNeftFacility(), "NEFT_FACILITY");
applyMapped(account, fc.isRtgsFacility(), "RTGS_FACILITY");
applyMapped(account, fc.isInterBankTotalCapFacility(),"INTER_BANK_TOTAL_CAP");
applyMapped(account, fc.isWithdrawalFacility(), "WITHDRAWAL_FACILITY");
applyMapped(account, fc.isDepositFacilityKyc(), "DEPOSIT_FACILITY_KYC");
applyMapped(account, fc.isVirtualDebitCard(), "VIRTUAL_DEBIT_CARD");
}
// Wrapper to carry a charge transaction together with its charge entity for GL posting
private record ChargeGlEntry(
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction txn,
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountCharge charge) {}
// entity type for savings/current accounts in acc_gl_journal_entry
private static final int ENTITY_TYPE_SAVINGS = 2;
private void postGlEntries(final CurrentAccount account,
final java.util.List<org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction> depositTxns,
final java.util.List<ChargeGlEntry> chargeEntries) {
try {
final CurrentProduct product = currentProductRepository.findById(account.getProductId()).orElse(null);
if (product == null) return;
final Integer accountingRule = product.getAccountingType();
if (accountingRule == null || accountingRule.equals(AccountingRuleType.NONE.getValue())) {
log.debug("[GL] Product {} accounting=NONE — skipping", account.getProductId());
return;
}
final GLAccount cashAccount = findGlAccount(account.getProductId(), CashAccountsForSavings.SAVINGS_REFERENCE.getValue());
final GLAccount controlAccount = findGlAccount(account.getProductId(), CashAccountsForSavings.SAVINGS_CONTROL.getValue());
final GLAccount feeIncomeAccount = findGlAccount(account.getProductId(), CashAccountsForSavings.INCOME_FROM_FEES.getValue());
if (cashAccount == null || controlAccount == null) {
log.warn("[GL] Missing GL account mappings for product id={} — skipping GL posting", account.getProductId());
return;
}
final String transactionRef = "CA" + account.getId();
// Deposits: DR SAVINGS_REFERENCE (cash in), CR SAVINGS_CONTROL (liability)
if (depositTxns != null) {
for (final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction txn : depositTxns) {
if (txn.getId() == null || txn.getAmount() == null) continue;
postEntry(account, cashAccount, JournalEntryType.DEBIT, txn.getAmount(), txn.getTransactionDate(), transactionRef);
postEntry(account, controlAccount, JournalEntryType.CREDIT, txn.getAmount(), txn.getTransactionDate(), transactionRef);
log.info("[GL] Deposit GL posted for account id={}, amount={}", account.getId(), txn.getAmount());
}
}
// Charges: DR SAVINGS_CONTROL (liability reduced), CR INCOME_FROM_FEES
if (chargeEntries != null && feeIncomeAccount != null) {
for (final ChargeGlEntry entry : chargeEntries) {
final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction txn = entry.txn();
if (txn.getId() == null || txn.getAmount() == null) continue;
postEntry(account, controlAccount, JournalEntryType.DEBIT, txn.getAmount(), txn.getTransactionDate(), transactionRef);
postEntry(account, feeIncomeAccount, JournalEntryType.CREDIT, txn.getAmount(), txn.getTransactionDate(), transactionRef);
log.info("[GL] Charge GL posted for account id={}, charge={}, amount={}", account.getId(), entry.charge().getCharge().getName(), txn.getAmount());
}
}
} catch (Exception e) {
log.error("[GL] Failed GL posting for current account id={}: {}", account.getId(), e.getMessage(), e);
}
}
private GLAccount findGlAccount(final Long productId, final int financialAccountType) {
final org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping mapping =
productToGLAccountMappingRepository.findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(
productId, PortfolioProductType.SAVING.getValue(), financialAccountType, null);
return mapping != null ? mapping.getGlAccount() : null;
}
private void postEntry(final CurrentAccount account, final GLAccount glAccount,
final JournalEntryType entryType, final java.math.BigDecimal amount,
final LocalDate date, final String transactionRef) {
final JournalEntry entry = JournalEntry.createNew(
account.getOffice(), null, glAccount,
account.getCurrencyCode(), transactionRef,
false, date, entryType, amount,
"Current Account activation",
ENTITY_TYPE_SAVINGS, account.getId(),
null, null, null, null, null);
journalEntryRepository.save(entry);
}
private void applyMapped(final CurrentAccount account, final boolean enabled, final String facilityType) {
if (!enabled) {
log.debug("[FacilityCharge] {} disabled — skip", facilityType);
return;
}
final var mappingOpt = facilityChargeMappingRepository.findByFacilityType(facilityType);
if (mappingOpt.isEmpty()) {
log.warn("[FacilityCharge] {} enabled but NO mapping found in m_facility_charge_mapping", facilityType);
return;
}
final org.apache.fineract.portfolio.charge.domain.Charge charge = mappingOpt.get().getCharge();
if (charge == null) {
log.warn("[FacilityCharge] {} mapping exists but charge_id is NULL", facilityType);
return;
}
log.info("[FacilityCharge] Applying charge '{}' (id={}) for facility {}", charge.getName(), charge.getId(), facilityType);
account.getCharges().add(
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountCharge
.createFromFacility(account, charge));
}
}
@@ -96,6 +96,7 @@ import org.apache.fineract.portfolio.note.domain.Note;
import org.apache.fineract.portfolio.note.domain.NoteRepository;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
import org.apache.fineract.portfolio.savings.service.FacilityChargeAutoApplyService;
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
@@ -165,6 +166,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
private final GSIMRepositoy gsimRepository;
private final SavingsAccountInterestPostingService savingsAccountInterestPostingService;
private final ErrorHandler errorHandler;
private final FacilityChargeAutoApplyService facilityChargeAutoApplyService;
@Transactional
@Override
@@ -199,6 +201,24 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
// Validate minimum opening balance
BigDecimal openingBalanceForValidation = BigDecimal.ZERO;
if (command.parameterExists("openingBalance")) {
final BigDecimal ob = command.bigDecimalValueOfParameterNamed("openingBalance");
if (ob != null) openingBalanceForValidation = ob;
}
final BigDecimal minRequiredOpening = account.getMinRequiredOpeningBalance();
if (minRequiredOpening != null && minRequiredOpening.compareTo(BigDecimal.ZERO) > 0
&& openingBalanceForValidation.compareTo(minRequiredOpening) < 0) {
final List<ApiParameterError> errors = new ArrayList<>();
errors.add(ApiParameterError.parameterError(
"openingBalance",
"Opening balance (" + openingBalanceForValidation + ") is less than minimum required opening balance (" + minRequiredOpening + ")",
"validation.msg.savingsaccount.openingBalance.less.than.min.required",
openingBalanceForValidation, minRequiredOpening));
throw new PlatformApiDataValidationException(errors);
}
checkClientOrGroupActive(account);
final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>();
@@ -214,6 +234,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
processPostActiveActions(account, fmt, existingTransactionIds, existingReversedTransactionIds);
// Auto-apply facility charges based on product facility config
facilityChargeAutoApplyService.applyFacilityChargesOnActivation(account);
this.savingAccountRepositoryWrapper.saveAndFlush(account);
// Collect any newly added facility activation charges (after flush so amountOutstanding is set)
// payCharge() internally adds transactions to account.transactions via handleChargeTransactions
// Do NOT add to existingTransactionIds — postJournalEntries must include them for GL posting
facilityChargeAutoApplyService.collectActivationCharges(account);
this.savingAccountRepositoryWrapper.saveAndFlush(account);
}
@@ -98,6 +98,7 @@ import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfig
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimit;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimitRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
import org.apache.fineract.portfolio.savings.service.FacilityChargeAutoApplyService;
import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformService;
import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformServiceImpl;
import org.apache.fineract.portfolio.savings.service.DepositAccountOnHoldTransactionReadPlatformService;
@@ -423,7 +424,7 @@ public class SavingsConfiguration {
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, AppUserRepositoryWrapper appuserRepository,
StandingInstructionRepository standingInstructionRepository, BusinessEventNotifierService businessEventNotifierService,
GSIMRepositoy gsimRepository, SavingsAccountInterestPostingService savingsAccountInterestPostingService,
ErrorHandler errorHandler) {
ErrorHandler errorHandler, FacilityChargeAutoApplyService facilityChargeAutoApplyService) {
return new SavingsAccountWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, savingAccountRepositoryWrapper,
staffRepository, savingsAccountTransactionRepository, savingAccountAssembler, savingsAccountTransactionDataValidator,
savingsAccountChargeDataValidator, paymentDetailWritePlatformService, journalEntryWritePlatformService,
@@ -431,7 +432,7 @@ public class SavingsConfiguration {
chargeRepository, savingsAccountChargeRepository, holidayRepository, workingDaysRepository, configurationDomainService,
depositAccountOnHoldTransactionRepository, entityDatatableChecksWritePlatformService, appuserRepository,
standingInstructionRepository, businessEventNotifierService, gsimRepository, savingsAccountInterestPostingService,
errorHandler);
errorHandler, facilityChargeAutoApplyService);
}
@Bean
@@ -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>