form 91/98 report, min max value update in AML rule and whihc rule apply day, week, month and some more updates
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s

This commit is contained in:
2026-08-02 11:45:54 +05:30
parent abc7eb22a2
commit c890faee78
6 changed files with 207 additions and 2 deletions
@@ -60,6 +60,18 @@ public class AmlTransactionRule {
@Column(name = "threshold_amount_high_risk", precision = 19, scale = 6)
private BigDecimal thresholdAmountHighRisk;
/**
* Declarative band the rule covers, shown on the AML Rules Master screen. Purely
* descriptive: unlike the tiered columns above these never take part in matching, so
* changing them cannot alter which transactions raise an alert. A null maximum means
* the rule is genuinely open-ended.
*/
@Column(name = "min_limit_amount", precision = 19, scale = 6)
private BigDecimal minLimitAmount;
@Column(name = "max_limit_amount", precision = 19, scale = 6)
private BigDecimal maxLimitAmount;
@Column(name = "rule_type", nullable = false, length = 20)
private String ruleType;
@@ -102,6 +114,14 @@ public class AmlTransactionRule {
return thresholdAmountHighRisk;
}
public BigDecimal getMinLimitAmount() {
return minLimitAmount;
}
public BigDecimal getMaxLimitAmount() {
return maxLimitAmount;
}
public AmlRuleType getRuleType() {
return AmlRuleType.valueOf(ruleType);
}
@@ -56,6 +56,10 @@ public final class KycRiskRuleData implements Serializable {
* linked {@code m_aml_transaction_rule} row has tiering configured (e.g. RTGS monitoring). */
private final BigDecimal txnThresholdAmountMediumRisk;
private final BigDecimal txnThresholdAmountHighRisk;
/** Declarative band the rule covers, for display on the AML Rules Master screen. Never
* used in matching; a null maximum means the rule is genuinely open-ended. */
private final BigDecimal txnMinLimitAmount;
private final BigDecimal txnMaxLimitAmount;
public KycRiskRuleData(final Long id, final String ruleName, final Integer legalFormId,
final Long occupationCategoryCvId, final String occupationCategoryName,
@@ -67,7 +71,8 @@ public final class KycRiskRuleData implements Serializable {
final String amlCheckStatus, final String bannedEntityMatch,
final String willfulDefaulter, final String ecgcSalFlag, final String dormantFlag,
final BigDecimal txnThresholdAmount, final String txnRuleType, final Integer txnWindowDays,
final BigDecimal txnThresholdAmountMediumRisk, final BigDecimal txnThresholdAmountHighRisk) {
final BigDecimal txnThresholdAmountMediumRisk, final BigDecimal txnThresholdAmountHighRisk,
final BigDecimal txnMinLimitAmount, final BigDecimal txnMaxLimitAmount) {
this.id = id;
this.ruleName = ruleName;
this.legalFormId = legalFormId;
@@ -94,6 +99,8 @@ public final class KycRiskRuleData implements Serializable {
this.txnWindowDays = txnWindowDays;
this.txnThresholdAmountMediumRisk = txnThresholdAmountMediumRisk;
this.txnThresholdAmountHighRisk = txnThresholdAmountHighRisk;
this.txnMinLimitAmount = txnMinLimitAmount;
this.txnMaxLimitAmount = txnMaxLimitAmount;
}
public Long getId() {
@@ -199,4 +206,12 @@ public final class KycRiskRuleData implements Serializable {
public BigDecimal getTxnThresholdAmountHighRisk() {
return txnThresholdAmountHighRisk;
}
public BigDecimal getTxnMinLimitAmount() {
return txnMinLimitAmount;
}
public BigDecimal getTxnMaxLimitAmount() {
return txnMaxLimitAmount;
}
}
@@ -80,7 +80,9 @@ public class KycRiskRulesReadPlatformServiceImpl implements KycRiskRulesReadPlat
txnRule != null ? txnRule.getRuleType().name() : null,
txnRule != null ? txnRule.getWindowDays() : null,
txnRule != null ? txnRule.getThresholdAmountMediumRisk() : null,
txnRule != null ? txnRule.getThresholdAmountHighRisk() : null
txnRule != null ? txnRule.getThresholdAmountHighRisk() : null,
txnRule != null ? txnRule.getMinLimitAmount() : null,
txnRule != null ? txnRule.getMaxLimitAmount() : null
);
}
}
@@ -493,4 +493,6 @@
<include file="parts/0438_add_tiered_thresholds_and_low_balance_rule.xml" relativeToChangelogFile="true"/>
<include file="parts/0439_fill_client_risk_rule_income_turnover_nulls.xml" relativeToChangelogFile="true"/>
<include file="parts/0440_seed_aml_test_alerts.xml" relativeToChangelogFile="true"/>
<include file="parts/0441_add_aml_rule_limit_bands.xml" relativeToChangelogFile="true"/>
<include file="parts/0442_seed_branch_offices.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,107 @@
<?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.13.xsd">
<!--
Declarative min/max band for each AML transaction rule, used by the AML Rules
Master screen.
These are deliberately NOT the existing threshold_amount_medium_risk /
threshold_amount_high_risk columns: those are live alerting overrides consumed by
AmlRuleEvaluationServiceImpl.resolveThreshold(), and populating them purely to fill
a display column would change which transactions raise alerts. These new columns
are read-only documentation of the band a rule covers and never affect matching.
-->
<changeSet author="fineract" id="0441-1">
<preConditions onFail="MARK_RAN">
<not>
<columnExists tableName="m_aml_transaction_rule" columnName="min_limit_amount"/>
</not>
</preConditions>
<addColumn tableName="m_aml_transaction_rule">
<column name="min_limit_amount" type="DECIMAL(19,6)"/>
<column name="max_limit_amount" type="DECIMAL(19,6)"/>
</addColumn>
</changeSet>
<!--
Band values. The minimum is the amount at which the rule starts to bite; the maximum
is the amount beyond which the exposure is treated as materially more serious and
escalated. A NULL maximum means the rule is genuinely open-ended.
-->
<changeSet author="fineract" id="0441-2">
<sql>
UPDATE m_aml_transaction_rule SET min_limit_amount = 1000000, max_limit_amount = 2500000
WHERE rule_code = 'CTR_THRESHOLD_10L';
UPDATE m_aml_transaction_rule SET min_limit_amount = 1000000, max_limit_amount = 2500000
WHERE rule_code = 'STRUCTURED_CTR_10L_MONTHLY';
UPDATE m_aml_transaction_rule SET min_limit_amount = 2500000, max_limit_amount = 5000000
WHERE rule_code = 'LARGE_CASH_TXN_25L';
-- Structuring rules watch the band immediately BELOW the reporting threshold.
UPDATE m_aml_transaction_rule SET min_limit_amount = 700000, max_limit_amount = 1000000
WHERE rule_code = 'STRUCTURING_BELOW_10L';
UPDATE m_aml_transaction_rule SET min_limit_amount = 35000, max_limit_amount = 50000
WHERE rule_code = 'STRUCTURING_BELOW_50K';
UPDATE m_aml_transaction_rule SET min_limit_amount = 900000, max_limit_amount = 1000000
WHERE rule_code = 'FREQUENT_CASH_NEAR_10L';
UPDATE m_aml_transaction_rule SET min_limit_amount = 50000, max_limit_amount = 200000
WHERE rule_code = 'FREQUENT_LOW_CASH_DEPOSIT';
UPDATE m_aml_transaction_rule SET min_limit_amount = 50000, max_limit_amount = 200000
WHERE rule_code = 'FREQUENT_LOW_CASH_WITHDRAWAL';
UPDATE m_aml_transaction_rule SET min_limit_amount = 500000, max_limit_amount = 2000000
WHERE rule_code = 'RTGS_HIGH_VALUE';
UPDATE m_aml_transaction_rule SET min_limit_amount = 2000000, max_limit_amount = 5000000
WHERE rule_code = 'JEWELLER_ACCOUNT_LARGE_TXN';
UPDATE m_aml_transaction_rule SET min_limit_amount = 50000, max_limit_amount = 200000
WHERE rule_code = 'CHARITABLE_ORG_CASH_WITHDRAWAL';
UPDATE m_aml_transaction_rule SET min_limit_amount = 50000, max_limit_amount = 500000
WHERE rule_code = 'PEP_ACCOUNT_MONITORING';
UPDATE m_aml_transaction_rule SET min_limit_amount = 500000, max_limit_amount = 1000000
WHERE rule_code = 'NEW_ACCOUNT_LARGE_TXN';
UPDATE m_aml_transaction_rule SET min_limit_amount = 200000, max_limit_amount = 500000
WHERE rule_code = 'LOAN_REPAYMENT_IN_CASH';
UPDATE m_aml_transaction_rule SET min_limit_amount = 1000000, max_limit_amount = 2500000
WHERE rule_code = 'LOW_BALANCE_HIGH_ACTIVITY';
-- RAPID_MOVEMENT_IN_ACCOUNTS is a pure velocity rule with no monetary threshold,
-- so it is intentionally left without a band.
</sql>
</changeSet>
<!--
The three High-risk income rules were seeded with a floor but a zero ceiling, which the
UI cannot distinguish from "not captured". Normalise the zeros to NULL so an absent
ceiling reads as genuinely open-ended, and give the medium bands their missing floors.
-->
<changeSet author="fineract" id="0441-3">
<sql>
UPDATE m_client_risk_rule SET max_monthly_income = NULL WHERE max_monthly_income = 0;
UPDATE m_client_risk_rule SET max_annual_income = NULL WHERE max_annual_income = 0;
UPDATE m_client_risk_rule SET max_annual_turnover = NULL WHERE max_annual_turnover = 0;
UPDATE m_client_risk_rule SET min_monthly_income = NULL WHERE min_monthly_income = 0;
UPDATE m_client_risk_rule SET min_annual_income = NULL WHERE min_annual_income = 0;
UPDATE m_client_risk_rule SET min_annual_turnover = NULL WHERE min_annual_turnover = 0;
</sql>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,59 @@
<?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.13.xsd">
<!--
Branch offices under Head Office.
Until now the tenant had a single office, so "see only my branch" had nothing to
scope to. external_id carries the branch code used by the General Ledger and the
RBI return screens, which is what lets a logged-in user's office be resolved to a
branch without matching on display names.
-->
<changeSet author="fineract" id="0442-1">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_office WHERE external_id IN ('0002','0003','0004','0005','0006')</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_office (parent_id, hierarchy, external_id, name, opening_date)
VALUES
((SELECT id FROM m_office WHERE hierarchy = '.'), '.2.', '0002', 'Andheri (E)', DATE '2018-04-01'),
((SELECT id FROM m_office WHERE hierarchy = '.'), '.3.', '0003', 'Bandra (W)', DATE '2018-04-01'),
((SELECT id FROM m_office WHERE hierarchy = '.'), '.4.', '0004', 'Borivali', DATE '2019-07-01'),
((SELECT id FROM m_office WHERE hierarchy = '.'), '.5.', '0005', 'Dadar', DATE '2020-01-02'),
((SELECT id FROM m_office WHERE hierarchy = '.'), '.6.', '0006', 'Thane', DATE '2021-06-01');
</sql>
</changeSet>
<!-- Head Office carries the branch code the GL ledger uses for it. -->
<changeSet author="fineract" id="0442-2">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_office WHERE external_id = 'HO'</sqlCheck>
</preConditions>
<sql>
UPDATE m_office SET external_id = 'HO' WHERE hierarchy = '.';
</sql>
</changeSet>
</databaseChangeLog>