implemented the Edit prodict functionality for the RD,FD,DDS,LIC

This commit is contained in:
2026-10-16 10:33:57 +05:30
parent 0afcf059a8
commit 4c40784a0e
28 changed files with 874 additions and 32 deletions
@@ -79,6 +79,17 @@ public class MakercheckersApiResource {
}
@GET
@Path("/mine")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "List my pending Maker Checker Entries", description = "Requests the requestor submitted that still await approval. Unlike the checker inbox it needs no checker permission, so a maker can see what is pending. Example: makercheckers/mine?entityName=LOANPRODUCT")
public List<AuditData> retrieveMyPendingCommands(@Context final UriInfo uriInfo, @BeanParam MakerCheckerRequest makerCheckerRequest) {
final SQLBuilder extraCriteria = getExtraCriteria(makerCheckerRequest);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return readPlatformService.retrieveMyPendingEntries(extraCriteria, settings.isIncludeJson());
}
@GET
@Path("/searchtemplate")
@Consumes({ MediaType.APPLICATION_JSON })
@@ -33,6 +33,9 @@ public interface AuditReadPlatformService {
List<AuditData> retrieveAllEntriesToBeChecked(SQLBuilder extraCriteria, boolean includeJson);
/** Requests the current user submitted that still await a checker, whatever the user's own checker permissions. */
List<AuditData> retrieveMyPendingEntries(SQLBuilder extraCriteria, boolean includeJson);
AuditData retrieveAuditEntry(Long auditId);
AuditSearchData retrieveSearchTemplate(String useType);
@@ -214,10 +214,17 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService {
return retrieveEntries("makerchecker", extraCriteria, " order by aud.id, mk.username", includeJson);
}
@Override
public List<AuditData> retrieveMyPendingEntries(final SQLBuilder extraCriteria, final boolean includeJson) {
extraCriteria.addCriteria("aud.status = ", 2);
extraCriteria.addCriteria("aud.maker_id = ", this.context.authenticatedUser().getId());
return retrieveEntries("makerchecker-mine", extraCriteria, " order by aud.id, mk.username", includeJson);
}
private List<AuditData> retrieveEntries(final String useType, final SQLBuilder extraCriteria, final String groupAndOrderBySQL,
final boolean includeJson) {
if ((!useType.equals("audit") && !useType.equals("makerchecker"))) {
if ((!useType.equals("audit") && !useType.equals("makerchecker") && !useType.equals("makerchecker-mine"))) {
throw new PlatformDataIntegrityException("error.msg.invalid.auditSearchTemplate.useType",
"Invalid Audit Search Template UseType: " + useType);
}
@@ -679,8 +679,9 @@ public final class LoanProductDataValidator {
final Long transfersInSuspenseAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element);
// not mapped by the bank (only the nine GLs are); optional
baseDataValidator.reset().parameter(LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue())
.value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero();
.value(transfersInSuspenseAccountId).ignoreIfNull().integerGreaterThanZero();
final Long incomeFromInterestId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INTEREST_ON_LOANS.getValue(), element);
@@ -700,7 +701,7 @@ public final class LoanProductDataValidator {
final Long incomeFromRecoveryAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), element);
baseDataValidator.reset().parameter(LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue())
.value(incomeFromRecoveryAccountId).notNull().integerGreaterThanZero();
.value(incomeFromRecoveryAccountId).ignoreIfNull().integerGreaterThanZero();
final Long writeOffAccountId = this.fromApiJsonHelper
.extractLongNamed(LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), element);
@@ -43,7 +43,8 @@ public class LoanProductAdvanceExtService {
"securityType", "chargeType", "eligibleAmountBasis", "margin", "ltv", "dpMethod", "lienRequired",
"lienMarkingStage", "lienCertificateRequired", "lienCertificatePrefix", "printCertificateOnDisbursement",
"interestBasis", "spread", "interestChargedOn", "interestPostingFrequency", "penalApplicable", "penalRate",
"penalAppliedOn", "penalGraceDays", "npaTrigger", "interestAfterNpa", "undebitedInterestGlId",
"penalAppliedOn", "penalGraceDays", "npaTrigger", "smaStartAfterMonths", "stockStatementValidityDays", "memberRequired", "guarantorMustBeMember",
"incomeMultiple", "minVintageMonths", "requiredDocuments", "interestAfterNpa", "undebitedInterestGlId",
"renewalAllowed", "renewalPeriodMonths", "renewalGraceDays", "preclosureAllowed", "lockInMonths",
"interestOnPreclosure", "revaluationFreqMonths", "insuranceRequired", "stockStatementFreqMonths",
"chequeBookEnabled", "chequeType", "defaultChequeBookSize", "ppsThreshold", "ppsMandatoryAbove",
@@ -76,7 +77,11 @@ public class LoanProductAdvanceExtService {
"partPrepaymentAllowed", "minPartPayment", "allowedOn", "partPaymentsPerYear",
"partPrepaymentMode", "foreclosureFloatingIndividual", "foreclosureFixedIndividual", "foreclosureNonIndividual",
"foreclosureTakeover", "foreclosureQuoteValidDays", "autoCloseOnZeroBalance", "nocLetter",
"restructuring", "releaseCollateralOnClosure", "topUpAllowed", "balanceTransferOutAllowed" };
"restructuring", "releaseCollateralOnClosure", "topUpAllowed", "balanceTransferOutAllowed",
// SICR & LGD (RBI 2026 ECL framework)
"sicrDaysThreshold", "sicrRevolvingDaysThreshold", "sicrRatingDowngradeNotches", "sicrPricingIncreaseTriggerPct",
"sicrAssessmentBasis", "sicrRebuttalAllowed", "npaOutOfOrderBasis", "cropDuration", "useOwnLgdModel", "lgdSecuredPct",
"lgdUnsecuredPct", "eligibleCollateralType" };
/**
* Insert or partial-update the satellite for this product. Returns the fields that actually changed so the
@@ -155,6 +160,14 @@ public class LoanProductAdvanceExtService {
setString(command, "penalAppliedOn", ext::getPenalAppliedOn, ext::setPenalAppliedOn, changes);
setInteger(command, "penalGraceDays", ext::getPenalGraceDays, ext::setPenalGraceDays, changes);
setString(command, "npaTrigger", ext::getNpaTrigger, ext::setNpaTrigger, changes);
setInteger(command, "smaStartAfterMonths", ext::getSmaStartAfterMonths, ext::setSmaStartAfterMonths, changes);
setBoolean(command, "memberRequired", ext::getMemberRequired, ext::setMemberRequired, changes);
setBoolean(command, "guarantorMustBeMember", ext::getGuarantorMustBeMember, ext::setGuarantorMustBeMember, changes);
setDecimal(command, "incomeMultiple", ext::getIncomeMultiple, ext::setIncomeMultiple, changes);
setInteger(command, "minVintageMonths", ext::getMinVintageMonths, ext::setMinVintageMonths, changes);
setString(command, "requiredDocuments", ext::getRequiredDocuments, ext::setRequiredDocuments, changes);
setInteger(command, "stockStatementValidityDays", ext::getStockStatementValidityDays,
ext::setStockStatementValidityDays, changes);
setString(command, "interestAfterNpa", ext::getInterestAfterNpa, ext::setInterestAfterNpa, changes);
setLong(command, "undebitedInterestGlId", ext::getUndebitedInterestGlId, ext::setUndebitedInterestGlId, changes);
setBoolean(command, "renewalAllowed", ext::getRenewalAllowed, ext::setRenewalAllowed, changes);
@@ -289,6 +302,20 @@ public class LoanProductAdvanceExtService {
setBoolean(command, "releaseCollateralOnClosure", ext::getReleaseCollateralOnClosure, ext::setReleaseCollateralOnClosure, changes);
setBoolean(command, "topUpAllowed", ext::getTopUpAllowed, ext::setTopUpAllowed, changes);
setBoolean(command, "balanceTransferOutAllowed", ext::getBalanceTransferOutAllowed, ext::setBalanceTransferOutAllowed, changes);
// SICR & LGD (RBI 2026 ECL framework)
setInteger(command, "sicrDaysThreshold", ext::getSicrDaysThreshold, ext::setSicrDaysThreshold, changes);
setInteger(command, "sicrRevolvingDaysThreshold", ext::getSicrRevolvingDaysThreshold, ext::setSicrRevolvingDaysThreshold, changes);
setInteger(command, "sicrRatingDowngradeNotches", ext::getSicrRatingDowngradeNotches, ext::setSicrRatingDowngradeNotches, changes);
setDecimal(command, "sicrPricingIncreaseTriggerPct", ext::getSicrPricingIncreaseTriggerPct, ext::setSicrPricingIncreaseTriggerPct,
changes);
setString(command, "sicrAssessmentBasis", ext::getSicrAssessmentBasis, ext::setSicrAssessmentBasis, changes);
setBoolean(command, "sicrRebuttalAllowed", ext::getSicrRebuttalAllowed, ext::setSicrRebuttalAllowed, changes);
setString(command, "npaOutOfOrderBasis", ext::getNpaOutOfOrderBasis, ext::setNpaOutOfOrderBasis, changes);
setString(command, "cropDuration", ext::getCropDuration, ext::setCropDuration, changes);
setBoolean(command, "useOwnLgdModel", ext::getUseOwnLgdModel, ext::setUseOwnLgdModel, changes);
setDecimal(command, "lgdSecuredPct", ext::getLgdSecuredPct, ext::setLgdSecuredPct, changes);
setDecimal(command, "lgdUnsecuredPct", ext::getLgdUnsecuredPct, ext::setLgdUnsecuredPct, changes);
setString(command, "eligibleCollateralType", ext::getEligibleCollateralType, ext::setEligibleCollateralType, changes);
}
// ---- typed setters: only write params that are present, and only report real changes ----------
@@ -403,6 +430,13 @@ public class LoanProductAdvanceExtService {
map.put("penalAppliedOn", ext.getPenalAppliedOn());
map.put("penalGraceDays", ext.getPenalGraceDays());
map.put("npaTrigger", ext.getNpaTrigger());
map.put("smaStartAfterMonths", ext.getSmaStartAfterMonths());
map.put("memberRequired", ext.getMemberRequired());
map.put("guarantorMustBeMember", ext.getGuarantorMustBeMember());
map.put("incomeMultiple", ext.getIncomeMultiple());
map.put("minVintageMonths", ext.getMinVintageMonths());
map.put("requiredDocuments", ext.getRequiredDocuments());
map.put("stockStatementValidityDays", ext.getStockStatementValidityDays());
map.put("interestAfterNpa", ext.getInterestAfterNpa());
map.put("undebitedInterestGlId", ext.getUndebitedInterestGlId());
map.put("renewalAllowed", ext.getRenewalAllowed());
@@ -509,6 +543,19 @@ public class LoanProductAdvanceExtService {
map.put("releaseCollateralOnClosure", ext.getReleaseCollateralOnClosure());
map.put("topUpAllowed", ext.getTopUpAllowed());
map.put("balanceTransferOutAllowed", ext.getBalanceTransferOutAllowed());
// SICR & LGD (RBI 2026 ECL framework)
map.put("sicrDaysThreshold", ext.getSicrDaysThreshold());
map.put("sicrRevolvingDaysThreshold", ext.getSicrRevolvingDaysThreshold());
map.put("sicrRatingDowngradeNotches", ext.getSicrRatingDowngradeNotches());
map.put("sicrPricingIncreaseTriggerPct", ext.getSicrPricingIncreaseTriggerPct());
map.put("sicrAssessmentBasis", ext.getSicrAssessmentBasis());
map.put("sicrRebuttalAllowed", ext.getSicrRebuttalAllowed());
map.put("npaOutOfOrderBasis", ext.getNpaOutOfOrderBasis());
map.put("cropDuration", ext.getCropDuration());
map.put("useOwnLgdModel", ext.getUseOwnLgdModel());
map.put("lgdSecuredPct", ext.getLgdSecuredPct());
map.put("lgdUnsecuredPct", ext.getLgdUnsecuredPct());
map.put("eligibleCollateralType", ext.getEligibleCollateralType());
return map;
}
}
@@ -0,0 +1,63 @@
/**
* 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.prudentialfloor.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.prudentialfloor.data.PrudentialFloorData;
import org.apache.fineract.portfolio.prudentialfloor.service.PrudentialFloorReadPlatformService;
import org.springframework.stereotype.Component;
/**
* Read-only: this master is seeded from the RBI 2026 Directions (para 82) rather than created per bank, so it
* ships without a create/update command path for now - a Board-approved "tighten only" override is a follow-up.
*/
@RequiredArgsConstructor
@Path("/v1/prudentialfloors")
@Component
@Tag(name = "Prudential Floor Master", description = "RBI 2026 ECL stage-wise prudential floor lookup, by loan product category")
public class PrudentialFloorsApiResource {
private final PlatformSecurityContext securityContext;
private final PrudentialFloorReadPlatformService readPlatformService;
public static final String PRUDENTIAL_FLOOR = "PRUDENTIAL_FLOOR";
@GET
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "List all Prudential Floor rows", description = "Optionally filter by product category")
public List<PrudentialFloorData> getPrudentialFloors(
@QueryParam("productCategory") @Parameter(description = "productCategory") final String productCategory) {
securityContext.authenticatedUser().validateHasReadPermission(PRUDENTIAL_FLOOR);
if (productCategory == null || productCategory.isBlank()) {
return this.readPlatformService.retrieveAll();
}
return this.readPlatformService.retrieveForCategory(productCategory);
}
}
@@ -0,0 +1,63 @@
/**
* 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.prudentialfloor.service;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.prudentialfloor.data.PrudentialFloorData;
import org.apache.fineract.portfolio.prudentialfloor.domain.PrudentialFloor;
import org.apache.fineract.portfolio.prudentialfloor.domain.PrudentialFloorRepository;
import org.apache.fineract.portfolio.prudentialfloor.mapper.PrudentialFloorMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Read-only lookup over the RBI 2026 prudential-floor master (para 82). A loan product never stores its own
* floor %: it declares a product category and a stage (the latter via its Delinquency Bucket), and the
* applicable floor is resolved here at reporting/provisioning time, so every product using the same category
* always sees the same, currently-seeded regulatory minimum.
*/
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PrudentialFloorReadPlatformService {
private final PrudentialFloorRepository repository;
private final PrudentialFloorMapper mapper;
public List<PrudentialFloorData> retrieveAll() {
return mapper.map(repository.findAll());
}
public List<PrudentialFloorData> retrieveForCategory(final String productCategory) {
return mapper.map(repository.findByProductCategoryOrderByStageAscDurationFromDaysAsc(productCategory));
}
/**
* The floor applicable to one exposure: for Stage 1/2 the single row for that category+stage; for Stage 3
* the row whose duration band contains {@code daysInStage} (days since the exposure entered Stage 3, not
* days overdue). Returns {@code null} when the category/stage/duration combination has not been seeded -
* callers should fall back to the residual "any other loan product category" floor (para 82 item (xv)) in
* that case rather than treat it as zero.
*/
public PrudentialFloorData retrieveFloor(final String productCategory, final Integer stage, final Integer daysInStage) {
final List<PrudentialFloor> candidates = repository.findByProductCategoryAndStage(productCategory, stage);
return candidates.stream().filter(row -> row.coversDuration(daysInStage)).findFirst().map(mapper::map).orElse(null);
}
}
@@ -623,6 +623,13 @@
<include file="parts/0595_seed_loan_rate_register_gl_accounts.xml" relativeToChangelogFile="true"/>
<include file="parts/0596_enable_delinquency_maker_checker.xml" relativeToChangelogFile="true"/>
<include file="parts/0597_add_loan_product_loans_ext_fields.xml" relativeToChangelogFile="true"/>
<include file="parts/0598_add_loan_product_sma_start_fields.xml" relativeToChangelogFile="true"/>
<include file="parts/0599_enable_loan_product_update_maker_checker.xml" relativeToChangelogFile="true"/>
<!-- RBI 2026 ECL framework: SMA/NPA asset classification, prudential floor master, SICR/LGD product config. -->
<include file="parts/0599_add_delinquency_range_stage.xml" relativeToChangelogFile="true"/>
<include file="parts/0600_create_prudential_floor_master.xml" relativeToChangelogFile="true"/>
<include file="parts/0601_add_loan_product_advance_ext_sicr_lgd_fields.xml" relativeToChangelogFile="true"/>
<!-- DDS: the scheme's own tables, settings and permissions. Kept together and last, because
each one builds on the core the block above sets up and none of it builds on DDS. -->
@@ -0,0 +1,39 @@
<?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">
<!-- SMA counting start (Advances: 9 months after disbursement, Loans/EMI: 1 month) and the CC/OD stock
statement validity window that feeds the NPA trigger. -->
<changeSet author="cbsnexora" id="0598_add_loan_product_sma_start_fields">
<addColumn tableName="m_product_loan_advance_ext">
<column name="sma_start_after_months" type="INT"/>
<column name="stock_statement_validity_days" type="INT"/>
<column name="elig_member_required" type="BOOLEAN"/>
<column name="elig_guarantor_must_be_member" type="BOOLEAN"/>
<column name="elig_income_multiple" type="DECIMAL(10,2)"/>
<column name="elig_min_vintage_months" type="INT"/>
<column name="required_documents" type="VARCHAR(1000)"/>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,35 @@
<?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">
<!-- RBI 2026 ECL framework: a delinquency range only ever decided an SMA/NPA label by day-count; it now also
records which ECL stage (1, 2 or 3) that label maps to, so a product's bucket can drive staging directly
instead of a parallel lookup. Nullable: existing ranges keep working as plain labels until re-classified. -->
<changeSet author="cbsnexora" id="0599_add_delinquency_range_stage">
<addColumn tableName="m_delinquency_range">
<column name="stage" type="INT" defaultValueComputed="NULL"/>
</addColumn>
</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">
<!--
Maker-checker on editing a loan or advance product.
Creating a product already goes to a checker (CREATE_LOANPRODUCT is gated). An edit changes rates,
limits, margin, GL accounts and lien rules that live accounts depend on, so it must not take effect on
one person's click either. UPDATE_LOANPRODUCT is gated with can_maker_checker = true: an edit lands in
m_portfolio_command_source as PENDING and is applied when a checker approves it. UPDATE_LOANPRODUCT_CHECKER
already exists and is what approving requires. Roles are left as they are.
-->
<changeSet id="0599_loan_product_update_maker_checker" author="cbsnexora" runOnChange="false">
<validCheckSum>ANY</validCheckSum>
<sql>
UPDATE m_permission
SET can_maker_checker = TRUE
WHERE code = 'UPDATE_LOANPRODUCT';
</sql>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,167 @@
<?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">
<!-- RBI 2026 ECL framework (Directions para 82): the stage-wise prudential floor, keyed by loan product
category rather than by loan product. Seeded below with a representative set of categories; the
remaining categories and the category-specific Stage 3 table variants (unsecured retail, housing/other
real estate, government-guaranteed) are a follow-up seed, not a schema change. created_by/
last_modified_by use 1 (the system user) since this is regulatory data, not something a maker entered. -->
<changeSet author="cbsnexora" id="0600_create_prudential_floor_master_table">
<createTable tableName="m_prudential_floor">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="product_category" type="VARCHAR(100)">
<constraints nullable="false"/>
</column>
<column name="stage" type="INT">
<constraints nullable="false"/>
</column>
<column name="duration_from_days" type="INT" defaultValueComputed="NULL"/>
<column name="duration_to_days" type="INT" defaultValueComputed="NULL"/>
<column name="secured_floor_pct" type="DECIMAL(19,6)">
<constraints nullable="false"/>
</column>
<column name="unsecured_floor_pct" type="DECIMAL(19,6)">
<constraints nullable="false"/>
</column>
<column name="created_by" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="created_on_utc" type="DATETIME">
<constraints nullable="false"/>
</column>
<column name="version" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="last_modified_by" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="last_modified_on_utc" type="DATETIME">
<constraints nullable="false"/>
</column>
</createTable>
<createIndex indexName="IX_m_prudential_floor_category_stage" tableName="m_prudential_floor">
<column name="product_category"/>
<column name="stage"/>
</createIndex>
</changeSet>
<changeSet author="cbsnexora" id="0600_add_prudential_floor_permission">
<insert tableName="m_permission">
<column name="grouping" value="organisation"/>
<column name="code" value="READ_PRUDENTIAL_FLOOR"/>
<column name="entity_name" value="PRUDENTIAL_FLOOR"/>
<column name="action_name" value="READ"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
<changeSet author="cbsnexora" id="0600_seed_prudential_floor_stage_1_2">
<!-- Stage 1 / Stage 2: a single floor per category (not split secured/unsecured in the Directions),
so secured_floor_pct = unsecured_floor_pct here. -->
<insert tableName="m_prudential_floor"><column name="product_category" value="Secured Retail Loans"/><column name="stage" valueNumeric="1"/><column name="secured_floor_pct" valueNumeric="0.40"/><column name="unsecured_floor_pct" valueNumeric="0.40"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Secured Retail Loans"/><column name="stage" valueNumeric="2"/><column name="secured_floor_pct" valueNumeric="5.00"/><column name="unsecured_floor_pct" valueNumeric="5.00"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Corporate Loan"/><column name="stage" valueNumeric="1"/><column name="secured_floor_pct" valueNumeric="0.40"/><column name="unsecured_floor_pct" valueNumeric="0.40"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Corporate Loan"/><column name="stage" valueNumeric="2"/><column name="secured_floor_pct" valueNumeric="5.00"/><column name="unsecured_floor_pct" valueNumeric="5.00"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Small and Micro Enterprises"/><column name="stage" valueNumeric="1"/><column name="secured_floor_pct" valueNumeric="0.25"/><column name="unsecured_floor_pct" valueNumeric="0.25"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Small and Micro Enterprises"/><column name="stage" valueNumeric="2"/><column name="secured_floor_pct" valueNumeric="5.00"/><column name="unsecured_floor_pct" valueNumeric="5.00"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Medium Enterprises"/><column name="stage" valueNumeric="1"/><column name="secured_floor_pct" valueNumeric="0.40"/><column name="unsecured_floor_pct" valueNumeric="0.40"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Medium Enterprises"/><column name="stage" valueNumeric="2"/><column name="secured_floor_pct" valueNumeric="5.00"/><column name="unsecured_floor_pct" valueNumeric="5.00"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Farm Credit - Agricultural Activities"/><column name="stage" valueNumeric="1"/><column name="secured_floor_pct" valueNumeric="0.25"/><column name="unsecured_floor_pct" valueNumeric="0.25"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Farm Credit - Agricultural Activities"/><column name="stage" valueNumeric="2"/><column name="secured_floor_pct" valueNumeric="5.00"/><column name="unsecured_floor_pct" valueNumeric="5.00"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Unsecured Retail Loans"/><column name="stage" valueNumeric="1"/><column name="secured_floor_pct" valueNumeric="1.00"/><column name="unsecured_floor_pct" valueNumeric="1.00"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Unsecured Retail Loans"/><column name="stage" valueNumeric="2"/><column name="secured_floor_pct" valueNumeric="5.00"/><column name="unsecured_floor_pct" valueNumeric="5.00"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Gold Loan"/><column name="stage" valueNumeric="1"/><column name="secured_floor_pct" valueNumeric="0.40"/><column name="unsecured_floor_pct" valueNumeric="0.40"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Gold Loan"/><column name="stage" valueNumeric="2"/><column name="secured_floor_pct" valueNumeric="1.50"/><column name="unsecured_floor_pct" valueNumeric="1.50"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Housing Loan - Individual"/><column name="stage" valueNumeric="1"/><column name="secured_floor_pct" valueNumeric="0.25"/><column name="unsecured_floor_pct" valueNumeric="0.25"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Housing Loan - Individual"/><column name="stage" valueNumeric="2"/><column name="secured_floor_pct" valueNumeric="1.50"/><column name="unsecured_floor_pct" valueNumeric="1.50"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Loan against Term Deposit / LIC / KVP"/><column name="stage" valueNumeric="1"/><column name="secured_floor_pct" valueNumeric="0.40"/><column name="unsecured_floor_pct" valueNumeric="0.40"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Loan against Term Deposit / LIC / KVP"/><column name="stage" valueNumeric="2"/><column name="secured_floor_pct" valueNumeric="0.40"/><column name="unsecured_floor_pct" valueNumeric="0.40"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
</changeSet>
<changeSet author="cbsnexora" id="0600_seed_prudential_floor_stage_3_standard">
<!-- Stage 3 ("standard" duration table, para 82(5) first table): applied here to every seeded category
alike as a starting point. The Directions in fact vary this table by category group (housing/real
estate and unsecured retail use their own bands) - narrowing this per category is a follow-up seed,
not a schema change, since duration_from_days/duration_to_days already carry whatever band a row
needs. -->
<insert tableName="m_prudential_floor"><column name="product_category" value="Secured Retail Loans"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="0"/><column name="duration_to_days" valueNumeric="365"/><column name="secured_floor_pct" valueNumeric="25"/><column name="unsecured_floor_pct" valueNumeric="40"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Secured Retail Loans"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="366"/><column name="duration_to_days" valueNumeric="730"/><column name="secured_floor_pct" valueNumeric="40"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Secured Retail Loans"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="731"/><column name="duration_to_days" valueNumeric="1095"/><column name="secured_floor_pct" valueNumeric="55"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Secured Retail Loans"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1096"/><column name="duration_to_days" valueNumeric="1460"/><column name="secured_floor_pct" valueNumeric="75"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Secured Retail Loans"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1461"/><column name="secured_floor_pct" valueNumeric="100"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Corporate Loan"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="0"/><column name="duration_to_days" valueNumeric="365"/><column name="secured_floor_pct" valueNumeric="25"/><column name="unsecured_floor_pct" valueNumeric="40"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Corporate Loan"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="366"/><column name="duration_to_days" valueNumeric="730"/><column name="secured_floor_pct" valueNumeric="40"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Corporate Loan"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="731"/><column name="duration_to_days" valueNumeric="1095"/><column name="secured_floor_pct" valueNumeric="55"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Corporate Loan"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1096"/><column name="duration_to_days" valueNumeric="1460"/><column name="secured_floor_pct" valueNumeric="75"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Corporate Loan"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1461"/><column name="secured_floor_pct" valueNumeric="100"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Small and Micro Enterprises"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="0"/><column name="duration_to_days" valueNumeric="365"/><column name="secured_floor_pct" valueNumeric="25"/><column name="unsecured_floor_pct" valueNumeric="40"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Small and Micro Enterprises"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="366"/><column name="duration_to_days" valueNumeric="730"/><column name="secured_floor_pct" valueNumeric="40"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Small and Micro Enterprises"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="731"/><column name="duration_to_days" valueNumeric="1095"/><column name="secured_floor_pct" valueNumeric="55"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Small and Micro Enterprises"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1096"/><column name="duration_to_days" valueNumeric="1460"/><column name="secured_floor_pct" valueNumeric="75"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Small and Micro Enterprises"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1461"/><column name="secured_floor_pct" valueNumeric="100"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Medium Enterprises"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="0"/><column name="duration_to_days" valueNumeric="365"/><column name="secured_floor_pct" valueNumeric="25"/><column name="unsecured_floor_pct" valueNumeric="40"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Medium Enterprises"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="366"/><column name="duration_to_days" valueNumeric="730"/><column name="secured_floor_pct" valueNumeric="40"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Medium Enterprises"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="731"/><column name="duration_to_days" valueNumeric="1095"/><column name="secured_floor_pct" valueNumeric="55"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Medium Enterprises"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1096"/><column name="duration_to_days" valueNumeric="1460"/><column name="secured_floor_pct" valueNumeric="75"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Medium Enterprises"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1461"/><column name="secured_floor_pct" valueNumeric="100"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Farm Credit - Agricultural Activities"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="0"/><column name="duration_to_days" valueNumeric="365"/><column name="secured_floor_pct" valueNumeric="25"/><column name="unsecured_floor_pct" valueNumeric="40"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Farm Credit - Agricultural Activities"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="366"/><column name="duration_to_days" valueNumeric="730"/><column name="secured_floor_pct" valueNumeric="40"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Farm Credit - Agricultural Activities"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="731"/><column name="duration_to_days" valueNumeric="1095"/><column name="secured_floor_pct" valueNumeric="55"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Farm Credit - Agricultural Activities"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1096"/><column name="duration_to_days" valueNumeric="1460"/><column name="secured_floor_pct" valueNumeric="75"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Farm Credit - Agricultural Activities"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1461"/><column name="secured_floor_pct" valueNumeric="100"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Gold Loan"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="0"/><column name="duration_to_days" valueNumeric="365"/><column name="secured_floor_pct" valueNumeric="25"/><column name="unsecured_floor_pct" valueNumeric="40"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Gold Loan"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="366"/><column name="duration_to_days" valueNumeric="730"/><column name="secured_floor_pct" valueNumeric="40"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Gold Loan"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="731"/><column name="duration_to_days" valueNumeric="1095"/><column name="secured_floor_pct" valueNumeric="55"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Gold Loan"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1096"/><column name="duration_to_days" valueNumeric="1460"/><column name="secured_floor_pct" valueNumeric="75"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Gold Loan"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1461"/><column name="secured_floor_pct" valueNumeric="100"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Housing Loan - Individual"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="0"/><column name="duration_to_days" valueNumeric="365"/><column name="secured_floor_pct" valueNumeric="10"/><column name="unsecured_floor_pct" valueNumeric="25"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Housing Loan - Individual"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="366"/><column name="duration_to_days" valueNumeric="730"/><column name="secured_floor_pct" valueNumeric="20"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Housing Loan - Individual"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="731"/><column name="duration_to_days" valueNumeric="1095"/><column name="secured_floor_pct" valueNumeric="30"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Housing Loan - Individual"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1096"/><column name="duration_to_days" valueNumeric="1460"/><column name="secured_floor_pct" valueNumeric="40"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Housing Loan - Individual"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="1461"/><column name="secured_floor_pct" valueNumeric="100"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Unsecured Retail Loans"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="0"/><column name="duration_to_days" valueNumeric="365"/><column name="secured_floor_pct" valueNumeric="25"/><column name="unsecured_floor_pct" valueNumeric="25"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Unsecured Retail Loans"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="366"/><column name="secured_floor_pct" valueNumeric="100"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
<insert tableName="m_prudential_floor"><column name="product_category" value="Loan against Term Deposit / LIC / KVP"/><column name="stage" valueNumeric="3"/><column name="duration_from_days" valueNumeric="0"/><column name="secured_floor_pct" valueNumeric="100"/><column name="unsecured_floor_pct" valueNumeric="100"/><column name="created_by" valueNumeric="1"/><column name="created_on_utc" valueComputed="NOW()"/><column name="version" valueNumeric="1"/><column name="last_modified_by" valueNumeric="1"/><column name="last_modified_on_utc" valueComputed="NOW()"/></insert>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,49 @@
<?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">
<!-- RBI 2026 ECL framework, Penal/SMA/NPA wizard step: SICR trigger configuration and the LGD backstop /
own-model override. The prudential floor % itself is NOT stored here - it is looked up from
m_prudential_floor by product category, so every product stays in sync with a single regulatory table. -->
<changeSet author="cbsnexora" id="0601_add_loan_product_advance_ext_sicr_lgd_fields">
<addColumn tableName="m_product_loan_advance_ext">
<!-- SICR trigger configuration -->
<column name="sicr_days_threshold" type="INT"/>
<column name="sicr_revolving_days_threshold" type="INT"/>
<column name="sicr_rating_downgrade_notches" type="INT"/>
<column name="sicr_pricing_increase_trigger_pct" type="DECIMAL(19,6)"/>
<column name="sicr_assessment_basis" type="VARCHAR(30)"/>
<column name="sicr_rebuttal_allowed" type="BOOLEAN"/>
<!-- NPA trigger extras -->
<column name="npa_out_of_order_basis" type="VARCHAR(60)"/>
<column name="crop_duration" type="VARCHAR(20)"/>
<!-- LGD backstop / override -->
<column name="use_own_lgd_model" type="BOOLEAN"/>
<column name="lgd_secured_pct" type="DECIMAL(19,6)"/>
<column name="lgd_unsecured_pct" type="DECIMAL(19,6)"/>
<column name="eligible_collateral_type" type="VARCHAR(200)"/>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -173,7 +173,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
when(loanAccountData.getId()).thenReturn(1L);
when(loanAccountData.getAccountNo()).thenReturn("0001");
when(loanAccountData.getExternalId()).thenReturn(ExternalIdFactory.produce("externalId"));
when(loanAccountData.getDelinquencyRange()).thenReturn(new DelinquencyRangeData(1L, "classification", 1, 10));
when(loanAccountData.getDelinquencyRange()).thenReturn(new DelinquencyRangeData(1L, "classification", 1, 10, null));
when(loanAccountData.getCurrency()).thenAnswer(
a -> new CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(), loanCurrency.getInMultiplesOf()));
when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
@@ -238,7 +238,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
when(loanAccountData.getId()).thenReturn(1L);
when(loanAccountData.getAccountNo()).thenReturn("0001");
when(loanAccountData.getExternalId()).thenReturn(ExternalIdFactory.produce("externalId"));
when(loanAccountData.getDelinquencyRange()).thenReturn(new DelinquencyRangeData(1L, "classification", 1, 10));
when(loanAccountData.getDelinquencyRange()).thenReturn(new DelinquencyRangeData(1L, "classification", 1, 10, null));
when(loanAccountData.getCurrency()).thenAnswer(
a -> new CurrencyData(loanCurrency.getCode(), loanCurrency.getDigitsAfterDecimal(), loanCurrency.getInMultiplesOf()));
when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
@@ -174,9 +174,9 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
final List<LoanDelinquencyActionData> effectiveDelinquencyList = Collections.emptyList();
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2, null);
range1.setId(1L);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30, null);
range2.setId(2L);
List<DelinquencyRange> listDelinquencyRanges = Arrays.asList(range1, range2);
DelinquencyBucket delinquencyBucket = new DelinquencyBucket("test Bucket");
@@ -217,9 +217,9 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
final List<LoanDelinquencyActionData> effectiveDelinquencyList = Collections.emptyList();
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2, null);
range1.setId(1L);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30, null);
range2.setId(2L);
List<DelinquencyRange> listDelinquencyRanges = Arrays.asList(range1, range2);
DelinquencyBucket delinquencyBucket = new DelinquencyBucket("test Bucket");
@@ -281,9 +281,9 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2, null);
range1.setId(1L);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30, null);
range2.setId(2L);
List<DelinquencyRange> listDelinquencyRanges = Arrays.asList(range1, range2);
@@ -346,9 +346,9 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
final List<LoanDelinquencyActionData> effectiveDelinquencyList = Collections.emptyList();
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2, null);
range1.setId(1L);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30, null);
range2.setId(2L);
List<DelinquencyRange> listDelinquencyRanges = Arrays.asList(range1, range2);
DelinquencyBucket delinquencyBucket = new DelinquencyBucket("test Bucket");
@@ -425,9 +425,9 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
final List<LoanDelinquencyActionData> effectiveDelinquencyList = Collections.emptyList();
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2, null);
range1.setId(1L);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30, null);
range2.setId(2L);
List<DelinquencyRange> listDelinquencyRanges = Arrays.asList(range1, range2);
DelinquencyBucket delinquencyBucket = new DelinquencyBucket("test Bucket");
@@ -510,9 +510,9 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
// given
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2, null);
range1.setId(1L);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30, null);
range2.setId(2L);
List<DelinquencyRange> listDelinquencyRanges = Arrays.asList(range1, range2);
DelinquencyBucket delinquencyBucket = new DelinquencyBucket("test Bucket");
@@ -597,9 +597,9 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
// given
Loan loanForProcessing = Mockito.mock(Loan.class);
when(loanForProcessing.getId()).thenReturn(1L);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2);
DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2, null);
range1.setId(1L);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30);
DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30, null);
range2.setId(2L);
List<DelinquencyRange> listDelinquencyRanges = Arrays.asList(range1, range2);
DelinquencyBucket delinquencyBucket = new DelinquencyBucket("test Bucket");