existing account saving and current add or remove nominee and digital & account features and also FE & RD nominee add or remove

This commit is contained in:
keshav898966
2026-07-10 18:40:16 +05:30
parent 9980310c9e
commit b064ee6307
35 changed files with 3439 additions and 23 deletions
@@ -932,6 +932,9 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom<Long>
}
private BigDecimal getEffectiveOverdraftInterestRateAsFraction(MathContext mc) {
if (this.nominalAnnualInterestRateOverdraft == null) {
return BigDecimal.ZERO;
}
return this.nominalAnnualInterestRateOverdraft.divide(BigDecimal.valueOf(100L), mc);
}
@@ -0,0 +1,213 @@
/**
* 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.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.useradministration.domain.AppUser;
/**
* Holds a pending edit request for an ACTIVE savings account's Nominee + Digital & Account Features.
* Presence of a row here with {@code status = PENDING} (enforced via the unique
* {@link #pendingLockKey}) is itself the lock - nothing is applied to the real
* {@code m_savings_account_facility_config} / {@code m_client_nominee} rows until a checker approves
* (see SavingsAccountEditWritePlatformServiceImpl). Only one nominee change (add/update/delete) can be
* proposed per request, so its fields are kept as flat typed columns on this same table.
*/
@Entity
@Table(name = "m_savings_account_pending_edit")
@Getter
@Setter
public class SavingsAccountPendingEdit extends AbstractPersistableCustom<Long> {
public static final String STATUS_PENDING = "PENDING";
public static final String STATUS_APPROVED = "APPROVED";
public static final String STATUS_REJECTED = "REJECTED";
public static final String NOMINEE_OP_ADD = "ADD";
public static final String NOMINEE_OP_UPDATE = "UPDATE";
public static final String NOMINEE_OP_DELETE = "DELETE";
@ManyToOne
@JoinColumn(name = "savings_account_id", nullable = false)
private SavingsAccount savingsAccount;
@ManyToOne
@JoinColumn(name = "createdby_id", nullable = false)
private AppUser maker;
@ManyToOne
@JoinColumn(name = "lastmodifiedby_id")
private AppUser checker;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdOn;
@Column(name = "lastmodified_date")
private LocalDateTime checkedOn;
@Column(name = "status", nullable = false, length = 20)
private String status;
@Column(name = "reject_reason", length = 500)
private String rejectReason;
/** "SAVINGS_ACCOUNT_EDIT:<savingsAccountId>" while PENDING, NULL otherwise - the real DB-level lock. */
@Column(name = "pending_lock_key", length = 40)
private String pendingLockKey;
/** Links to the audit-only m_portfolio_command_source row created for this edit's submit/approve/reject. */
@Column(name = "command_source_id")
private Long commandSourceId;
@Column(name = "hold_transaction_id")
private Long holdTransactionId;
@Column(name = "hold_amount", precision = 19, scale = 6)
private BigDecimal holdAmount;
@Column(name = "facility_config_submitted", nullable = false)
private boolean facilityConfigSubmitted = false;
@Column(name = "atm_facility")
private Boolean atmFacility;
@Column(name = "atm_withdrawal_facility")
private Boolean atmWithdrawalFacility;
@Column(name = "upi_facility")
private Boolean upiFacility;
@Column(name = "pos_transactions_facility")
private Boolean posTransactionsFacility;
@Column(name = "cheque_book")
private Boolean chequeBook;
@Column(name = "imps_facility")
private Boolean impsFacility;
@Column(name = "sms_alerts")
private Boolean smsAlerts;
@Column(name = "bbps_facility")
private Boolean bbpsFacility;
@Column(name = "insurance_cover")
private Boolean insuranceCover;
@Column(name = "bepg_facility")
private Boolean bepgFacility;
@Column(name = "neft_facility")
private Boolean neftFacility;
@Column(name = "rtgs_facility")
private Boolean rtgsFacility;
@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;
@Column(name = "virtual_debit_card")
private Boolean virtualDebitCard;
@Column(name = "nominee_submitted", nullable = false)
private boolean nomineeSubmitted = false;
@Column(name = "nominee_operation", length = 10)
private String nomineeOperation;
@Column(name = "nominee_id")
private Long nomineeId;
@Column(name = "nominee_client_id")
private Long nomineeClientId;
@Column(name = "relationship_cv_id")
private Long relationshipCvId;
@Column(name = "percentage_share", precision = 5, scale = 2)
private BigDecimal percentageShare;
@Column(name = "nominee_is_active")
private Boolean nomineeIsActive;
@Column(name = "nominee_is_minor")
private Boolean nomineeIsMinor;
@Column(name = "guardian_client_id")
private Long guardianClientId;
@Column(name = "guardian_relationship_cv_id")
private Long guardianRelationshipCvId;
@Column(name = "effective_date")
private LocalDate effectiveDate;
@Column(name = "nominee_remarks", length = 500)
private String nomineeRemarks;
protected SavingsAccountPendingEdit() {}
public SavingsAccountPendingEdit(final SavingsAccount savingsAccount, final AppUser maker) {
this.savingsAccount = savingsAccount;
this.maker = maker;
this.createdOn = LocalDateTime.now();
this.status = STATUS_PENDING;
this.pendingLockKey = lockKeyFor(savingsAccount.getId());
}
public static String lockKeyFor(final Long savingsAccountId) {
return "SAVINGS_ACCOUNT_EDIT:" + savingsAccountId;
}
public boolean isPending() {
return STATUS_PENDING.equals(this.status);
}
public void markApproved(final AppUser checker) {
this.status = STATUS_APPROVED;
this.checker = checker;
this.checkedOn = LocalDateTime.now();
this.pendingLockKey = null;
}
public void markRejected(final AppUser checker, final String reason) {
this.status = STATUS_REJECTED;
this.checker = checker;
this.checkedOn = LocalDateTime.now();
this.rejectReason = reason;
this.pendingLockKey = null;
}
}
@@ -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;
import org.springframework.stereotype.Repository;
@Repository
public interface SavingsAccountPendingEditRepository extends JpaRepository<SavingsAccountPendingEdit, Long> {
Optional<SavingsAccountPendingEdit> findBySavingsAccountIdAndStatus(Long savingsAccountId, String status);
List<SavingsAccountPendingEdit> findByStatus(String status);
}
@@ -0,0 +1,37 @@
/**
* 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.exception;
import java.math.BigDecimal;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* Thrown at edit-submission time when the account's available balance cannot cover the charge for
* the digital/account feature(s) the maker is requesting to enable.
*/
public class SavingsAccountEditInsufficientBalanceException extends AbstractPlatformDomainRuleException {
public SavingsAccountEditInsufficientBalanceException(final Long savingsId, final BigDecimal availableBalance,
final BigDecimal requiredAmount) {
super("error.msg.savingsaccount.edit.insufficient.balance",
"Account balance is insufficient for the requested feature(s). Available balance: " + availableBalance
+ ", required: " + requiredAmount,
savingsId, availableBalance, requiredAmount);
}
}
@@ -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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class SavingsAccountEditPendingApprovalException extends AbstractPlatformDomainRuleException {
public SavingsAccountEditPendingApprovalException(final Long savingsId) {
super("error.msg.savingsaccount.edit.pending.approval",
"Savings account with identifier " + savingsId + " already has a Nominee/Digital-Feature edit pending approval",
savingsId);
}
}
@@ -0,0 +1,31 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.savings.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class SavingsAccountEditSelfApprovalNotAllowedException extends AbstractPlatformDomainRuleException {
public SavingsAccountEditSelfApprovalNotAllowedException(final Long savingsId) {
super("error.msg.savingsaccount.edit.self.approval.not.allowed",
"The maker of the pending Nominee/Digital-Feature edit for savings account " + savingsId
+ " cannot also approve or reject it",
savingsId);
}
}
@@ -0,0 +1,29 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class SavingsAccountNoPendingEditException extends AbstractPlatformDomainRuleException {
public SavingsAccountNoPendingEditException(final Long savingsId) {
super("error.msg.savingsaccount.no.pending.edit",
"Savings account with identifier " + savingsId + " has no Nominee/Digital-Feature edit awaiting approval", savingsId);
}
}
@@ -88,6 +88,81 @@ public class FacilityChargeAutoApplyService {
}
}
/**
* Sum of the mapped charge amounts for the given facility types - used to size the hold placed at
* edit-submission time (see SavingsAccountEditWritePlatformServiceImpl) before any charge is actually applied.
*/
public BigDecimal totalChargeAmountFor(final java.util.Set<String> facilityTypes) {
BigDecimal total = BigDecimal.ZERO;
for (final String facilityType : facilityTypes) {
final java.util.Optional<FacilityChargeMapping> mapping = mappingRepository.findByFacilityType(facilityType);
if (mapping.isPresent() && mapping.get().getCharge() != null) {
total = total.add(mapping.get().getCharge().getAmount());
}
}
return total;
}
/**
* Adds a fresh {@link SavingsAccountCharge} for each of the given (newly re-enabled) facility types, so approving
* a Nominee/Digital-Feature edit always charges again for the transition - unlike
* {@link #applyFacilityChargesOnActivation(SavingsAccount)}'s {@code applyIfEnabled}, this deliberately has NO
* "already applied" dedup-by-name guard: a facility that was enabled at activation, disabled, then re-enabled via
* an approved edit must be charged again, even though a (now-paid, now-irrelevant) charge object with the same
* name already sits in {@code account.charges()} from the earlier cycle.
*/
public List<SavingsAccountCharge> applyChargesForFacilities(final SavingsAccount account, final java.util.Set<String> facilityTypes) {
final List<SavingsAccountCharge> chargesToApply = new ArrayList<>();
for (final String facilityType : facilityTypes) {
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, null, resolveDueDate(charge), true, feeOnMonthDay, charge.getFeeInterval());
accountCharge.update(account);
account.charges().add(accountCharge);
chargesToApply.add(accountCharge);
});
}
if (!chargesToApply.isEmpty()) {
savingsAccountChargeRepository.saveAll(chargesToApply);
}
return chargesToApply;
}
/**
* Pays exactly the given charges (typically the just-added result of {@link #applyChargesForFacilities}) as of
* {@code transactionDate}. Unlike {@link #collectActivationCharges(SavingsAccount)} - which posts at the
* account's activation date and sweeps every unpaid activation-type charge - this is scoped to a specific set of
* charges and a caller-supplied date, for posting a Nominee/Digital-Feature edit's charges on the day it's
* approved rather than backdating them to activation.
*/
public List<SavingsAccountTransaction> collectCharges(final SavingsAccount account, final List<SavingsAccountCharge> charges,
final LocalDate transactionDate) {
final List<SavingsAccountTransaction> transactions = new ArrayList<>();
for (final SavingsAccountCharge charge : charges) {
if (charge.isPaid() || charge.isWaived()) continue;
final BigDecimal amount = charge.getAmountOutstanding(account.getCurrency()).getAmount();
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) continue;
final Money amountMoney = Money.of(account.getCurrency(), amount);
final SavingsAccountTransaction txn = account.payCharge(charge, amountMoney, transactionDate, false, null);
if (txn != null) {
transactions.add(txn);
log.info("Collected facility charge '{}' ₹{} from savings account id={}", charge.getCharge().getName(), amount,
account.getId());
}
}
if (!transactions.isEmpty()) {
savingsAccountTransactionRepository.saveAll(transactions);
account.recalculateBalancesAfterFacilityCharges(transactionDate);
}
return transactions;
}
/**
* Collects all unpaid SavingsActivation charges on the account.
* Called after facility charges are saved and flushed, so amounts are properly initialized.
@@ -0,0 +1,36 @@
/**
* 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 org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.savings.data.SavingsAccountPendingEditData;
public interface SavingsAccountEditWritePlatformService {
CommandProcessingResult submitEditForApproval(Long savingsId, JsonCommand command);
CommandProcessingResult approveEdit(Long savingsId);
CommandProcessingResult rejectEdit(Long savingsId, String rejectReason);
SavingsAccountPendingEditData retrievePendingEdit(Long savingsId);
java.util.Collection<SavingsAccountPendingEditData> retrieveAllPendingEdits();
}
@@ -51,4 +51,6 @@
<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" />
</databaseChangeLog>
@@ -0,0 +1,124 @@
<?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">
<!--
Holds a pending edit request for an ACTIVE savings account's Nominee + Digital & Account
Features, mirroring the m_savings_product_temp maker-checker pattern: presence of a row here
with status=PENDING (enforced via the unique pending_lock_key) is itself the lock, and nothing
is applied to the real m_savings_account_facility_config / m_client_nominee rows until a
checker approves (see SavingsAccountEditWritePlatformServiceImpl). One row per request; only
one nominee change (add/update/delete) can be proposed per request, so its fields are kept as
flat typed columns on this same table rather than a satellite table.
-->
<changeSet author="fineract" id="2030-1">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="m_savings_account_pending_edit" />
</not>
</preConditions>
<createTable tableName="m_savings_account_pending_edit">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false" />
</column>
<column name="savings_account_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_saoe_savings_account_id" references="m_savings_account(id)" />
</column>
<column name="createdby_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_saoe_maker_id" references="m_appuser(id)" />
</column>
<column name="lastmodifiedby_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_saoe_checker_id" references="m_appuser(id)" />
</column>
<column name="created_date" type="DATETIME">
<constraints nullable="false" />
</column>
<column name="lastmodified_date" type="DATETIME">
<constraints nullable="true" />
</column>
<column name="status" type="VARCHAR(20)" defaultValue="PENDING">
<constraints nullable="false" />
</column>
<column name="reject_reason" type="VARCHAR(500)" />
<column name="pending_lock_key" type="VARCHAR(40)">
<constraints nullable="true" unique="true" uniqueConstraintName="saoe_unq_pending_lock_key" />
</column>
<column name="command_source_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_saoe_command_source_id" references="m_portfolio_command_source(id)" />
</column>
<!-- Hold placed at submission time for the total charge of newly-enabled facilities; released
(no charge) on reject, released-then-converted-to-a-posted-charge on approve. -->
<column name="hold_transaction_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_saoe_hold_txn_id" references="m_savings_account_transaction(id)" />
</column>
<column name="hold_amount" type="DECIMAL(19,6)" />
<!-- Digital & Account Features - same 16 columns/names as m_savings_account_facility_config.
NULL = maker did not touch this toggle; facility_config_submitted tells approve()/UI
whether this block was submitted at all. -->
<column name="facility_config_submitted" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false" />
</column>
<column name="atm_facility" type="BOOLEAN" />
<column name="atm_withdrawal_facility" type="BOOLEAN" />
<column name="upi_facility" type="BOOLEAN" />
<column name="pos_transactions_facility" type="BOOLEAN" />
<column name="cheque_book" type="BOOLEAN" />
<column name="imps_facility" type="BOOLEAN" />
<column name="sms_alerts" type="BOOLEAN" />
<column name="bbps_facility" type="BOOLEAN" />
<column name="insurance_cover" type="BOOLEAN" />
<column name="bepg_facility" type="BOOLEAN" />
<column name="neft_facility" type="BOOLEAN" />
<column name="rtgs_facility" type="BOOLEAN" />
<column name="inter_bank_total_cap_facility" type="BOOLEAN" />
<column name="withdrawal_facility" type="BOOLEAN" />
<column name="deposit_facility_kyc" type="BOOLEAN" />
<column name="virtual_debit_card" type="BOOLEAN" />
<!-- Nominee change - one per request (add / update / delete), same fields as m_client_nominee. -->
<column name="nominee_submitted" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false" />
</column>
<column name="nominee_operation" type="VARCHAR(10)" />
<column name="nominee_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_saoe_nominee_id" references="m_client_nominee(id)" />
</column>
<column name="nominee_client_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_saoe_nominee_client_id" references="m_client(id)" />
</column>
<column name="relationship_cv_id" type="BIGINT" />
<column name="percentage_share" type="DECIMAL(5,2)" />
<column name="nominee_is_active" type="BOOLEAN" />
<column name="nominee_is_minor" type="BOOLEAN" />
<column name="guardian_client_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_saoe_guardian_client_id" references="m_client(id)" />
</column>
<column name="guardian_relationship_cv_id" type="BIGINT" />
<column name="effective_date" type="DATE" />
<column name="nominee_remarks" type="VARCHAR(500)" />
</createTable>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,84 @@
<?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">
<!--
Permissions gating the Nominee + Digital & Account Features maker-checker edit flow for
savings accounts. SUBMITEDIT lets a maker propose the edit (this is the "Edit" action shown
on the account view); APPROVEEDIT/REJECTEDIT let a checker resolve it. The _CHECKER permission
follows the "<ACTION>_<ENTITY>_CHECKER" convention (see 2022_add_product_checker_permissions.xml)
so the pending request also surfaces in the generic Checker Inbox for limited checkers.
-->
<changeSet author="fineract" id="2031-1">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'SUBMITEDIT_SAVINGSACCOUNT'</sqlCheck>
</preConditions>
<insert tableName="m_permission">
<column name="grouping" value="transaction_savings" />
<column name="code" value="SUBMITEDIT_SAVINGSACCOUNT" />
<column name="entity_name" value="SAVINGSACCOUNT" />
<column name="action_name" value="SUBMITEDIT" />
<column name="can_maker_checker" valueBoolean="false" />
</insert>
</changeSet>
<changeSet author="fineract" id="2031-2">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'APPROVEEDIT_SAVINGSACCOUNT'</sqlCheck>
</preConditions>
<insert tableName="m_permission">
<column name="grouping" value="transaction_savings" />
<column name="code" value="APPROVEEDIT_SAVINGSACCOUNT" />
<column name="entity_name" value="SAVINGSACCOUNT" />
<column name="action_name" value="APPROVEEDIT" />
<column name="can_maker_checker" valueBoolean="false" />
</insert>
</changeSet>
<changeSet author="fineract" id="2031-3">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'REJECTEDIT_SAVINGSACCOUNT'</sqlCheck>
</preConditions>
<insert tableName="m_permission">
<column name="grouping" value="transaction_savings" />
<column name="code" value="REJECTEDIT_SAVINGSACCOUNT" />
<column name="entity_name" value="SAVINGSACCOUNT" />
<column name="action_name" value="REJECTEDIT" />
<column name="can_maker_checker" valueBoolean="false" />
</insert>
</changeSet>
<changeSet author="fineract" id="2031-4">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'APPROVEEDIT_SAVINGSACCOUNT_CHECKER'</sqlCheck>
</preConditions>
<insert tableName="m_permission">
<column name="grouping" value="transaction_savings" />
<column name="code" value="APPROVEEDIT_SAVINGSACCOUNT_CHECKER" />
<column name="entity_name" value="SAVINGSACCOUNT" />
<column name="action_name" value="APPROVEEDIT_CHECKER" />
<column name="can_maker_checker" valueBoolean="false" />
</insert>
</changeSet>
</databaseChangeLog>