cash-only registers, day book sections and DD amendments
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Cashier reports now reconcile against the drawer: - Received/Payment exclude what cannot be cash rather than admitting only proven cash, so registers that pay out without recording a denomination breakdown are no longer silently dropped - Closing report scoped to the cashier, with opening/closing balance, reversed counter transactions excluded, and cash DD receipts counted - Day book split into Cashier and Clerk sections, filed by whose cash moved rather than who raised the request Fixed one deposit or withdrawal raising two vouchers: the cashier deposit and clerk withdrawal registers did not suppress the inner savings command's voucher, doubling the day's cash on both reports. Sponsor DD: - Amendment request flow (beneficiary, payable-at branch and city) under maker-checker; face value and funding stay unamendable - Cash DD denominations were collected but never saved, and the amount check demanded a figure notes could not add to; both fixed and the breakdown is now required Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+8
-8
@@ -56,8 +56,9 @@ public class SavingsProductAccountingDataValidator {
|
||||
baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue())
|
||||
.value(savingsReferenceAccountId).notNull().integerGreaterThanZero();
|
||||
}
|
||||
if (fromApiJsonHelper.parameterExists(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element)
|
||||
|| ignoreExistenceValidation) {
|
||||
// Transfers In Suspense is not offered by the savings product screen, so it is validated only
|
||||
// when actually supplied — ignoreExistenceValidation must not make it mandatory on create.
|
||||
if (fromApiJsonHelper.parameterExists(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element)) {
|
||||
final Long transfersInSuspenseAccountId = fromApiJsonHelper
|
||||
.extractLongNamed(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element);
|
||||
baseDataValidator.reset().parameter(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue())
|
||||
@@ -85,22 +86,21 @@ public class SavingsProductAccountingDataValidator {
|
||||
.value(incomeFromPenaltyId).notNull().integerGreaterThanZero();
|
||||
}
|
||||
if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
|
||||
if (fromApiJsonHelper.parameterExists(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element)
|
||||
|| ignoreExistenceValidation) {
|
||||
// Overdraft Control / Income From Interest / Write Off are likewise not offered by the
|
||||
// savings product screen, so they too are validated only when actually supplied.
|
||||
if (fromApiJsonHelper.parameterExists(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element)) {
|
||||
final Long overdraftControlAccountId = fromApiJsonHelper
|
||||
.extractLongNamed(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
|
||||
baseDataValidator.reset().parameter(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue())
|
||||
.value(overdraftControlAccountId).notNull().integerGreaterThanZero();
|
||||
}
|
||||
if (fromApiJsonHelper.parameterExists(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), element)
|
||||
|| ignoreExistenceValidation) {
|
||||
if (fromApiJsonHelper.parameterExists(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), element)) {
|
||||
final Long incomeFromInterest = fromApiJsonHelper
|
||||
.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), element);
|
||||
baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue())
|
||||
.value(incomeFromInterest).notNull().integerGreaterThanZero();
|
||||
}
|
||||
if (fromApiJsonHelper.parameterExists(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), element)
|
||||
|| ignoreExistenceValidation) {
|
||||
if (fromApiJsonHelper.parameterExists(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), element)) {
|
||||
final Long writtenoff = fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
|
||||
element);
|
||||
baseDataValidator.reset().parameter(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue()).value(writtenoff)
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 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.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Record that an escheated balance was actually remitted to RBI's Depositor Education and Awareness
|
||||
* Fund.
|
||||
*
|
||||
* <p>
|
||||
* Escheat and remittance are two separate events. The nightly job closes the account and takes the
|
||||
* balance out; sending that money to RBI happens outside this system, on RBI's own schedule. Until
|
||||
* a row exists here the balance is out of the customer's account but not accounted for as having
|
||||
* reached the fund, and the escheat report says exactly that rather than assuming it was sent.
|
||||
*
|
||||
* <p>
|
||||
* A customer can claim an unclaimed balance at any time even after remittance: the bank pays them
|
||||
* and recovers the amount from the fund. {@code refundedOn} records that, so a remittance that has
|
||||
* since been drawn back is not counted twice.
|
||||
*
|
||||
* <p>
|
||||
* One row per savings account — an account's balance goes to DEAF once.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_deaf_remittance")
|
||||
public class DeafRemittance {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "savings_account_id", nullable = false, unique = true)
|
||||
private Long savingsAccountId;
|
||||
|
||||
@Column(name = "client_id", nullable = false)
|
||||
private Long clientId;
|
||||
|
||||
/** The amount taken out of the account at escheat, which is what is owed to the fund. */
|
||||
@Column(name = "amount", nullable = false, scale = 6, precision = 19)
|
||||
private BigDecimal amount;
|
||||
|
||||
/** The day the account was closed and the balance removed. */
|
||||
@Column(name = "escheated_on", nullable = false)
|
||||
private LocalDate escheatedOn;
|
||||
|
||||
/** The day the money was sent to RBI. */
|
||||
@Column(name = "remitted_on", nullable = false)
|
||||
private LocalDate remittedOn;
|
||||
|
||||
/** RBI's acknowledgement for the remittance, as printed on their advice. */
|
||||
@Column(name = "deaf_reference", length = 100)
|
||||
private String deafReference;
|
||||
|
||||
/** Set only if the customer later claimed the balance and it was recovered from the fund. */
|
||||
@Column(name = "refunded_on")
|
||||
private LocalDate refundedOn;
|
||||
|
||||
@Column(name = "remarks", length = 500)
|
||||
private String remarks;
|
||||
|
||||
@Column(name = "recorded_by_user_id")
|
||||
private Long recordedByUserId;
|
||||
|
||||
@Column(name = "recorded_on", nullable = false)
|
||||
private LocalDateTime recordedOn;
|
||||
|
||||
protected DeafRemittance() {
|
||||
// JPA
|
||||
}
|
||||
|
||||
private DeafRemittance(final Long savingsAccountId, final Long clientId, final BigDecimal amount,
|
||||
final LocalDate escheatedOn, final LocalDate remittedOn, final String deafReference, final String remarks,
|
||||
final Long recordedByUserId) {
|
||||
this.savingsAccountId = savingsAccountId;
|
||||
this.clientId = clientId;
|
||||
this.amount = amount;
|
||||
this.escheatedOn = escheatedOn;
|
||||
this.remittedOn = remittedOn;
|
||||
this.deafReference = deafReference;
|
||||
this.remarks = remarks;
|
||||
this.recordedByUserId = recordedByUserId;
|
||||
this.recordedOn = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public static DeafRemittance record(final Long savingsAccountId, final Long clientId, final BigDecimal amount,
|
||||
final LocalDate escheatedOn, final LocalDate remittedOn, final String deafReference, final String remarks,
|
||||
final Long recordedByUserId) {
|
||||
return new DeafRemittance(savingsAccountId, clientId, amount, escheatedOn, remittedOn, deafReference, remarks,
|
||||
recordedByUserId);
|
||||
}
|
||||
|
||||
/** Records that the customer claimed the balance back and it was recovered from the fund. */
|
||||
public void markRefunded(final LocalDate refundedOn) {
|
||||
this.refundedOn = refundedOn;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Long getSavingsAccountId() {
|
||||
return savingsAccountId;
|
||||
}
|
||||
|
||||
public Long getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public LocalDate getEscheatedOn() {
|
||||
return escheatedOn;
|
||||
}
|
||||
|
||||
public LocalDate getRemittedOn() {
|
||||
return remittedOn;
|
||||
}
|
||||
|
||||
public String getDeafReference() {
|
||||
return deafReference;
|
||||
}
|
||||
|
||||
public LocalDate getRefundedOn() {
|
||||
return refundedOn;
|
||||
}
|
||||
|
||||
public String getRemarks() {
|
||||
return remarks;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 DeafRemittanceRepository extends JpaRepository<DeafRemittance, Long> {
|
||||
|
||||
Optional<DeafRemittance> findBySavingsAccountId(Long savingsAccountId);
|
||||
|
||||
boolean existsBySavingsAccountId(Long savingsAccountId);
|
||||
|
||||
List<DeafRemittance> findBySavingsAccountIdIn(List<Long> savingsAccountIds);
|
||||
}
|
||||
-3
@@ -68,9 +68,6 @@ public class FacilityConfigEditFields {
|
||||
@Column(name = "fc_inter_bank_total_cap_facility")
|
||||
private Boolean interBankTotalCapFacility;
|
||||
|
||||
@Column(name = "fc_withdrawal_facility")
|
||||
private Boolean withdrawalFacility;
|
||||
|
||||
@Column(name = "fc_deposit_facility_kyc")
|
||||
private Boolean depositFacilityKyc;
|
||||
|
||||
|
||||
-2
@@ -417,7 +417,6 @@ public final class ProductPendingEditFieldMapper {
|
||||
f.setNeftFacility(booleanOf(command, "accountFacilityConfig", "neftFacility"));
|
||||
f.setRtgsFacility(booleanOf(command, "accountFacilityConfig", "rtgsFacility"));
|
||||
f.setInterBankTotalCapFacility(booleanOf(command, "accountFacilityConfig", "interBankTotalCapFacility"));
|
||||
f.setWithdrawalFacility(booleanOf(command, "accountFacilityConfig", "withdrawalFacility"));
|
||||
f.setDepositFacilityKyc(booleanOf(command, "accountFacilityConfig", "depositFacilityKyc"));
|
||||
f.setVirtualDebitCard(booleanOf(command, "accountFacilityConfig", "virtualDebitCard"));
|
||||
return true;
|
||||
@@ -441,7 +440,6 @@ public final class ProductPendingEditFieldMapper {
|
||||
config.addProperty("neftFacility", f.getNeftFacility());
|
||||
config.addProperty("rtgsFacility", f.getRtgsFacility());
|
||||
config.addProperty("interBankTotalCapFacility", f.getInterBankTotalCapFacility());
|
||||
config.addProperty("withdrawalFacility", f.getWithdrawalFacility());
|
||||
config.addProperty("depositFacilityKyc", f.getDepositFacilityKyc());
|
||||
config.addProperty("virtualDebitCard", f.getVirtualDebitCard());
|
||||
root.add("accountFacilityConfig", config);
|
||||
|
||||
+4687
-4615
File diff suppressed because it is too large
Load Diff
-7
@@ -75,9 +75,6 @@ public class SavingsAccountFacilityConfig extends AbstractPersistableCustom<Long
|
||||
@Column(name = "inter_bank_total_cap_facility")
|
||||
private Boolean interBankTotalCapFacility = false;
|
||||
|
||||
@Column(name = "withdrawal_facility")
|
||||
private Boolean withdrawalFacility = false;
|
||||
|
||||
@Column(name = "deposit_facility_kyc")
|
||||
private Boolean depositFacilityKyc = false;
|
||||
|
||||
@@ -138,9 +135,6 @@ public class SavingsAccountFacilityConfig extends AbstractPersistableCustom<Long
|
||||
public Boolean getInterBankTotalCapFacility() { return interBankTotalCapFacility; }
|
||||
public void setInterBankTotalCapFacility(Boolean v) { this.interBankTotalCapFacility = v != null ? v : false; }
|
||||
|
||||
public Boolean getWithdrawalFacility() { return withdrawalFacility; }
|
||||
public void setWithdrawalFacility(Boolean v) { this.withdrawalFacility = v != null ? v : false; }
|
||||
|
||||
public Boolean getDepositFacilityKyc() { return depositFacilityKyc; }
|
||||
public void setDepositFacilityKyc(Boolean v) { this.depositFacilityKyc = v != null ? v : false; }
|
||||
|
||||
@@ -162,7 +156,6 @@ public class SavingsAccountFacilityConfig extends AbstractPersistableCustom<Long
|
||||
if (config.has("neftFacility")) setNeftFacility(config.get("neftFacility").getAsBoolean());
|
||||
if (config.has("rtgsFacility")) setRtgsFacility(config.get("rtgsFacility").getAsBoolean());
|
||||
if (config.has("interBankTotalCapFacility")) setInterBankTotalCapFacility(config.get("interBankTotalCapFacility").getAsBoolean());
|
||||
if (config.has("withdrawalFacility")) setWithdrawalFacility(config.get("withdrawalFacility").getAsBoolean());
|
||||
if (config.has("depositFacilityKyc")) setDepositFacilityKyc(config.get("depositFacilityKyc").getAsBoolean());
|
||||
if (config.has("virtualDebitCard")) setVirtualDebitCard(config.get("virtualDebitCard").getAsBoolean());
|
||||
}
|
||||
|
||||
-3
@@ -134,9 +134,6 @@ public class SavingsAccountPendingEdit extends AbstractPersistableCustom<Long> {
|
||||
@Column(name = "inter_bank_total_cap_facility")
|
||||
private Boolean interBankTotalCapFacility;
|
||||
|
||||
@Column(name = "withdrawal_facility")
|
||||
private Boolean withdrawalFacility;
|
||||
|
||||
@Column(name = "deposit_facility_kyc")
|
||||
private Boolean depositFacilityKyc;
|
||||
|
||||
|
||||
-2
@@ -371,8 +371,6 @@ public class SavingsProductAssembler {
|
||||
facilityConfigJson.get("rtgsFacility").getAsBoolean() : false);
|
||||
config.setInterBankTotalCapFacility(facilityConfigJson.has("interBankTotalCapFacility") ?
|
||||
facilityConfigJson.get("interBankTotalCapFacility").getAsBoolean() : false);
|
||||
config.setWithdrawalFacility(facilityConfigJson.has("withdrawalFacility") ?
|
||||
facilityConfigJson.get("withdrawalFacility").getAsBoolean() : false);
|
||||
config.setDepositFacilityKyc(facilityConfigJson.has("depositFacilityKyc") ?
|
||||
facilityConfigJson.get("depositFacilityKyc").getAsBoolean() : false);
|
||||
config.setVirtualDebitCard(facilityConfigJson.has("virtualDebitCard") ?
|
||||
|
||||
-7
@@ -75,9 +75,6 @@ public class SavingsProductFacilityConfig extends AbstractPersistableCustom<Long
|
||||
@Column(name = "inter_bank_total_cap_facility")
|
||||
private Boolean interBankTotalCapFacility = false;
|
||||
|
||||
@Column(name = "withdrawal_facility")
|
||||
private Boolean withdrawalFacility = false;
|
||||
|
||||
@Column(name = "deposit_facility_kyc")
|
||||
private Boolean depositFacilityKyc = false;
|
||||
|
||||
@@ -138,9 +135,6 @@ public class SavingsProductFacilityConfig extends AbstractPersistableCustom<Long
|
||||
public Boolean getInterBankTotalCapFacility() { return interBankTotalCapFacility; }
|
||||
public void setInterBankTotalCapFacility(Boolean v) { this.interBankTotalCapFacility = v != null ? v : false; }
|
||||
|
||||
public Boolean getWithdrawalFacility() { return withdrawalFacility; }
|
||||
public void setWithdrawalFacility(Boolean v) { this.withdrawalFacility = v != null ? v : false; }
|
||||
|
||||
public Boolean getDepositFacilityKyc() { return depositFacilityKyc; }
|
||||
public void setDepositFacilityKyc(Boolean v) { this.depositFacilityKyc = v != null ? v : false; }
|
||||
|
||||
@@ -163,7 +157,6 @@ public class SavingsProductFacilityConfig extends AbstractPersistableCustom<Long
|
||||
this.neftFacility = other.getNeftFacility();
|
||||
this.rtgsFacility = other.getRtgsFacility();
|
||||
this.interBankTotalCapFacility = other.getInterBankTotalCapFacility();
|
||||
this.withdrawalFacility = other.getWithdrawalFacility();
|
||||
this.depositFacilityKyc = other.getDepositFacilityKyc();
|
||||
this.virtualDebitCard = other.getVirtualDebitCard();
|
||||
}
|
||||
|
||||
-2
@@ -81,7 +81,6 @@ public class FacilityChargeAutoApplyService {
|
||||
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);
|
||||
|
||||
@@ -305,7 +304,6 @@ public class FacilityChargeAutoApplyService {
|
||||
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()) {
|
||||
|
||||
+89
-14
@@ -18,30 +18,43 @@
|
||||
*/
|
||||
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.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Seeds default facility charges on service startup via JDBC, one per facility type (ATM, UPI,
|
||||
* NEFT etc.), so the Facility Charge Mappings admin screen has ready-made charges to choose from.
|
||||
* Deliberately does NOT link a charge to its facility's m_facility_charge_mapping row or activate
|
||||
* it — that mapping must be an explicit admin choice made through that screen (an unmapped facility
|
||||
* is blocked from being enabled on an account by FacilityChargeAutoApplyService). Seeder only
|
||||
* creates charges that are missing — safe to run repeatedly.
|
||||
* NEFT etc.), and points each facility's m_facility_charge_mapping row at its own default charge,
|
||||
* so the Facility Charge Mappings admin screen comes up correctly configured on any install rather
|
||||
* than showing an unmapped dash on every row.
|
||||
* <p>
|
||||
* Mappings are seeded INACTIVE on purpose. Activating a mapping is what actually starts charging
|
||||
* the customer, so that stays an explicit admin decision made through that screen —
|
||||
* FacilityChargeAutoApplyService treats an inactive mapping exactly like an unmapped one and blocks
|
||||
* the facility from being enabled until an admin flips Active. Seeding the charge only removes the
|
||||
* tedium of picking the obvious one; it does not pre-approve any billing.
|
||||
* <p>
|
||||
* Every step is guarded (charges created only when missing, mappings linked only while still
|
||||
* unmapped), so an admin's own choice of charge or amount is never overwritten and the seeder is
|
||||
* safe to run repeatedly and against existing databases.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class FacilityChargeSeederService {
|
||||
@DependsOn({ "tenantDetailsService", "tenantDatabaseUpgradeService" })
|
||||
public class FacilityChargeSeederService implements InitializingBean {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final TenantDetailsService tenantDetailsService;
|
||||
|
||||
// facilityType → [chargeName, defaultAmount, chargeTimeType, chargeAppliesTo]
|
||||
// chargeTimeType: 3=SAVINGS_ACTIVATION, 6=ANNUAL_FEE, 7=MONTHLY_FEE
|
||||
@@ -63,13 +76,50 @@ public class FacilityChargeSeederService {
|
||||
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
|
||||
/**
|
||||
* Runs once per tenant after the tenant datasource and Liquibase migrations are ready.
|
||||
* <p>
|
||||
* This deliberately does NOT use {@code @PostConstruct}: that fires while the bean graph is still
|
||||
* being built, before Fineract's per-tenant routing datasource is available, so every tenant table
|
||||
* lookup failed with "bad SQL grammar" and the seeder silently skipped on every startup. Seeding
|
||||
* has to happen after {@code tenantDatabaseUpgradeService} has migrated the schema, with the tenant
|
||||
* bound to the thread so the routing datasource resolves to that tenant's database.
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
final List<FineractPlatformTenant> tenants = tenantDetailsService.findAllTenants();
|
||||
if (tenants == null || tenants.isEmpty()) {
|
||||
log.warn("FacilityChargeSeeder: no tenants found, skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
final FineractPlatformTenant originalTenant = ThreadLocalContextUtil.getTenant();
|
||||
try {
|
||||
for (final FineractPlatformTenant tenant : tenants) {
|
||||
try {
|
||||
ThreadLocalContextUtil.setTenant(tenant);
|
||||
seedFacilityCharges();
|
||||
} catch (Exception e) {
|
||||
// Seeding defaults must never take the platform down for one bad tenant.
|
||||
log.warn("FacilityChargeSeeder: skipped for tenant '{}' — {}", tenant.getTenantIdentifier(),
|
||||
e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
ThreadLocalContextUtil.setTenant(originalTenant);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Not {@code @Transactional}: this is invoked directly from {@link #afterPropertiesSet()} on the same
|
||||
* bean, so Spring's proxy would be bypassed and the annotation would be a false promise. It needs no
|
||||
* transaction anyway — every statement is individually guarded and idempotent, so a partial run simply
|
||||
* completes on the next startup.
|
||||
*/
|
||||
public void seedFacilityCharges() {
|
||||
try {
|
||||
for (Map.Entry<String, Object[]> entry : FACILITY_DEFAULTS.entrySet()) {
|
||||
@@ -107,8 +157,7 @@ public class FacilityChargeSeederService {
|
||||
}
|
||||
|
||||
// 2. Ensure the facility_charge_mapping row itself exists so the admin screen lists every
|
||||
// facility type, but leave it unmapped/inactive — an admin must explicitly pick a charge
|
||||
// for it via the Facility Charge Mappings screen before it can be enabled on an account.
|
||||
// facility type. Inserted inactive and unmapped; step 3 below fills in the charge.
|
||||
List<Long> existingMapping = jdbcTemplate.queryForList(
|
||||
"SELECT id FROM m_facility_charge_mapping WHERE facility_type = ?", Long.class, facilityType);
|
||||
|
||||
@@ -121,11 +170,37 @@ public class FacilityChargeSeederService {
|
||||
|
||||
log.info("FacilityChargeSeeder: added unmapped facility row '{}'", facilityType);
|
||||
}
|
||||
|
||||
// 3. Point each still-unmapped facility at its own default charge (ATM_WITHDRAWAL ->
|
||||
// "ATM Withdrawal Charge", ...) so a fresh install shows the correct charge on every row
|
||||
// instead of a dash, on any machine, without an admin re-picking all 15 by hand.
|
||||
//
|
||||
// is_active is deliberately left false: activating the mapping is what actually starts
|
||||
// billing the customer, and that must stay an explicit admin decision made through the
|
||||
// Facility Charge Mappings screen (FacilityChargeAutoApplyService.requireMappedCharge
|
||||
// treats an inactive mapping exactly like an unmapped one, so nothing is charged until
|
||||
// an admin flips Active). This only removes the tedium of choosing the obvious charge,
|
||||
// not the approval step.
|
||||
//
|
||||
// Scoped to charge_id IS NULL so a mapping or amount an admin has already set is never
|
||||
// overwritten -- that makes this safe to re-run on every startup and on existing databases.
|
||||
final int linked = jdbcTemplate.update(
|
||||
"UPDATE m_facility_charge_mapping SET charge_id = "
|
||||
+ "(SELECT id FROM m_charge WHERE name = ? AND is_deleted = false ORDER BY id LIMIT 1) "
|
||||
+ "WHERE facility_type = ? AND charge_id IS NULL",
|
||||
chargeName, facilityType);
|
||||
|
||||
if (linked > 0) {
|
||||
log.info("FacilityChargeSeeder: mapped facility '{}' to charge '{}' (left inactive"
|
||||
+ " -- an admin must activate it before it applies)", facilityType, chargeName);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("FacilityChargeSeeder: seeding complete.");
|
||||
} catch (Exception e) {
|
||||
log.warn("FacilityChargeSeeder: skipped — {}", e.getMessage());
|
||||
// Log the cause, not just the message: this swallowed a startup-ordering failure silently
|
||||
// for a long time because the underlying SQLException never reached the log.
|
||||
log.warn("FacilityChargeSeeder: skipped — {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+58
-57
@@ -1,57 +1,58 @@
|
||||
<?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">
|
||||
<!-- Sequence is starting from 2000 to make it easier to move existing liquibase changesets here -->
|
||||
<include file="parts/2001_add_savings_accrual_job.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2002_add_savings_accrual_permission.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2003_add_accrued_till_date_to_savings_account.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2004_add_savings_account_cob_infrastructure.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2005_add_savings_product_classification_code_values.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2006_add_period_frequency_options.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2007_add_interest_calculation_type_option.xml" relativeToChangelogFile="true" />
|
||||
<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" />
|
||||
<include file="parts/2014_add_is_mandatory_to_joint_holders.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2015_add_senior_citizen_to_savings_account.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2016_add_share_linking_enabled_to_savings_account.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2017_add_saving_product_temp_table.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2018_fix_saving_product_temp_lock_constraint.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2019_add_command_source_link_to_saving_product_temp.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2020_generalize_product_temp_table.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2021_add_fd_rd_approval_permissions.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2022_add_product_checker_permissions.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2023_fix_product_approval_permissions.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2025_recreate_savings_product_temp_table.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2026_add_pending_edit_typed_columns.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2027_add_pending_edit_satellite_tables.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2028_update_saving_product_temp_table.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2029_remove_proposed_payload_json.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2030_add_savings_account_pending_edit_table.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2031_add_savings_account_edit_approval_permissions.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2032_add_share_account_id_to_savings_account.xml" relativeToChangelogFile="true" />
|
||||
</databaseChangeLog>
|
||||
<?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">
|
||||
<!-- Sequence is starting from 2000 to make it easier to move existing liquibase changesets here -->
|
||||
<include file="parts/2001_add_savings_accrual_job.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2002_add_savings_accrual_permission.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2003_add_accrued_till_date_to_savings_account.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2004_add_savings_account_cob_infrastructure.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2005_add_savings_product_classification_code_values.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2006_add_period_frequency_options.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2007_add_interest_calculation_type_option.xml" relativeToChangelogFile="true" />
|
||||
<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" />
|
||||
<include file="parts/2014_add_is_mandatory_to_joint_holders.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2015_add_senior_citizen_to_savings_account.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2016_add_share_linking_enabled_to_savings_account.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2017_add_saving_product_temp_table.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2018_fix_saving_product_temp_lock_constraint.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2019_add_command_source_link_to_saving_product_temp.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2020_generalize_product_temp_table.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2021_add_fd_rd_approval_permissions.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2022_add_product_checker_permissions.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2023_fix_product_approval_permissions.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2025_recreate_savings_product_temp_table.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2026_add_pending_edit_typed_columns.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2027_add_pending_edit_satellite_tables.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2028_update_saving_product_temp_table.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2029_remove_proposed_payload_json.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2030_add_savings_account_pending_edit_table.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2031_add_savings_account_edit_approval_permissions.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2032_add_share_account_id_to_savings_account.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/2032_remove_withdrawal_facility.xml" relativeToChangelogFile="true" />
|
||||
</databaseChangeLog>
|
||||
|
||||
+17
@@ -188,4 +188,21 @@
|
||||
);
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="2005-8-savings-product-account-type-add-normal-saving-account">
|
||||
<sql>
|
||||
INSERT INTO m_code (code_name, is_system_defined)
|
||||
SELECT 'SavingsProductAccountType', TRUE
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_code WHERE code_name = 'SavingsProductAccountType');
|
||||
|
||||
INSERT INTO m_code_value (code_id, code_value, order_position, code_description, is_active, is_mandatory)
|
||||
SELECT c.id, 'Normal Saving Account', 4, 'Normal Saving Account', TRUE, FALSE
|
||||
FROM m_code c
|
||||
WHERE c.code_name = 'SavingsProductAccountType'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_code_value cv
|
||||
WHERE cv.code_id = c.id AND cv.code_value = 'Normal Saving Account'
|
||||
);
|
||||
</sql>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<?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">
|
||||
|
||||
<!--
|
||||
Retires the "Withdrawal Facility" account feature and its auto-applied charge.
|
||||
|
||||
The facility flag is gone from the product/account APIs and UI, so the charge can no
|
||||
longer be switched on for anything new. This changeset stops it billing going forward
|
||||
and hides it from charge pickers, while leaving already-posted transactions intact so
|
||||
historical statements and the trial balance still reconcile.
|
||||
|
||||
The withdrawal_facility / fc_withdrawal_facility columns are deliberately NOT dropped:
|
||||
they are simply no longer mapped or read. Dropping them would be irreversible and buys
|
||||
nothing, and the historical changesets that created them stay untouched so Liquibase
|
||||
checksums keep validating.
|
||||
-->
|
||||
|
||||
<!-- Step 1: unmap the facility type so the seeder/auto-apply can never resolve a charge -->
|
||||
<changeSet id="2032-1" author="cbs-keshav">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<tableExists tableName="m_facility_charge_mapping" />
|
||||
</preConditions>
|
||||
<sql>
|
||||
DELETE FROM m_facility_charge_mapping WHERE facility_type = 'WITHDRAWAL_FACILITY';
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<!-- Step 2: drop pending (unpaid, unwaived) withdrawal-facility charges from savings accounts.
|
||||
Paid or waived rows are left alone so posted accounting entries stay balanced. -->
|
||||
<changeSet id="2032-2" author="cbs-keshav">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<tableExists tableName="m_savings_account_charge" />
|
||||
</preConditions>
|
||||
<sql>
|
||||
DELETE FROM m_savings_account_charge
|
||||
WHERE charge_id IN (SELECT id FROM m_charge WHERE name = 'Withdrawal Facility Charge')
|
||||
AND COALESCE(is_paid_derived, false) = false
|
||||
AND COALESCE(waived, false) = false;
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<!-- Step 3: soft-delete + deactivate the charge definition so it disappears from
|
||||
charge dropdowns without breaking FKs from any historical charge rows. -->
|
||||
<changeSet id="2032-3" author="cbs-keshav">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="1">SELECT COUNT(*) FROM m_charge WHERE name = 'Withdrawal Facility Charge' AND is_deleted = false</sqlCheck>
|
||||
</preConditions>
|
||||
<sql>
|
||||
UPDATE m_charge SET is_active = false, is_deleted = true
|
||||
WHERE name = 'Withdrawal Facility Charge';
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<!-- Step 4: stop the flag driving anything if a stale row is still set -->
|
||||
<changeSet id="2032-4" author="cbs-keshav">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_savings_account_facility_config" />
|
||||
<columnExists tableName="m_savings_account_facility_config" columnName="withdrawal_facility" />
|
||||
</and>
|
||||
</preConditions>
|
||||
<sql>
|
||||
UPDATE m_savings_account_facility_config SET withdrawal_facility = false WHERE withdrawal_facility = true;
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="2032-5" author="cbs-keshav">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_savings_product_facility_config" />
|
||||
<columnExists tableName="m_savings_product_facility_config" columnName="withdrawal_facility" />
|
||||
</and>
|
||||
</preConditions>
|
||||
<sql>
|
||||
UPDATE m_savings_product_facility_config SET withdrawal_facility = false WHERE withdrawal_facility = true;
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user