kyc risk and AML & Compliance Checks automate on client creation
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-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (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 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 - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract 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 Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (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-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (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 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 - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract 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 Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
This commit is contained in:
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.aml.api;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/** Request body for {@code POST /v1/aml/alerts/{id}/review}. {@code disposition}: WHITELIST | ESCALATE. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class AlertReviewRequest {
|
||||
|
||||
private String disposition;
|
||||
private String notes;
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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.aml.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
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.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlAlertData;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlAlertReadPlatformService;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlAlertWritePlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/aml/alerts")
|
||||
@Component
|
||||
@Tag(name = "AML Alerts", description = "Live transaction-monitoring alerts raised by the AML rule engine against savings, loan and fund-transfer activity.")
|
||||
@RequiredArgsConstructor
|
||||
public class AmlAlertApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final AmlAlertReadPlatformService readPlatformService;
|
||||
private final AmlAlertWritePlatformService writePlatformService;
|
||||
private final DefaultToApiJsonSerializer<AmlAlertData> toApiJsonSerializer;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "List AML alerts",
|
||||
description = "Optionally filter by status via ?status= : RAISED, UNDER_REVIEW, WHITELISTED, ESCALATED.")
|
||||
public String retrieveAll(@QueryParam("status") final String status) {
|
||||
this.context.authenticatedUser();
|
||||
final List<AmlAlertData> alerts = this.readPlatformService.retrieve(status);
|
||||
return this.toApiJsonSerializer.serialize(alerts);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{alertId}/review")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "AML Officer first-level review of an alert",
|
||||
description = "disposition WHITELIST closes the alert as a false positive; ESCALATE creates an STR case for Principal Officer review.")
|
||||
public String review(@PathParam("alertId") final Long alertId, final AlertReviewRequest request) {
|
||||
this.context.authenticatedUser().validateHasPermissionTo("REVIEW_AML_ALERT");
|
||||
if ("ESCALATE".equalsIgnoreCase(request.getDisposition())) {
|
||||
this.writePlatformService.escalate(alertId, request.getNotes());
|
||||
} else {
|
||||
this.writePlatformService.whitelist(alertId, request.getNotes());
|
||||
}
|
||||
final AmlAlertData updated = this.readPlatformService.retrieve(null).stream().filter(a -> a.id().equals(alertId)).findFirst()
|
||||
.orElse(null);
|
||||
return this.toApiJsonSerializer.serialize(updated);
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.aml.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.time.YearMonth;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlCtrReportData;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlCtrReportGenerationService;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlCtrReportReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/aml/ctr-reports")
|
||||
@Component
|
||||
@Tag(name = "AML CTR Reports",
|
||||
description = "Monthly, mechanical Cash Transaction Reports - threshold-based, no Principal Officer judgment involved.")
|
||||
@RequiredArgsConstructor
|
||||
public class AmlCtrReportApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final AmlCtrReportReadPlatformService readPlatformService;
|
||||
private final AmlCtrReportGenerationService generationService;
|
||||
private final DefaultToApiJsonSerializer<AmlCtrReportData> toApiJsonSerializer;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "List generated CTR reports")
|
||||
public String retrieveAll() {
|
||||
this.context.authenticatedUser();
|
||||
final List<AmlCtrReportData> reports = this.readPlatformService.retrieveAll();
|
||||
return this.toApiJsonSerializer.serialize(reports);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("generate")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Generate the CTR report for a month", description = "month format: yyyy-MM. Idempotent - fails if already generated.")
|
||||
public String generate(final CtrGenerateRequest request) {
|
||||
this.context.authenticatedUser().validateHasPermissionTo("REVIEW_AML_ALERT");
|
||||
final YearMonth month = YearMonth.parse(request.getMonth());
|
||||
this.generationService.generateForMonth(month);
|
||||
return this.toApiJsonSerializer.serialize(this.readPlatformService.retrieveAll());
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 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.aml.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlStrCaseData;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrConclusion;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlStrCaseReadPlatformService;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlStrCaseWritePlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/aml/str-cases")
|
||||
@Component
|
||||
@Tag(name = "AML STR Cases",
|
||||
description = "Principal-Officer-only investigation and conclusion of escalated AML alerts, and STR filing to FIU-IND.")
|
||||
@RequiredArgsConstructor
|
||||
public class AmlStrCaseApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final AmlStrCaseReadPlatformService readPlatformService;
|
||||
private final AmlStrCaseWritePlatformService writePlatformService;
|
||||
private final DefaultToApiJsonSerializer<AmlStrCaseData> toApiJsonSerializer;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "List AML STR cases")
|
||||
public String retrieveAll() {
|
||||
this.context.authenticatedUser();
|
||||
final List<AmlStrCaseData> cases = this.readPlatformService.retrieveAll();
|
||||
return this.toApiJsonSerializer.serialize(cases);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{strCaseId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve one AML STR case")
|
||||
public String retrieveOne(@PathParam("strCaseId") final Long strCaseId) {
|
||||
this.context.authenticatedUser();
|
||||
return this.toApiJsonSerializer.serialize(this.readPlatformService.retrieveOne(strCaseId));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("weekly-digest")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Internal MIS digest of STR cases concluded suspicious in the last 7 days",
|
||||
description = "For compliance tracking only - does not replace each case's own 7-working-day FIU filing deadline.")
|
||||
public String weeklyDigest() {
|
||||
this.context.authenticatedUser();
|
||||
final List<AmlStrCaseData> digest = this.readPlatformService.weeklyDigest();
|
||||
return this.toApiJsonSerializer.serialize(digest);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{strCaseId}/assign")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Assign a Principal Officer to an STR case")
|
||||
public String assign(@PathParam("strCaseId") final Long strCaseId, final StrCaseAssignRequest request) {
|
||||
this.context.authenticatedUser().validateHasPermissionTo("CONCLUDE_AML_STR");
|
||||
this.writePlatformService.assignPrincipalOfficer(strCaseId, request.getPrincipalOfficerUserId());
|
||||
return this.toApiJsonSerializer.serialize(this.readPlatformService.retrieveOne(strCaseId));
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{strCaseId}/conclude")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Principal Officer conclusion",
|
||||
description = "conclusion NOT_SUSPICIOUS closes the case; SUSPICIOUS sets the 7-working-day FIU filing due date starting today.")
|
||||
public String conclude(@PathParam("strCaseId") final Long strCaseId, final StrCaseConcludeRequest request) {
|
||||
this.context.authenticatedUser().validateHasPermissionTo("CONCLUDE_AML_STR");
|
||||
final AmlStrConclusion conclusion = AmlStrConclusion.valueOf(request.getConclusion());
|
||||
this.writePlatformService.conclude(strCaseId, conclusion, request.getInvestigationNotes(), request.getSourceOfFundsNotes());
|
||||
return this.toApiJsonSerializer.serialize(this.readPlatformService.retrieveOne(strCaseId));
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{strCaseId}/file")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Record that the STR has been filed with FIU-IND")
|
||||
public String fileStr(@PathParam("strCaseId") final Long strCaseId, final StrCaseFileRequest request) {
|
||||
this.context.authenticatedUser().validateHasPermissionTo("CONCLUDE_AML_STR");
|
||||
this.writePlatformService.fileStr(strCaseId, request.getReferenceNo());
|
||||
return this.toApiJsonSerializer.serialize(this.readPlatformService.retrieveOne(strCaseId));
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.aml.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
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.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.aml.data.ClientRiskCategoryHistoryData;
|
||||
import org.apache.fineract.portfolio.aml.service.ClientRiskCategoryHistoryReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/clients/{clientId}/risk-category-history")
|
||||
@Component
|
||||
@Tag(name = "Client Risk Category History",
|
||||
description = "Audit trail of a client's KYC risk-category changes, including automatic escalation after repeated AML STR cases.")
|
||||
@RequiredArgsConstructor
|
||||
public class ClientRiskCategoryHistoryApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final ClientRiskCategoryHistoryReadPlatformService readPlatformService;
|
||||
private final DefaultToApiJsonSerializer<ClientRiskCategoryHistoryData> toApiJsonSerializer;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve a client's risk-category change history")
|
||||
public String retrieveForClient(@PathParam("clientId") final Long clientId) {
|
||||
this.context.authenticatedUser();
|
||||
final List<ClientRiskCategoryHistoryData> history = this.readPlatformService.retrieveForClient(clientId);
|
||||
return this.toApiJsonSerializer.serialize(history);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.aml.api;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/** Request body for {@code POST /v1/aml/ctr-reports/generate}. {@code month} format: yyyy-MM. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CtrGenerateRequest {
|
||||
|
||||
private String month;
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.aml.api;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/** Request body for {@code POST /v1/aml/str-cases/{id}/assign}. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class StrCaseAssignRequest {
|
||||
|
||||
private Long principalOfficerUserId;
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.aml.api;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/** Request body for {@code POST /v1/aml/str-cases/{id}/conclude}. {@code conclusion}: NOT_SUSPICIOUS | SUSPICIOUS. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class StrCaseConcludeRequest {
|
||||
|
||||
private String conclusion;
|
||||
private String investigationNotes;
|
||||
private String sourceOfFundsNotes;
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.aml.api;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/** Request body for {@code POST /v1/aml/str-cases/{id}/file}. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class StrCaseFileRequest {
|
||||
|
||||
private String referenceNo;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.aml.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record AmlAlertData(Long id, String transactionType, Long transactionId, Long clientId, String clientDisplayName,
|
||||
String ruleCode, String ruleDescription, BigDecimal amount, String currencyCode, LocalDateTime raisedOn, String status,
|
||||
String reviewedByUsername, LocalDateTime reviewedOn, String reviewNotes, String disposition) implements Serializable {
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.aml.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record AmlCtrReportData(Long id, String reportMonth, LocalDateTime generatedOn, int recordCount, String fileReference,
|
||||
String status, LocalDateTime submittedOn) implements Serializable {
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.aml.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record AmlStrCaseData(Long id, Long alertId, Long clientId, String clientDisplayName, String assignedPoUsername,
|
||||
String investigationNotes, String sourceOfFundsNotes, String conclusion, String concludedByUsername, LocalDate concludedOn,
|
||||
LocalDate fiuDueDate, LocalDate strFiledOn, String strReferenceNo, String status, long cumulativeStrCaseCountForClient)
|
||||
implements Serializable {
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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.aml.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlTransactionType;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
|
||||
/**
|
||||
* Normalized view of a savings/loan/transfer transaction handed to {@code AmlRuleEvaluationService}
|
||||
* by the business-event listener that observed it. Keeps the evaluation service itself decoupled
|
||||
* from the savings/loan/fund-transfer domain models - each listener is responsible for building
|
||||
* this context (including, where relevant, {@link #windowAggregateAmount}) from the repositories
|
||||
* it already has access to.
|
||||
*/
|
||||
public final class AmlTransactionContext {
|
||||
|
||||
private final AmlTransactionType transactionType;
|
||||
private final Long transactionId;
|
||||
private final Client client;
|
||||
private final BigDecimal amount;
|
||||
private final String currencyCode;
|
||||
private final LocalDateTime occurredOn;
|
||||
private final boolean cash;
|
||||
/**
|
||||
* For STRUCTURING/VELOCITY rules: the sum of this client's qualifying transactions of this
|
||||
* type within the rule's window, INCLUSIVE of this transaction. Null if not computed - such
|
||||
* rules are simply skipped for this context rather than guessed at.
|
||||
*/
|
||||
private final BigDecimal windowAggregateAmount;
|
||||
|
||||
public AmlTransactionContext(AmlTransactionType transactionType, Long transactionId, Client client, BigDecimal amount,
|
||||
String currencyCode, LocalDateTime occurredOn, boolean cash, BigDecimal windowAggregateAmount) {
|
||||
this.transactionType = transactionType;
|
||||
this.transactionId = transactionId;
|
||||
this.client = client;
|
||||
this.amount = amount;
|
||||
this.currencyCode = currencyCode;
|
||||
this.occurredOn = occurredOn;
|
||||
this.cash = cash;
|
||||
this.windowAggregateAmount = windowAggregateAmount;
|
||||
}
|
||||
|
||||
public AmlTransactionType getTransactionType() {
|
||||
return transactionType;
|
||||
}
|
||||
|
||||
public Long getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public String getCurrencyCode() {
|
||||
return currencyCode;
|
||||
}
|
||||
|
||||
public LocalDateTime getOccurredOn() {
|
||||
return occurredOn;
|
||||
}
|
||||
|
||||
public boolean isCash() {
|
||||
return cash;
|
||||
}
|
||||
|
||||
public BigDecimal getWindowAggregateAmount() {
|
||||
return windowAggregateAmount;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.aml.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record ClientRiskCategoryHistoryData(Long id, Long clientId, Integer previousCategory, Integer newCategory, String reason,
|
||||
String triggeredBy, Long strCaseId, LocalDateTime changedOn, String changedByUsername) implements Serializable {
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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.aml.event;
|
||||
|
||||
/** Marker interface whose only implementation registers AML business-event listeners on startup. */
|
||||
public interface AmlDomainService {
|
||||
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 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.aml.event;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanDisbursalTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionMakeRepaymentPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsDepositBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsWithdrawalBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlTransactionContext;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlTransactionType;
|
||||
import org.apache.fineract.portfolio.aml.repository.AmlLoanTransactionQueryRepository;
|
||||
import org.apache.fineract.portfolio.aml.repository.AmlSavingsTransactionQueryRepository;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlRuleEvaluationService;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
|
||||
|
||||
/**
|
||||
* Registers post-business-event listeners that evaluate every live savings and loan transaction
|
||||
* against the AML rule catalog, purely additively - listeners observe transactions that already
|
||||
* completed successfully and never alter their outcome. Runs synchronously in the same request,
|
||||
* mirroring {@code org.apache.fineract.notification.service.NotificationDomainServiceImpl}, so an
|
||||
* alert is atomic with the transaction that raised it.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class AmlDomainServiceImpl implements AmlDomainService {
|
||||
|
||||
/**
|
||||
* Fixed lookback used for every STRUCTURING/VELOCITY rule regardless of its own configured
|
||||
* window_days - a v1 approximation (errs toward over-inclusion, the safer failure mode for AML
|
||||
* monitoring) until per-rule windows are wired through.
|
||||
*/
|
||||
private static final int STRUCTURING_WINDOW_DAYS = 30;
|
||||
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final AmlRuleEvaluationService ruleEvaluationService;
|
||||
private final AmlSavingsTransactionQueryRepository savingsQueryRepository;
|
||||
private final AmlLoanTransactionQueryRepository loanQueryRepository;
|
||||
|
||||
@PostConstruct
|
||||
public void addListeners() {
|
||||
businessEventNotifierService.addPostBusinessEventListener(SavingsDepositBusinessEvent.class, new SavingsDepositListener());
|
||||
businessEventNotifierService.addPostBusinessEventListener(SavingsWithdrawalBusinessEvent.class, new SavingsWithdrawalListener());
|
||||
businessEventNotifierService.addPostBusinessEventListener(LoanDisbursalTransactionBusinessEvent.class,
|
||||
new LoanDisbursalListener());
|
||||
businessEventNotifierService.addPostBusinessEventListener(LoanTransactionMakeRepaymentPostBusinessEvent.class,
|
||||
new LoanRepaymentListener());
|
||||
}
|
||||
|
||||
private final class SavingsDepositListener implements BusinessEventListener<SavingsDepositBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(SavingsDepositBusinessEvent event) {
|
||||
evaluateSavingsTransaction(event.get(), true);
|
||||
}
|
||||
}
|
||||
|
||||
private final class SavingsWithdrawalListener implements BusinessEventListener<SavingsWithdrawalBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(SavingsWithdrawalBusinessEvent event) {
|
||||
evaluateSavingsTransaction(event.get(), false);
|
||||
}
|
||||
}
|
||||
|
||||
private final class LoanDisbursalListener implements BusinessEventListener<LoanDisbursalTransactionBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(LoanDisbursalTransactionBusinessEvent event) {
|
||||
evaluateLoanTransaction(event.get());
|
||||
}
|
||||
}
|
||||
|
||||
private final class LoanRepaymentListener implements BusinessEventListener<LoanTransactionMakeRepaymentPostBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(LoanTransactionMakeRepaymentPostBusinessEvent event) {
|
||||
evaluateLoanTransaction(event.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void evaluateSavingsTransaction(SavingsAccountTransaction transaction, boolean depositDirection) {
|
||||
final Client client = transaction.getSavingsAccount().getClient();
|
||||
if (client == null) {
|
||||
// group/JLG savings with no individual client behind it are out of scope for this rule set
|
||||
return;
|
||||
}
|
||||
final LocalDate since = LocalDate.now().minusDays(STRUCTURING_WINDOW_DAYS);
|
||||
final List<SavingsAccountTransaction> recent = savingsQueryRepository.findForClientSince(client.getId(), since);
|
||||
final BigDecimal windowAggregate = recent.stream().filter(t -> depositDirection ? t.isDeposit() : t.isWithdrawal())
|
||||
.map(SavingsAccountTransaction::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
final AmlTransactionContext context = new AmlTransactionContext(AmlTransactionType.SAVINGS, transaction.getId(), client,
|
||||
transaction.getAmount(), transaction.getSavingsAccount().getCurrency().getCode(),
|
||||
transaction.getDateOf().atStartOfDay(), isCash(transaction.getPaymentDetail()), windowAggregate);
|
||||
try {
|
||||
ruleEvaluationService.evaluate(context);
|
||||
} catch (final RuntimeException e) {
|
||||
// AML alerting must never block the underlying savings transaction it is observing
|
||||
log.error("AML rule evaluation failed for savings transaction {}", transaction.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void evaluateLoanTransaction(LoanTransaction transaction) {
|
||||
final Client client = transaction.getLoan().getClient();
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
final LocalDate since = LocalDate.now().minusDays(STRUCTURING_WINDOW_DAYS);
|
||||
final List<LoanTransaction> recent = loanQueryRepository.findForClientSince(client.getId(), since);
|
||||
final BigDecimal windowAggregate = recent.stream().filter(LoanTransaction::isRepaymentLikeType)
|
||||
.map(t -> t.getAmount(transaction.getLoan().getCurrency()).getAmount()).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
final AmlTransactionContext context = new AmlTransactionContext(AmlTransactionType.LOAN, transaction.getId(), client,
|
||||
transaction.getAmount(transaction.getLoan().getCurrency()).getAmount(), transaction.getLoan().getCurrency().getCode(),
|
||||
transaction.getTransactionDate().atStartOfDay(), isCash(transaction.getPaymentDetail()), windowAggregate);
|
||||
try {
|
||||
ruleEvaluationService.evaluate(context);
|
||||
} catch (final RuntimeException e) {
|
||||
log.error("AML rule evaluation failed for loan transaction {}", transaction.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCash(PaymentDetail paymentDetail) {
|
||||
return paymentDetail == null || Boolean.TRUE.equals(paymentDetail.getPaymentType().getIsCashPayment());
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.aml.job;
|
||||
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlCtrReportGenerationService;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
@Configuration
|
||||
public class AmlCtrReportBatchJobConfig {
|
||||
|
||||
@Autowired
|
||||
private JobRepository jobRepository;
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
@Autowired
|
||||
private AmlCtrReportGenerationService generationService;
|
||||
|
||||
@Bean
|
||||
public AmlCtrReportBatchTasklet amlCtrReportBatchTasklet() {
|
||||
return new AmlCtrReportBatchTasklet(generationService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected Step amlCtrReportBatchStep() {
|
||||
return new StepBuilder(JobName.CTR_REPORT_GENERATION.name(), jobRepository)
|
||||
.tasklet(amlCtrReportBatchTasklet(), transactionManager).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Job amlCtrReportBatchJob() {
|
||||
return new JobBuilder(JobName.CTR_REPORT_GENERATION.name(), jobRepository).start(amlCtrReportBatchStep())
|
||||
.incrementer(new RunIdIncrementer()).build();
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.aml.job;
|
||||
|
||||
import java.time.YearMonth;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlCtrReport;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlCtrReportGenerationService;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||
import org.springframework.batch.repeat.RepeatStatus;
|
||||
|
||||
/**
|
||||
* Generates the CTR report for the previous calendar month as a {@code DRAFT} - mechanical
|
||||
* threshold aggregation only. Admin-triggered today (see {@code AmlCtrReportApiResource}); wiring
|
||||
* to a real monthly cron is a follow-up, matching the same "driven by an admin endpoint today"
|
||||
* stance already used for {@code NeftBatchProcessorService}.
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class AmlCtrReportBatchTasklet implements Tasklet {
|
||||
|
||||
private final AmlCtrReportGenerationService generationService;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
|
||||
final YearMonth previousMonth = YearMonth.now().minusMonths(1);
|
||||
try {
|
||||
final AmlCtrReport report = generationService.generateForMonth(previousMonth);
|
||||
log.info("[Job:CTR_REPORT_GENERATION] generated CTR report for {} with {} line(s)", previousMonth,
|
||||
report.getRecordCount());
|
||||
} catch (final IllegalStateException e) {
|
||||
log.info("[Job:CTR_REPORT_GENERATION] {} — skipping", e.getMessage());
|
||||
}
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.aml.job;
|
||||
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlStrWeeklyDigestService;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
@Configuration
|
||||
public class AmlStrWeeklyDigestJobConfig {
|
||||
|
||||
@Autowired
|
||||
private JobRepository jobRepository;
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
@Autowired
|
||||
private AmlStrWeeklyDigestService digestService;
|
||||
|
||||
@Bean
|
||||
public AmlStrWeeklyDigestTasklet amlStrWeeklyDigestTasklet() {
|
||||
return new AmlStrWeeklyDigestTasklet(digestService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected Step amlStrWeeklyDigestStep() {
|
||||
return new StepBuilder(JobName.AML_STR_WEEKLY_DIGEST.name(), jobRepository)
|
||||
.tasklet(amlStrWeeklyDigestTasklet(), transactionManager).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Job amlStrWeeklyDigestJob() {
|
||||
return new JobBuilder(JobName.AML_STR_WEEKLY_DIGEST.name(), jobRepository).start(amlStrWeeklyDigestStep())
|
||||
.incrementer(new RunIdIncrementer()).build();
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.aml.job;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCase;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlStrWeeklyDigestService;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||
import org.springframework.batch.repeat.RepeatStatus;
|
||||
|
||||
/**
|
||||
* Periodic heartbeat that surfaces the current week's suspicious-STR count in the application log
|
||||
* for ops visibility. The actual digest content (for compliance MIS use) is pulled on demand via
|
||||
* {@code AmlStrCaseApiResource#weeklyDigest}, mirroring how the CTR job only drafts a report that a
|
||||
* human then reviews/exports.
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class AmlStrWeeklyDigestTasklet implements Tasklet {
|
||||
|
||||
private final AmlStrWeeklyDigestService digestService;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
|
||||
final List<AmlStrCase> digest = digestService.currentWeekDigest();
|
||||
log.info("[Job:AML_STR_WEEKLY_DIGEST] {} STR case(s) concluded suspicious in the last 7 days", digest.size());
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.aml.repository;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import org.apache.fineract.portfolio.fundtransfer.domain.FundTransfer;
|
||||
import org.apache.fineract.portfolio.fundtransfer.domain.FundTransferStatus;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* Read-only aggregation over {@code FundTransfer} for AML window-based rules (structuring /
|
||||
* velocity), across all channels (INTERNAL/IMPS/NEFT/RTGS).
|
||||
*
|
||||
* <p>
|
||||
* Lives under {@code .repository} (not {@code .service}) because {@code JPAConfig} only scans
|
||||
* {@code org.apache.fineract.**.domain} and {@code org.apache.fineract.**.repository} for
|
||||
* {@code @EnableJpaRepositories}.
|
||||
*/
|
||||
public interface AmlFundTransferQueryRepository extends JpaRepository<FundTransfer, Long> {
|
||||
|
||||
@Query("SELECT COALESCE(SUM(t.amount), 0) FROM FundTransfer t WHERE t.clientId = :clientId "
|
||||
+ "AND t.status = :status AND t.initiatedAt >= :since")
|
||||
BigDecimal sumForClientSince(@Param("clientId") Long clientId, @Param("status") FundTransferStatus status,
|
||||
@Param("since") LocalDateTime since);
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.aml.repository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* Read-only aggregation queries over {@code LoanTransaction} for AML window-based rules
|
||||
* (structuring / velocity). Direction/kind (repayment-like) is filtered in Java via
|
||||
* {@link LoanTransaction#isRepaymentLikeType()} rather than in JPQL.
|
||||
*
|
||||
* <p>
|
||||
* Lives under {@code .repository} (not {@code .service}) because {@code JPAConfig} only scans
|
||||
* {@code org.apache.fineract.**.domain} and {@code org.apache.fineract.**.repository} for
|
||||
* {@code @EnableJpaRepositories}.
|
||||
*/
|
||||
public interface AmlLoanTransactionQueryRepository extends JpaRepository<LoanTransaction, Long> {
|
||||
|
||||
@Query("SELECT t FROM LoanTransaction t WHERE t.loan.client.id = :clientId AND t.dateOf >= :since AND t.reversed = false")
|
||||
List<LoanTransaction> findForClientSince(@Param("clientId") Long clientId, @Param("since") LocalDate since);
|
||||
|
||||
@Query("SELECT t FROM LoanTransaction t WHERE t.dateOf BETWEEN :start AND :end AND t.reversed = false")
|
||||
List<LoanTransaction> findInDateRange(@Param("start") LocalDate start, @Param("end") LocalDate end);
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.aml.repository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* Read-only aggregation queries over {@code SavingsAccountTransaction} for AML window-based rules
|
||||
* (structuring / velocity). Kept separate from {@code SavingsAccountTransactionRepository} so the
|
||||
* AML module doesn't modify savings module code - direction (deposit/withdrawal) is filtered in
|
||||
* Java via {@link SavingsAccountTransaction#isDeposit()}/{@code isWithdrawal()} rather than in
|
||||
* JPQL, since {@code typeOf} there is a raw internal code.
|
||||
*
|
||||
* <p>
|
||||
* Lives under {@code .repository} (not {@code .service}) because {@code JPAConfig} only scans
|
||||
* {@code org.apache.fineract.**.domain} and {@code org.apache.fineract.**.repository} for
|
||||
* {@code @EnableJpaRepositories}.
|
||||
*/
|
||||
public interface AmlSavingsTransactionQueryRepository extends JpaRepository<SavingsAccountTransaction, Long> {
|
||||
|
||||
@Query("SELECT t FROM SavingsAccountTransaction t WHERE t.savingsAccount.client.id = :clientId "
|
||||
+ "AND t.dateOf >= :since AND t.reversed = false")
|
||||
List<SavingsAccountTransaction> findForClientSince(@Param("clientId") Long clientId, @Param("since") LocalDate since);
|
||||
|
||||
@Query("SELECT t FROM SavingsAccountTransaction t WHERE t.dateOf BETWEEN :start AND :end AND t.reversed = false")
|
||||
List<SavingsAccountTransaction> findInDateRange(@Param("start") LocalDate start, @Param("end") LocalDate end);
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlAlertData;
|
||||
|
||||
public interface AmlAlertReadPlatformService {
|
||||
|
||||
/** @param statusFilter one of RAISED/UNDER_REVIEW/WHITELISTED/ESCALATED, or null/blank for all. */
|
||||
List<AmlAlertData> retrieve(String statusFilter);
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlAlertData;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlAlert;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlAlertRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class AmlAlertReadPlatformServiceImpl implements AmlAlertReadPlatformService {
|
||||
|
||||
private final AmlAlertRepository repository;
|
||||
|
||||
@Override
|
||||
public List<AmlAlertData> retrieve(String statusFilter) {
|
||||
final List<AmlAlert> alerts = (statusFilter == null || statusFilter.isBlank()) ? repository.findAll()
|
||||
: repository.findByStatusOrderByRaisedOnAsc(statusFilter);
|
||||
return alerts.stream().sorted(Comparator.comparing(AmlAlert::getRaisedOn).reversed()).map(this::mapToData)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private AmlAlertData mapToData(AmlAlert alert) {
|
||||
return new AmlAlertData(alert.getId(), alert.getTransactionType().name(), alert.getTransactionId(), alert.getClient().getId(),
|
||||
alert.getClient().getDisplayName(), alert.getRule().getRuleCode(), alert.getRule().getDescription(), alert.getAmount(),
|
||||
alert.getCurrencyCode(), alert.getRaisedOn(), alert.getStatus().name(),
|
||||
alert.getReviewedBy() == null ? null : alert.getReviewedBy().getUsername(), alert.getReviewedOn(),
|
||||
alert.getReviewNotes(), alert.getDisposition() == null ? null : alert.getDisposition().name());
|
||||
}
|
||||
}
|
||||
+30
@@ -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.aml.service;
|
||||
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlAlert;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCase;
|
||||
|
||||
/** First-level AML Officer disposition on an {@link AmlAlert}. */
|
||||
public interface AmlAlertWritePlatformService {
|
||||
|
||||
AmlAlert whitelist(Long alertId, String notes);
|
||||
|
||||
AmlStrCase escalate(Long alertId, String notes);
|
||||
}
|
||||
+63
@@ -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.aml.service;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlAlert;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlAlertRepository;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCase;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCaseRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class AmlAlertWritePlatformServiceImpl implements AmlAlertWritePlatformService {
|
||||
|
||||
private final AmlAlertRepository alertRepository;
|
||||
private final AmlStrCaseRepository strCaseRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final ClientRiskEscalationService clientRiskEscalationService;
|
||||
|
||||
@Override
|
||||
public AmlAlert whitelist(Long alertId, String notes) {
|
||||
final AmlAlert alert = loadAlert(alertId);
|
||||
alert.whitelist(context.authenticatedUser(), notes);
|
||||
return alertRepository.save(alert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AmlStrCase escalate(Long alertId, String notes) {
|
||||
final AmlAlert alert = loadAlert(alertId);
|
||||
alert.escalate(context.authenticatedUser(), notes);
|
||||
alertRepository.save(alert);
|
||||
|
||||
final AmlStrCase strCase = strCaseRepository.save(AmlStrCase.openFor(alert));
|
||||
clientRiskEscalationService.onStrCaseEscalated(strCase);
|
||||
return strCase;
|
||||
}
|
||||
|
||||
private AmlAlert loadAlert(Long alertId) {
|
||||
return alertRepository.findById(alertId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("AML alert " + alertId + " does not exist"));
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.YearMonth;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlCtrReport;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlCtrReportLine;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlCtrReportLineRepository;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlCtrReportRepository;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlTransactionType;
|
||||
import org.apache.fineract.portfolio.aml.repository.AmlLoanTransactionQueryRepository;
|
||||
import org.apache.fineract.portfolio.aml.repository.AmlSavingsTransactionQueryRepository;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Monthly, mechanical CTR batch: every individually-large cash transaction (savings or loan, ≥
|
||||
* ₹10L) gets its own line; every client whose smaller cash transactions of a single type sum to ≥
|
||||
* ₹10L within the month gets one connected-group line for that type. Purely threshold-based - no
|
||||
* Principal Officer judgment, matching the regulation. NEFT/RTGS transfers are non-cash by nature
|
||||
* and are out of scope for CTR.
|
||||
*
|
||||
* <p>
|
||||
* Known v1 limitation: a client splitting cash activity across BOTH savings and loan channels to
|
||||
* stay under ₹10L in each is not caught by this aggregation - each channel is summed separately.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class AmlCtrReportGenerationService {
|
||||
|
||||
private static final BigDecimal CTR_THRESHOLD = new BigDecimal("1000000");
|
||||
|
||||
private final AmlCtrReportRepository ctrReportRepository;
|
||||
private final AmlCtrReportLineRepository ctrReportLineRepository;
|
||||
private final AmlSavingsTransactionQueryRepository savingsQueryRepository;
|
||||
private final AmlLoanTransactionQueryRepository loanQueryRepository;
|
||||
|
||||
public AmlCtrReport generateForMonth(YearMonth month) {
|
||||
final String reportMonth = month.toString();
|
||||
if (ctrReportRepository.findByReportMonth(reportMonth).isPresent()) {
|
||||
throw new IllegalStateException("CTR report for " + reportMonth + " has already been generated");
|
||||
}
|
||||
|
||||
final LocalDate start = month.atDay(1);
|
||||
final LocalDate end = month.atEndOfMonth();
|
||||
|
||||
final List<PendingLine> pendingLines = new ArrayList<>();
|
||||
final Map<Long, BigDecimal> smallSavingsCashByClient = new HashMap<>();
|
||||
final Map<Long, BigDecimal> smallLoanCashByClient = new HashMap<>();
|
||||
|
||||
for (final SavingsAccountTransaction t : savingsQueryRepository.findInDateRange(start, end)) {
|
||||
if (!(t.isDeposit() || t.isWithdrawal()) || !isCash(t.getPaymentDetail())) {
|
||||
continue;
|
||||
}
|
||||
final Client client = t.getSavingsAccount().getClient();
|
||||
if (client == null) {
|
||||
continue;
|
||||
}
|
||||
if (t.getAmount().compareTo(CTR_THRESHOLD) >= 0) {
|
||||
pendingLines.add(new PendingLine(AmlTransactionType.SAVINGS, t.getId(), client.getId(), t.getAmount(), false));
|
||||
} else {
|
||||
smallSavingsCashByClient.merge(client.getId(), t.getAmount(), BigDecimal::add);
|
||||
}
|
||||
}
|
||||
|
||||
for (final LoanTransaction t : loanQueryRepository.findInDateRange(start, end)) {
|
||||
if (!t.isRepaymentLikeType() || !isCash(t.getPaymentDetail())) {
|
||||
continue;
|
||||
}
|
||||
final Client client = t.getLoan().getClient();
|
||||
if (client == null) {
|
||||
continue;
|
||||
}
|
||||
final BigDecimal amount = t.getAmount(t.getLoan().getCurrency()).getAmount();
|
||||
if (amount.compareTo(CTR_THRESHOLD) >= 0) {
|
||||
pendingLines.add(new PendingLine(AmlTransactionType.LOAN, t.getId(), client.getId(), amount, false));
|
||||
} else {
|
||||
smallLoanCashByClient.merge(client.getId(), amount, BigDecimal::add);
|
||||
}
|
||||
}
|
||||
|
||||
addConnectedGroupLines(pendingLines, smallSavingsCashByClient, AmlTransactionType.SAVINGS);
|
||||
addConnectedGroupLines(pendingLines, smallLoanCashByClient, AmlTransactionType.LOAN);
|
||||
|
||||
final AmlCtrReport report = ctrReportRepository
|
||||
.save(AmlCtrReport.draft(reportMonth, LocalDateTime.now(), pendingLines.size()));
|
||||
for (final PendingLine pending : pendingLines) {
|
||||
ctrReportLineRepository.save(AmlCtrReportLine.of(report, pending.transactionType(), pending.transactionId(),
|
||||
pending.clientId(), pending.amount(), pending.connectedGroup()));
|
||||
}
|
||||
return report;
|
||||
}
|
||||
|
||||
private void addConnectedGroupLines(List<PendingLine> pendingLines, Map<Long, BigDecimal> smallAmountsByClient,
|
||||
AmlTransactionType transactionType) {
|
||||
smallAmountsByClient.forEach((clientId, total) -> {
|
||||
if (total.compareTo(CTR_THRESHOLD) >= 0) {
|
||||
pendingLines.add(new PendingLine(transactionType, null, clientId, total, true));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Holds a line's values before the parent {@link AmlCtrReport} row exists to attach them to. */
|
||||
private record PendingLine(AmlTransactionType transactionType, Long transactionId, Long clientId, BigDecimal amount,
|
||||
boolean connectedGroup) {
|
||||
}
|
||||
|
||||
private boolean isCash(PaymentDetail paymentDetail) {
|
||||
return paymentDetail == null || Boolean.TRUE.equals(paymentDetail.getPaymentType().getIsCashPayment());
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlCtrReportData;
|
||||
|
||||
public interface AmlCtrReportReadPlatformService {
|
||||
|
||||
List<AmlCtrReportData> retrieveAll();
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlCtrReportData;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlCtrReport;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlCtrReportRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class AmlCtrReportReadPlatformServiceImpl implements AmlCtrReportReadPlatformService {
|
||||
|
||||
private final AmlCtrReportRepository repository;
|
||||
|
||||
@Override
|
||||
public List<AmlCtrReportData> retrieveAll() {
|
||||
return repository.findAll().stream().sorted(Comparator.comparing(AmlCtrReport::getReportMonth).reversed())
|
||||
.map(this::mapToData).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private AmlCtrReportData mapToData(AmlCtrReport report) {
|
||||
return new AmlCtrReportData(report.getId(), report.getReportMonth(), report.getGeneratedOn(), report.getRecordCount(),
|
||||
report.getFileReference(), report.getStatus().name(), report.getSubmittedOn());
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlTransactionContext;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlTransactionType;
|
||||
import org.apache.fineract.portfolio.aml.repository.AmlFundTransferQueryRepository;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.fundtransfer.domain.FundTransfer;
|
||||
import org.apache.fineract.portfolio.fundtransfer.domain.FundTransferStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Entry point for AML monitoring of NEFT/RTGS/IMPS/INTERNAL fund transfers. This module's
|
||||
* fund-transfer code (unlike savings/loans) doesn't use Fineract's BusinessEvent framework - it
|
||||
* has its own simpler {@code FundTransferEvent} append-only status log - so rather than bolting on
|
||||
* an unrelated event bus, this is called directly from the write paths (synchronous strategy
|
||||
* execution and the NEFT batch processor) at the point a transfer reaches SUCCESS.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class AmlFundTransferEvaluationService {
|
||||
|
||||
private static final int STRUCTURING_WINDOW_DAYS = 30;
|
||||
|
||||
private final AmlRuleEvaluationService ruleEvaluationService;
|
||||
private final AmlFundTransferQueryRepository fundTransferQueryRepository;
|
||||
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
||||
|
||||
public void evaluateIfSuccessful(FundTransfer transfer) {
|
||||
if (transfer.getStatus() != FundTransferStatus.SUCCESS || transfer.getClientId() == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final Client client = clientRepositoryWrapper.findOneWithNotFoundDetection(transfer.getClientId());
|
||||
final LocalDateTime since = LocalDateTime.now().minusDays(STRUCTURING_WINDOW_DAYS);
|
||||
final BigDecimal windowAggregate = fundTransferQueryRepository.sumForClientSince(client.getId(), FundTransferStatus.SUCCESS,
|
||||
since);
|
||||
|
||||
final AmlTransactionContext context = new AmlTransactionContext(AmlTransactionType.TRANSFER, transfer.getId(), client,
|
||||
transfer.getAmount(), "INR",
|
||||
transfer.getSettledAt() != null ? transfer.getSettledAt() : transfer.getInitiatedAt(), false, windowAggregate);
|
||||
ruleEvaluationService.evaluate(context);
|
||||
} catch (final RuntimeException e) {
|
||||
// AML alerting must never block the underlying fund transfer it is observing
|
||||
log.error("AML rule evaluation failed for fund transfer {}", transfer.getId(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlTransactionContext;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlAlert;
|
||||
|
||||
/**
|
||||
* Evaluates a single transaction against the active {@code AmlTransactionRule} catalog and
|
||||
* persists one {@link AmlAlert} per rule hit. Runs identically regardless of the client's current
|
||||
* KYC risk category - risk category is never used to suppress evaluation.
|
||||
*/
|
||||
public interface AmlRuleEvaluationService {
|
||||
|
||||
List<AmlAlert> evaluate(AmlTransactionContext context);
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlTransactionContext;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlAlert;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlAlertRepository;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlAppliesTo;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlTransactionRule;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlTransactionRuleRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class AmlRuleEvaluationServiceImpl implements AmlRuleEvaluationService {
|
||||
|
||||
private final AmlTransactionRuleRepository ruleRepository;
|
||||
private final AmlAlertRepository alertRepository;
|
||||
|
||||
@Override
|
||||
public List<AmlAlert> evaluate(AmlTransactionContext context) {
|
||||
final List<AmlTransactionRule> candidates = new ArrayList<>();
|
||||
candidates.addAll(ruleRepository.findByActiveTrueAndAppliesTo(context.getTransactionType().name()));
|
||||
candidates.addAll(ruleRepository.findByActiveTrueAndAppliesTo(AmlAppliesTo.ALL.name()));
|
||||
|
||||
final List<AmlAlert> raised = new ArrayList<>();
|
||||
for (final AmlTransactionRule rule : candidates) {
|
||||
if (isHit(rule, context)) {
|
||||
final AmlAlert alert = AmlAlert.raise(context.getTransactionType(), context.getTransactionId(), context.getClient(), rule,
|
||||
context.getAmount(), context.getCurrencyCode(), context.getOccurredOn());
|
||||
raised.add(alertRepository.save(alert));
|
||||
}
|
||||
}
|
||||
return raised;
|
||||
}
|
||||
|
||||
private boolean isHit(AmlTransactionRule rule, AmlTransactionContext context) {
|
||||
if (rule.getThresholdAmount() == null) {
|
||||
// Pattern-only VELOCITY rules (e.g. same-day movement) need bespoke detection logic
|
||||
// per rule and are not yet evaluated here - they exist in the catalog for future work.
|
||||
return false;
|
||||
}
|
||||
switch (rule.getRuleType()) {
|
||||
case THRESHOLD:
|
||||
return context.getAmount().compareTo(rule.getThresholdAmount()) >= 0;
|
||||
case STRUCTURING:
|
||||
case VELOCITY:
|
||||
return context.getWindowAggregateAmount() != null
|
||||
&& context.getWindowAggregateAmount().compareTo(rule.getThresholdAmount()) >= 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -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.aml.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlStrCaseData;
|
||||
|
||||
public interface AmlStrCaseReadPlatformService {
|
||||
|
||||
List<AmlStrCaseData> retrieveAll();
|
||||
|
||||
AmlStrCaseData retrieveOne(Long strCaseId);
|
||||
|
||||
List<AmlStrCaseData> weeklyDigest();
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.aml.data.AmlStrCaseData;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCase;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCaseRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class AmlStrCaseReadPlatformServiceImpl implements AmlStrCaseReadPlatformService {
|
||||
|
||||
private final AmlStrCaseRepository repository;
|
||||
private final AmlStrWeeklyDigestService digestService;
|
||||
|
||||
@Override
|
||||
public List<AmlStrCaseData> retrieveAll() {
|
||||
return repository.findAll().stream().map(this::mapToData).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AmlStrCaseData retrieveOne(Long strCaseId) {
|
||||
return repository.findById(strCaseId).map(this::mapToData)
|
||||
.orElseThrow(() -> new EntityNotFoundException("AML STR case " + strCaseId + " does not exist"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AmlStrCaseData> weeklyDigest() {
|
||||
return digestService.currentWeekDigest().stream().map(this::mapToData).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private AmlStrCaseData mapToData(AmlStrCase strCase) {
|
||||
final long cumulative = repository.countByClient_Id(strCase.getClient().getId());
|
||||
return new AmlStrCaseData(strCase.getId(), strCase.getAlert().getId(), strCase.getClient().getId(),
|
||||
strCase.getClient().getDisplayName(), strCase.getAssignedPo() == null ? null : strCase.getAssignedPo().getUsername(),
|
||||
strCase.getInvestigationNotes(), strCase.getSourceOfFundsNotes(),
|
||||
strCase.getConclusion() == null ? null : strCase.getConclusion().name(),
|
||||
strCase.getConcludedBy() == null ? null : strCase.getConcludedBy().getUsername(), strCase.getConcludedOn(),
|
||||
strCase.getFiuDueDate(), strCase.getStrFiledOn(), strCase.getStrReferenceNo(), strCase.getStatus().name(), cumulative);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.aml.service;
|
||||
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCase;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrConclusion;
|
||||
|
||||
/** Principal-Officer-only actions on an escalated {@link AmlStrCase}. */
|
||||
public interface AmlStrCaseWritePlatformService {
|
||||
|
||||
AmlStrCase assignPrincipalOfficer(Long strCaseId, Long principalOfficerUserId);
|
||||
|
||||
AmlStrCase conclude(Long strCaseId, AmlStrConclusion conclusion, String investigationNotes, String sourceOfFundsNotes);
|
||||
|
||||
AmlStrCase fileStr(Long strCaseId, String referenceNo);
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import java.time.LocalDate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCase;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCaseRepository;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrConclusion;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.apache.fineract.useradministration.domain.AppUserRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class AmlStrCaseWritePlatformServiceImpl implements AmlStrCaseWritePlatformService {
|
||||
|
||||
private final AmlStrCaseRepository strCaseRepository;
|
||||
private final AppUserRepository appUserRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final StrSlaCalculator strSlaCalculator;
|
||||
|
||||
@Override
|
||||
public AmlStrCase assignPrincipalOfficer(Long strCaseId, Long principalOfficerUserId) {
|
||||
final AmlStrCase strCase = loadCase(strCaseId);
|
||||
final AppUser po = appUserRepository.findById(principalOfficerUserId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("User " + principalOfficerUserId + " does not exist"));
|
||||
strCase.assignPrincipalOfficer(po);
|
||||
return strCaseRepository.save(strCase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AmlStrCase conclude(Long strCaseId, AmlStrConclusion conclusion, String investigationNotes, String sourceOfFundsNotes) {
|
||||
final AmlStrCase strCase = loadCase(strCaseId);
|
||||
final AppUser principalOfficer = context.authenticatedUser();
|
||||
final LocalDate concludedOn = LocalDate.now();
|
||||
final LocalDate fiuDueDate = conclusion == AmlStrConclusion.SUSPICIOUS
|
||||
? strSlaCalculator.calculateFiuDueDate(concludedOn, strCase.getClient().officeId())
|
||||
: null;
|
||||
strCase.conclude(principalOfficer, conclusion, investigationNotes, sourceOfFundsNotes, concludedOn, fiuDueDate);
|
||||
return strCaseRepository.save(strCase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AmlStrCase fileStr(Long strCaseId, String referenceNo) {
|
||||
final AmlStrCase strCase = loadCase(strCaseId);
|
||||
strCase.fileStr(referenceNo, LocalDate.now());
|
||||
return strCaseRepository.save(strCase);
|
||||
}
|
||||
|
||||
private AmlStrCase loadCase(Long strCaseId) {
|
||||
return strCaseRepository.findById(strCaseId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("AML STR case " + strCaseId + " does not exist"));
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCase;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCaseRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Internal MIS view of STR cases the Principal Officer concluded suspicious in the last 7 calendar
|
||||
* days. This is separate from, and does not replace, each case's own individual 7-working-day FIU
|
||||
* filing deadline ({@code AmlStrCase.fiuDueDate}) - it exists purely for compliance-team tracking.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class AmlStrWeeklyDigestService {
|
||||
|
||||
private static final int DIGEST_WINDOW_DAYS = 7;
|
||||
|
||||
private final AmlStrCaseRepository strCaseRepository;
|
||||
|
||||
public List<AmlStrCase> currentWeekDigest() {
|
||||
final LocalDate today = LocalDate.now();
|
||||
return strCaseRepository.findConcludedSuspiciousBetween(today.minusDays(DIGEST_WINDOW_DAYS), today);
|
||||
}
|
||||
|
||||
public List<AmlStrCase> digestFor(LocalDate from, LocalDate to) {
|
||||
return strCaseRepository.findConcludedSuspiciousBetween(from, to);
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.aml.data.ClientRiskCategoryHistoryData;
|
||||
|
||||
public interface ClientRiskCategoryHistoryReadPlatformService {
|
||||
|
||||
List<ClientRiskCategoryHistoryData> retrieveForClient(Long clientId);
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.aml.data.ClientRiskCategoryHistoryData;
|
||||
import org.apache.fineract.portfolio.aml.domain.ClientRiskCategoryHistory;
|
||||
import org.apache.fineract.portfolio.aml.domain.ClientRiskCategoryHistoryRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class ClientRiskCategoryHistoryReadPlatformServiceImpl implements ClientRiskCategoryHistoryReadPlatformService {
|
||||
|
||||
private final ClientRiskCategoryHistoryRepository repository;
|
||||
|
||||
@Override
|
||||
public List<ClientRiskCategoryHistoryData> retrieveForClient(Long clientId) {
|
||||
return repository.findByClient_IdOrderByChangedOnDesc(clientId).stream().map(this::mapToData).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ClientRiskCategoryHistoryData mapToData(ClientRiskCategoryHistory history) {
|
||||
return new ClientRiskCategoryHistoryData(history.getId(), history.getClient().getId(), history.getPreviousCategory(),
|
||||
history.getNewCategory(), history.getReason(), history.getTriggeredBy().name(),
|
||||
history.getStrCase() == null ? null : history.getStrCase().getId(), history.getChangedOn(),
|
||||
history.getChangedBy().getUsername());
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCase;
|
||||
|
||||
/**
|
||||
* A client's existing static KYC risk category never suppresses AML monitoring - a Low/Medium
|
||||
* risk client who breaches a rule still gets an alert and still goes through the STR pipeline.
|
||||
* This service closes the feedback loop the other way: on a client's 3rd cumulative
|
||||
* {@link AmlStrCase}, their risk category is automatically escalated one level, so repeated
|
||||
* breaches are reflected back into their KYC profile rather than only living in the AML module.
|
||||
*/
|
||||
public interface ClientRiskEscalationService {
|
||||
|
||||
void onStrCaseEscalated(AmlStrCase strCase);
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCase;
|
||||
import org.apache.fineract.portfolio.aml.domain.AmlStrCaseRepository;
|
||||
import org.apache.fineract.portfolio.aml.domain.ClientRiskCategoryHistory;
|
||||
import org.apache.fineract.portfolio.aml.domain.ClientRiskCategoryHistoryRepository;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientKyc;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientKycRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.client.domain.KycRiskCategory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class ClientRiskEscalationServiceImpl implements ClientRiskEscalationService {
|
||||
|
||||
private static final int ESCALATION_THRESHOLD = 3;
|
||||
|
||||
private final AmlStrCaseRepository strCaseRepository;
|
||||
private final ClientKycRepositoryWrapper clientKycRepositoryWrapper;
|
||||
private final ClientRiskCategoryHistoryRepository historyRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
@Override
|
||||
public void onStrCaseEscalated(AmlStrCase strCase) {
|
||||
final Client client = strCase.getClient();
|
||||
final long cumulativeCases = strCaseRepository.countByClient_Id(client.getId());
|
||||
if (cumulativeCases != ESCALATION_THRESHOLD) {
|
||||
// only fires on the exact 3rd case, not every case from the 3rd onward
|
||||
return;
|
||||
}
|
||||
|
||||
final ClientKyc kyc = clientKycRepositoryWrapper.findByClientIdWithNotFoundDetection(client.getId());
|
||||
final Integer previousCategory = kyc.getRiskCategoryValue();
|
||||
final Integer nextCategory = nextCategory(previousCategory);
|
||||
if (nextCategory.equals(previousCategory)) {
|
||||
log.info("Client {} reached {} cumulative AML STR cases but is already at the highest risk category", client.getId(),
|
||||
cumulativeCases);
|
||||
return;
|
||||
}
|
||||
|
||||
kyc.escalateRiskCategory(nextCategory);
|
||||
historyRepository.save(ClientRiskCategoryHistory.autoEscalation(client, previousCategory, nextCategory, strCase,
|
||||
context.authenticatedUser(), LocalDateTime.now()));
|
||||
}
|
||||
|
||||
private Integer nextCategory(Integer currentCategory) {
|
||||
if (currentCategory == null || currentCategory.equals(KycRiskCategory.LOW.getValue())) {
|
||||
return KycRiskCategory.MEDIUM.getValue();
|
||||
}
|
||||
if (currentCategory.equals(KycRiskCategory.MEDIUM.getValue())) {
|
||||
return KycRiskCategory.HIGH.getValue();
|
||||
}
|
||||
return KycRiskCategory.HIGH.getValue();
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.aml.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
|
||||
import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Computes the RBI/PMLA-mandated 7-working-day FIU-IND filing deadline from a Principal Officer's
|
||||
* STR conclusion date. "Working day" excludes both the tenant's configured weekly off-days
|
||||
* ({@link WorkingDays}) and declared office holidays - reuses the same calendar machinery loan
|
||||
* repayment scheduling already depends on, rather than a bespoke calculator.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class StrSlaCalculator {
|
||||
|
||||
private static final int STR_FILING_WORKING_DAYS = 7;
|
||||
|
||||
private final WorkingDaysRepositoryWrapper workingDaysRepositoryWrapper;
|
||||
private final HolidayRepositoryWrapper holidayRepositoryWrapper;
|
||||
|
||||
public LocalDate calculateFiuDueDate(LocalDate concludedOn, Long officeId) {
|
||||
final WorkingDays workingDays = workingDaysRepositoryWrapper.findOne();
|
||||
LocalDate date = concludedOn;
|
||||
int workingDaysElapsed = 0;
|
||||
while (workingDaysElapsed < STR_FILING_WORKING_DAYS) {
|
||||
date = date.plusDays(1);
|
||||
if (isWorkingDay(workingDays, date, officeId)) {
|
||||
workingDaysElapsed++;
|
||||
}
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
private boolean isWorkingDay(WorkingDays workingDays, LocalDate date, Long officeId) {
|
||||
return WorkingDaysUtil.isWorkingDay(workingDays, date) && !holidayRepositoryWrapper.isHoliday(officeId, date);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.aml.starter;
|
||||
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.portfolio.aml.event.AmlDomainService;
|
||||
import org.apache.fineract.portfolio.aml.event.AmlDomainServiceImpl;
|
||||
import org.apache.fineract.portfolio.aml.repository.AmlLoanTransactionQueryRepository;
|
||||
import org.apache.fineract.portfolio.aml.repository.AmlSavingsTransactionQueryRepository;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlRuleEvaluationService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class AmlConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AmlDomainService.class)
|
||||
public AmlDomainService amlDomainService(BusinessEventNotifierService businessEventNotifierService,
|
||||
AmlRuleEvaluationService amlRuleEvaluationService, AmlSavingsTransactionQueryRepository savingsQueryRepository,
|
||||
AmlLoanTransactionQueryRepository loanQueryRepository) {
|
||||
return new AmlDomainServiceImpl(businessEventNotifierService, amlRuleEvaluationService, savingsQueryRepository,
|
||||
loanQueryRepository);
|
||||
}
|
||||
}
|
||||
+33
-1
@@ -41,6 +41,11 @@ public final class KycRiskRuleData implements Serializable {
|
||||
private final Integer riskCategory;
|
||||
private final String reason;
|
||||
private final Integer orderPosition;
|
||||
private final String amlCheckStatus;
|
||||
private final String bannedEntityMatch;
|
||||
private final String willfulDefaulter;
|
||||
private final String ecgcSalFlag;
|
||||
private final String dormantFlag;
|
||||
|
||||
public KycRiskRuleData(final Long id, final String ruleName, final Integer legalFormId,
|
||||
final Long occupationCategoryCvId, final String occupationCategoryName,
|
||||
@@ -48,7 +53,9 @@ public final class KycRiskRuleData implements Serializable {
|
||||
final BigDecimal minMonthlyIncome, final BigDecimal maxMonthlyIncome,
|
||||
final BigDecimal minAnnualIncome, final BigDecimal maxAnnualIncome,
|
||||
final BigDecimal minAnnualTurnover, final BigDecimal maxAnnualTurnover,
|
||||
final Integer riskCategory, final String reason, final Integer orderPosition) {
|
||||
final Integer riskCategory, final String reason, final Integer orderPosition,
|
||||
final String amlCheckStatus, final String bannedEntityMatch,
|
||||
final String willfulDefaulter, final String ecgcSalFlag, final String dormantFlag) {
|
||||
this.id = id;
|
||||
this.ruleName = ruleName;
|
||||
this.legalFormId = legalFormId;
|
||||
@@ -65,6 +72,11 @@ public final class KycRiskRuleData implements Serializable {
|
||||
this.riskCategory = riskCategory;
|
||||
this.reason = reason;
|
||||
this.orderPosition = orderPosition;
|
||||
this.amlCheckStatus = amlCheckStatus;
|
||||
this.bannedEntityMatch = bannedEntityMatch;
|
||||
this.willfulDefaulter = willfulDefaulter;
|
||||
this.ecgcSalFlag = ecgcSalFlag;
|
||||
this.dormantFlag = dormantFlag;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
@@ -130,4 +142,24 @@ public final class KycRiskRuleData implements Serializable {
|
||||
public Integer getOrderPosition() {
|
||||
return orderPosition;
|
||||
}
|
||||
|
||||
public String getAmlCheckStatus() {
|
||||
return amlCheckStatus;
|
||||
}
|
||||
|
||||
public String getBannedEntityMatch() {
|
||||
return bannedEntityMatch;
|
||||
}
|
||||
|
||||
public String getWillfulDefaulter() {
|
||||
return willfulDefaulter;
|
||||
}
|
||||
|
||||
public String getEcgcSalFlag() {
|
||||
return ecgcSalFlag;
|
||||
}
|
||||
|
||||
public String getDormantFlag() {
|
||||
return dormantFlag;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -62,7 +62,12 @@ public class KycRiskRulesReadPlatformServiceImpl implements KycRiskRulesReadPlat
|
||||
rule.getMaxAnnualTurnover(),
|
||||
rule.getRiskCategory(),
|
||||
rule.getReason(),
|
||||
rule.getOrderPosition()
|
||||
rule.getOrderPosition(),
|
||||
rule.getAmlCheckStatus(),
|
||||
rule.getBannedEntityMatch(),
|
||||
rule.getWillfulDefaulter(),
|
||||
rule.getEcgcSalFlag(),
|
||||
rule.getDormantFlag()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -30,6 +30,7 @@ import org.apache.fineract.infrastructure.imps.data.ImpsRequestData;
|
||||
import org.apache.fineract.infrastructure.imps.data.ImpsResponseData;
|
||||
import org.apache.fineract.infrastructure.imps.data.PartyData;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlFundTransferEvaluationService;
|
||||
import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
|
||||
import org.apache.fineract.portfolio.fundtransfer.data.ResolvedLimits;
|
||||
import org.apache.fineract.portfolio.fundtransfer.data.TransferResponseData;
|
||||
@@ -76,6 +77,7 @@ public class FundTransferWritePlatformServiceImpl implements FundTransferWritePl
|
||||
private final SanctionsScreeningService sanctionsScreeningService;
|
||||
private final TransferChannelWindowValidator windowValidator;
|
||||
private final SuspenseGlService suspenseGl;
|
||||
private final AmlFundTransferEvaluationService amlFundTransferEvaluationService;
|
||||
|
||||
// ─── /verify ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -168,6 +170,7 @@ public class FundTransferWritePlatformServiceImpl implements FundTransferWritePl
|
||||
|
||||
eventRepository.save(FundTransferEvent.of(saved.getId(), null, saved.getStatus(),
|
||||
needsApproval ? "INITIATED_PENDING_APPROVAL" : "INITIATED_AUTO_EXECUTE", null, cashier.getId()));
|
||||
amlFundTransferEvaluationService.evaluateIfSuccessful(saved);
|
||||
|
||||
return FundTransferReadPlatformServiceImpl.toData(saved);
|
||||
}
|
||||
@@ -190,6 +193,7 @@ public class FundTransferWritePlatformServiceImpl implements FundTransferWritePl
|
||||
executeStrategy(t);
|
||||
FundTransfer saved = fundTransferRepository.save(t);
|
||||
eventRepository.save(FundTransferEvent.of(saved.getId(), prev, saved.getStatus(), "APPROVED", null, approver.getId()));
|
||||
amlFundTransferEvaluationService.evaluateIfSuccessful(saved);
|
||||
return FundTransferReadPlatformServiceImpl.toData(saved);
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -26,6 +26,7 @@ import org.apache.fineract.infrastructure.imps.data.AccountData;
|
||||
import org.apache.fineract.infrastructure.imps.data.ImpsRequestData;
|
||||
import org.apache.fineract.infrastructure.imps.data.ImpsResponseData;
|
||||
import org.apache.fineract.infrastructure.imps.data.PartyData;
|
||||
import org.apache.fineract.portfolio.aml.service.AmlFundTransferEvaluationService;
|
||||
import org.apache.fineract.portfolio.fundtransfer.domain.FundTransfer;
|
||||
import org.apache.fineract.portfolio.fundtransfer.domain.FundTransferEvent;
|
||||
import org.apache.fineract.portfolio.fundtransfer.domain.FundTransferEventRepository;
|
||||
@@ -54,6 +55,7 @@ public class NeftBatchProcessorService {
|
||||
private final FundTransferEventRepository eventRepository;
|
||||
private final ImpsSwitchClient switchClient;
|
||||
private final SuspenseGlService suspenseGl;
|
||||
private final AmlFundTransferEvaluationService amlFundTransferEvaluationService;
|
||||
|
||||
@Value("${fineract.imps.our-default-ifsc:BANK0000001}")
|
||||
private String ourDefaultIfsc;
|
||||
@@ -109,6 +111,7 @@ public class NeftBatchProcessorService {
|
||||
fundTransferRepository.save(t);
|
||||
eventRepository.save(FundTransferEvent.of(t.getId(), prev, mapped, "NEFT_BATCH_PROCESSED",
|
||||
"code=" + code, null));
|
||||
amlFundTransferEvaluationService.evaluateIfSuccessful(t);
|
||||
log.info("[NeftBatchProcessor] txnRef={} {} → {} utr={}", t.getTxnRef(), prev, mapped, t.getUtr());
|
||||
return mapped;
|
||||
} catch (RuntimeException ex) {
|
||||
|
||||
@@ -483,4 +483,10 @@
|
||||
<include file="parts/0362_add_share_account_edit_approval_permissions.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0421_add_master_salutation_value.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0428_add_client_risk_rules.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0429_add_aml_fields_to_risk_rules.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0430_seed_aml_compliance_rules.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0433_create_aml_transaction_monitoring_tables.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0434_seed_aml_transaction_rules.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0435_add_aml_officer_and_principal_officer_roles.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0436_fix_missing_client_risk_rule_columns.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+39
@@ -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.13.xsd">
|
||||
<changeSet author="fineract" id="0429-1">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_risk_rule" columnName="aml_check_status"/></not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_risk_rule">
|
||||
<column name="aml_check_status" type="VARCHAR(20)"/>
|
||||
<column name="banned_entity_match" type="VARCHAR(10)"/>
|
||||
<column name="willful_defaulter" type="VARCHAR(10)"/>
|
||||
<column name="ecgc_sal_flag" type="VARCHAR(10)"/>
|
||||
<column name="dormant_flag" type="VARCHAR(20)"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
<changeSet author="fineract" id="0429-2">
|
||||
<modifyDataType tableName="m_client_risk_rule" columnName="rule_name" newDataType="VARCHAR(500)"/>
|
||||
<modifyDataType tableName="m_client_risk_rule" columnName="reason" newDataType="VARCHAR(500)"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
<?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">
|
||||
|
||||
<changeSet author="fineract" id="0430-1">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_client_risk_rule WHERE order_position = 1 AND rule_name LIKE 'Maintaining%'</sqlCheck>
|
||||
</preConditions>
|
||||
<sql>
|
||||
INSERT INTO m_client_risk_rule (rule_name, legal_form_id, occupation_category_cv_id, occupation_cv_id, min_monthly_income, min_annual_income, min_annual_turnover, risk_category, reason, order_position, aml_check_status, banned_entity_match, willful_defaulter, ecgc_sal_flag, dormant_flag)
|
||||
VALUES
|
||||
('Maintaining a low or overdrawn balance with high activity', null, null, null, null, null, null, 3, 'Maintaining a low or overdrawn balance with high activity', 1, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Multiple deposit of Mcs/DDs. etc followed by immediate withdrawal', null, null, null, null, null, null, 3, 'Multiple deposit of Mcs/DDs. etc followed by immediate withdrawal', 2, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Same funds being moved repeatedly among several accounts', null, null, null, null, null, null, 3, 'Same funds being moved repeatedly among several accounts', 3, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Sudden surge in activity level', null, null, null, null, null, null, 3, 'Sudden surge in activity level', 4, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Multiple accounts under the same name', null, null, null, null, null, null, 2, 'Multiple accounts under the same name', 5, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Large cash transaction', null, null, null, null, null, null, 3, 'Large cash transaction', 6, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('An account of a customer who does not reside/ have office near to the branch even though there are bank branches near his office/residence', null, null, null, null, null, null, 2, 'Customer does not reside near the branch', 7, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Depositing cash by means of numerous credit slip such that the amount of each deposit is not substantial but the total of which is substantial', null, null, null, null, null, null, 3, 'Deposits total is substantial', 8, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Unexplained activity in dormant account', null, null, null, null, null, null, 3, 'Unexplained activity in dormant account', 9, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Substantial cash withdrawals in accounts of charitable organization', null, null, null, null, null, null, 3, 'Substantial cash withdrawals in accounts of charitable organization', 10, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Monitoring accounts of PEP(politically exposed person)', null, null, null, null, null, null, 3, 'Monitoring accounts of PEP (politically exposed person)', 11, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Monitoring RTGS Transactions', null, null, null, null, null, null, 2, 'Monitoring RTGS Transactions', 12, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Monitoring accounts of Jewellers', null, null, null, null, null, null, 3, 'Monitoring accounts of Jewellers', 13, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('New account Large transaction', null, null, null, null, null, null, 2, 'New account Large transaction', 14, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Non Home Branch Transaction', null, null, null, null, null, null, 2, 'Non Home Branch Transaction', 15, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Monitoring accounts of Staff', null, null, null, null, null, null, 2, 'Monitoring accounts of Staff', 16, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High Net worth', null, null, null, null, null, null, 2, 'High Net worth', 17, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customers with common phone numbers,mobile numbers', null, null, null, null, null, null, 2, 'Customers with common phone numbers / mobile numbers', 18, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High Value Transactions without PAN ID', null, null, null, null, null, null, 3, 'High Value Transactions without PAN ID', 19, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customers joint holder in multiple accounts', null, null, null, null, null, null, 2, 'Customers joint holder in multiple accounts', 20, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Structuring of deposits through multiple branches', null, null, null, null, null, null, 3, 'Structuring of deposits through multiple branches', 21, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Monitoring NEFT Transactions', null, null, null, null, null, null, 2, 'Monitoring NEFT Transactions', 22, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Large cash transaction(Debit)', null, null, null, null, null, null, 3, 'Large cash transaction (Debit)', 23, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Rapid Movement in accounts', null, null, null, null, null, null, 3, 'Rapid Movement in accounts', 24, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Risk Categorization', null, null, null, null, null, null, 2, 'Risk Categorization', 25, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High Value Cash transaction inconsistent with Profile', null, null, null, null, null, null, 3, 'High Value Cash transaction inconsistent with Profile', 26, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High Cash Activity inconsistent with Profile', null, null, null, null, null, null, 3, 'High Cash Activity inconsistent with Profile', 27, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Splitting of Cash deposits just below INR 10, 00, 000 in multiple accounts in a month', null, null, null, null, null, null, 3, 'Splitting of Cash deposits below INR 10 Lakhs', 28, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Splitting of cash deposits just below INR 50000', null, null, null, null, null, null, 3, 'Splitting of cash deposits below INR 50K', 29, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Frequent low cash deposits', null, null, null, null, null, null, 2, 'Frequent low cash deposits', 30, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Frequent low cash withdrawals', null, null, null, null, null, null, 2, 'Frequent low cash withdrawals', 31, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Many to One Fund Transfer', null, null, null, null, null, null, 3, 'Many to One Fund Transfer', 32, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('One to Many fund transfer', null, null, null, null, null, null, 3, 'One to Many fund transfer', 33, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Multiple customers working together', null, null, null, null, null, null, 3, 'Multiple customers working together', 34, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Repeated small cash deposits followed by immediate ATM withdrawals in different location', null, null, null, null, null, null, 3, 'Repeated small deposits followed by ATM withdrawals', 35, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Repeated small value inward transfer from unrelated parties followed by immediate ATM withdrawals', null, null, null, null, null, null, 3, 'Inward transfer followed by ATM withdrawals', 36, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Repeated small value inward remittance from unrelated parties followed by ATM withdrawals', null, null, null, null, null, null, 3, 'Inward remittance followed by ATM withdrawals', 37, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Repayment of loan in cash', null, null, null, null, null, null, 2, 'Repayment of loan in cash', 38, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Premature closure of large FDR through PO/DD', null, null, null, null, null, null, 2, 'Premature closure of large FDR through PO/DD', 39, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High number of cheque leaves', null, null, null, null, null, null, 2, 'High number of cheque leaves', 40, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High value inward remittance', null, null, null, null, null, null, 3, 'High value inward remittance', 41, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Inward remittance in a new account', null, null, null, null, null, null, 3, 'Inward remittance in a new account', 42, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Threshold Limit in Credit entry', null, null, null, null, null, null, 2, 'Threshold Limit in Credit entry', 43, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Sudden Increase in Value of Transactions in a Month for the Client', null, null, null, null, null, null, 3, 'Sudden Increase in Value of Transactions in a Month for the Client', 44, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Number of Transactions in newly opened Accounts, monthly', null, null, null, null, null, null, 2, 'Number of Transactions in newly opened Accounts, monthly', 45, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Frequent Locker Operations (Monthly)', null, null, null, null, null, null, 2, 'Frequent Locker Operations (Monthly)', 46, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High Value Transactions in current account', null, null, null, null, null, null, 3, 'High Value Transactions in current account', 47, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Monitor accounts of parties'' occupation related to Real Estate', null, null, null, null, null, null, 3, 'Monitor accounts of parties'' occupation related to Real Estate', 48, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Large repetitive card usage at the same merchant', null, null, null, null, null, null, 2, 'Large repetitive card usage at the same merchant', 49, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Sudden High Value Transaction for the Client(Monthly)', null, null, null, null, null, null, 3, 'Sudden High Value Transaction for the Client (Monthly)', 50, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('One to Many Fund Transfer (Monthly)', null, null, null, null, null, null, 3, 'One to Many Fund Transfer (Monthly)', 51, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Many to One Fund Transfer (Monthly)', null, null, null, null, null, null, 3, 'Many to One Fund Transfer (Monthly)', 52, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Inward Remittance Inconsistent with Client Profile (Monthly)', null, null, null, null, null, null, 3, 'Inward Remittance Inconsistent with Client Profile (Monthly)', 53, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High Value Non- Cash Deposits in a day', null, null, null, null, null, null, 3, 'High Value Non-Cash Deposits in a day', 55, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High Value non- Cash Withdrawal in a day', null, null, null, null, null, null, 3, 'High Value non-Cash Withdrawal in a day', 56, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High Value Cash Deposits in a month', null, null, null, null, null, null, 3, 'High Value Cash Deposits in a month', 57, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High Value Cash Withdrawals in a month', null, null, null, null, null, null, 3, 'High Value Cash Withdrawals in a month', 58, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High Value non-cash Deposits in a month', null, null, null, null, null, null, 3, 'High Value non-cash Deposits in a month', 59, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High value non-cash withdrawals in a month', null, null, null, null, null, null, 3, 'High value non-cash withdrawals in a month', 60, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Sudden high value transaction for the client', null, null, null, null, null, null, 3, 'Sudden high value transaction for the client', 61, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Sudden increase in value of transactions in a month for the client', null, null, null, null, null, null, 3, 'Sudden increase in value of transactions in a month for the client', 62, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Sudden increase in number of transactions in a month for the client', null, null, null, null, null, null, 2, 'Sudden increase in number of transactions in a month for the client', 63, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High Value transactions in a new Account', null, null, null, null, null, null, 3, 'High Value transactions in a new Account', 64, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Frequent cash deposits just below INR 10,00,000', null, null, null, null, null, null, 3, 'Frequent cash deposits below INR 10 Lakhs', 65, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High Value transactions by high risk customers', null, null, null, null, null, null, 3, 'High Value transactions by high risk customers', 66, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High activity in a new account', null, null, null, null, null, null, 3, 'High activity in a new account', 67, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High value Cash transactions in accounts without PAN', null, null, null, null, null, null, 3, 'High value Cash transactions in accounts without PAN', 68, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('high value non cash transactions in accounts without PAN', null, null, null, null, null, null, 3, 'high value non cash transactions in accounts without PAN', 69, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Repeated small value transfers to a single Indian party', null, null, null, null, null, null, 3, 'Repeated small value transfers to a single Indian party', 70, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Repeated small value transfers to multiple parties followed by immediate cash/non-cash withdrawals (through ATMs or other modes)', null, null, null, null, null, null, 3, 'Repeated small value transfers with immediate withdrawals', 71, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Repayment of loan in cash greater than INR [X] in [Y] months', null, null, null, null, null, null, 2, 'Repayment of loan in cash greater than threshold', 72, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Repayment of loan in cash', null, null, null, null, null, null, 2, 'Repayment of loan in cash', 73, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Sudden increase in cash deposits of customers', null, null, null, null, null, null, 3, 'Sudden increase in cash deposits of customers', 74, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Sudden increase in cash withdrawals of customers', null, null, null, null, null, null, 3, 'Sudden increase in cash withdrawals of customers', 75, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High value transactions in accounts opened and closed in a short duration', null, null, null, null, null, null, 3, 'High value transactions in accounts opened and closed in short duration', 76, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Unusually high activity in Current Account(CA)', null, null, null, null, null, null, 3, 'Unusually high activity in Current Account (CA)', 77, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('NPOs or charities receiving funds from India or abroad, and transferring the same to a number of domestic and foreign beneficiaries', null, null, null, null, null, null, 3, 'NPOs/Charities transferring funds to domestic/foreign beneficiaries', 78, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High value cash deposits in a day', null, null, null, null, null, null, 3, 'High value cash deposits in a day', 79, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High value cash withdrawals in a day', null, null, null, null, null, null, 3, 'High value cash withdrawals in a day', 80, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High value transactions in new accounts followed by period of inactivity', null, null, null, null, null, null, 3, 'High value transactions followed by inactivity', 81, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Frequent low cash withdrawals', null, null, null, null, null, null, 2, 'Frequent low cash withdrawals', 82, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('GST Refund Fraud', null, null, null, null, null, null, 3, 'GST Refund Fraud detected', 83, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Additional RFIs Rule - 91', null, null, null, null, null, null, 2, 'Additional RFIs Rule - 91', 91, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Additional RFIs Rule - 92', null, null, null, null, null, null, 2, 'Additional RFIs Rule - 92', 92, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Additional RFIs Rule - 93', null, null, null, null, null, null, 2, 'Additional RFIs Rule - 93', 93, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Additional RFIs Rule - 94', null, null, null, null, null, null, 2, 'Additional RFIs Rule - 94', 94, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Amounts of money transfer carried out by natural persons and legal entities are in multiples of 100/1,000 /10,000/100,000 USD/EUR/National currency', null, null, null, null, null, null, 3, 'Money transfer in round multiples of currency', 101, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Cash deposits followed by immediate outward remittance transactions', null, null, null, null, null, null, 3, 'Cash deposits followed by immediate outward remittance', 102, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Cross border transaction involving a country with high TF risk', null, null, null, null, null, null, 3, 'Cross border transaction involving high TF risk country', 103, 'FLAGGED', 'YES', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - High value cross border transactions with a country or location with high ML risk/ tax havens', null, null, null, null, null, null, 3, 'High value cross border transaction involving high ML risk country', 104, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - High value foreign inward remittance', null, null, null, null, null, null, 3, 'High value foreign inward remittance', 105, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - High value outward remittances in accounts', null, null, null, null, null, null, 3, 'High value outward remittances in accounts', 106, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Inward foreign remittance in a new account', null, null, null, null, null, null, 3, 'Inward foreign remittance in a new account', 107, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Inward foreign remittance inconsistent with client profile', null, null, null, null, null, null, 3, 'Inward foreign remittance inconsistent with client profile', 108, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Inward remittance followed by immediate withdrawal/transfer to other accounts', null, null, null, null, null, null, 3, 'Inward remittance followed by immediate transfer', 109, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Inward remittance of same value from same remitter', null, null, null, null, null, null, 3, 'Inward remittance of same value from same remitter', 110, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Multiple cash deposits in one country followed by immediate ATM withdrawal in another country', null, null, null, null, null, null, 3, 'Multiple cash deposits followed by ATM withdrawal in another country', 111, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Non-cash deposits followed by immediate outward remittance transactions', null, null, null, null, null, null, 3, 'Non-cash deposits followed by immediate outward remittance', 112, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Outward foreign remittances in newly opened accounts', null, null, null, null, null, null, 3, 'Outward foreign remittances in newly opened accounts', 113, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Reg. NGOs - Prior Reference Category', null, null, null, null, null, null, 2, 'Account Monitoring - Reg. NGOs - Prior Reference Category', 114, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Repeated small value foreign inward remittance from unrelated parties followed by expenditure on specified activities such as purchase of tickets, hotel bookings etc', null, null, null, null, null, null, 3, 'Repeated small value foreign remittances for specified expenditures', 115, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Repeated small value inward remittance from unrelated parties followed by immediate cash withdrawal (through ATMs or other modes)', null, null, null, null, null, null, 3, 'Repeated small value inward remittances followed by cash withdrawal', 116, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Repeated small value withdrawals in sensitive locations', null, null, null, null, null, null, 3, 'Repeated small value withdrawals in sensitive locations', 117, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Structuring of cross border transactions to avoid reporting in Cross Border Wire Transfer Report(CBWTR)', null, null, null, null, null, null, 3, 'Structuring cross border transactions to avoid reporting', 118, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Afghan Drug Business - A company is used for making payments under international contracts for delivery of goods from India to Afghanistan and the company is not a contract party', null, null, null, null, null, null, 3, 'Payments under contract to Afghanistan where company is not a party', 119, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Afghan Drug Business - Large funds are received from or sent to Afghanistan as advances for export/ import without goods being exported/ imported', null, null, null, null, null, null, 3, 'Large funds sent/received to/from Afghanistan as advances without goods', 120, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Afghan Drug Business - Non residents remit funds to offshore companies and natural persons having offshore accounts', null, null, null, null, null, null, 3, 'Afghan drug business links to offshore accounts', 121, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Afghan Drug Business - Parties transacting in goods with Afghanistan which are not in the normal line of their declared business', null, null, null, null, null, null, 3, 'Parties transacting in goods with Afghanistan outside normal business line', 122, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Afghan Drug Business - Person getting cash deposits below threshold reporting limits are being followed by immediate ATM withdrawal in Afghanistan/Pakistan', null, null, null, null, null, null, 3, 'Cash deposits followed by immediate withdrawal in Afghanistan/Pakistan', 123, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Afghan Drug Business - Persons of Afghan nationality staying in India and sending multiple maximum permissible amounts to Afghanistan', null, null, null, null, null, null, 3, 'Afghan nationality sending multiple permissible amounts to Afghanistan', 124, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Afghan Drug Business - Students from other countries (having Afghan nationality) staying in India, maintaining more than one account and generating cash more than INR 10 lakh in a year', null, null, null, null, null, null, 3, 'Afghan students maintaining multiple accounts generating high cash', 125, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Afghan Drug Business - The accounts of persons show seasonal fluctuations in outbound transactions i.e. October - December and April - June coinciding with opium cultivation and harvesting', null, null, null, null, null, null, 3, 'Seasonal outbound fluctuations coinciding with opium harvesting', 126, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Credit Card Monitoring - Large credit balance in credit card', null, null, null, null, null, null, 2, 'Large credit balance in credit card', 127, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Credit Card Monitoring - Large repetitive credit card usage at the same merchant', null, null, null, null, null, null, 2, 'Large repetitive credit card usage at the same merchant', 128, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Credit Card Monitoring - Large value credit card transactions for purchase of high value goods', null, null, null, null, null, null, 2, 'Large value credit card transactions for high value goods', 129, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Credit Card Monitoring - Majority of credit card repayments in cash(repayments greater)', null, null, null, null, null, null, 2, 'Majority of credit card repayments in cash', 130, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Address found to be non existent or wrong', null, null, null, null, null, null, 3, 'Address found to be non-existent or wrong', 131, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Adverse media report about criminal activities of customer', null, null, null, null, null, null, 3, 'Adverse media report about criminal activities', 132, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Adverse media report about TF or terrorist activities of customer', null, null, null, null, null, null, 3, 'Adverse media report about terrorist financing/TF', 133, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Alert raised by other agents/subsidiaries /associates/other institutions', null, null, null, null, null, null, 2, 'Alert raised by other agents/subsidiaries/associates', 134, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Availing loan facility/ OD(Overdraft) facility against FDR(Fixed Deposit Receipt) within few days of creation', null, null, null, null, null, null, 2, 'Availing loan/OD facility against FDR within few days of creation', 135, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Complaint received from public', null, null, null, null, null, null, 2, 'Customer Due Diligence - Complaint received from public', 136, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Customer acting on behalf of a third party', null, null, null, null, null, null, 3, 'Customer acting on behalf of a third party', 137, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Customer avoiding nearer branches', null, null, null, null, null, null, 2, 'Customer avoiding nearer branches', 138, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Customer could not explain source of funds', null, null, null, null, null, null, 3, 'Customer could not explain source of funds', 139, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Customer did not complete transaction', null, null, null, null, null, null, 2, 'Customer did not complete transaction', 140, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Customer is being investigated for criminal offences', null, null, null, null, null, null, 3, 'Customer is being investigated for criminal offences', 141, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Customer is being investigated for TF offences', null, null, null, null, null, null, 3, 'Customer is being investigated for TF offences', 142, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Customer is nervous or over cautious', null, null, null, null, null, null, 2, 'Customer is nervous or over cautious', 143, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Customer left without opening account', null, null, null, null, null, null, 2, 'Customer left without opening account', 144, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Customer offered false or forged identification/address documents', null, null, null, null, null, null, 3, 'Customer offered false or forged ID/address documents', 145, 'FLAGGED', 'YES', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Customer provides inconsistent information', null, null, null, null, null, null, 2, 'Customer provides inconsistent information', 146, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Customer providing different details to avoid linkage', null, null, null, null, null, null, 3, 'Customer providing different details to avoid linkage', 147, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Customer wants to avoid reporting', null, null, null, null, null, null, 3, 'Customer wants to avoid reporting', 148, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Difficult to identify beneficial owner', null, null, null, null, null, null, 3, 'Difficult to identify beneficial owner', 149, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Directors are persons of very low means - individually they have nil or low net worth and do not have any substantial source of income', null, null, null, null, null, null, 3, 'Directors are persons of low net worth and no substantial source of income', 150, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Established Fraud Cases', null, null, null, null, null, null, 3, 'Customer Due Diligence - Established Fraud Cases', 151, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Frequent Locker operations', null, null, null, null, null, null, 2, 'Customer Due Diligence - Frequent Locker operations', 152, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Funds are transferred from the company to an unusually large number and different kind of beneficiaries(from different sectors/businesses)', null, null, null, null, null, null, 3, 'Funds transferred to large number of diverse beneficiaries', 153, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Generally no physical presence(other than a mailing address)', null, null, null, null, null, null, 3, 'Generally no physical presence (other than mailing address)', 154, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - High Forex Activity Authorised Dealer “ Category II(AD Cat-II) accounts', null, null, null, null, null, null, 3, 'High Forex Activity Authorised Dealer accounts', 155, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Little or no withdrawal from account for business purposes/ no recurrent business expenses', null, null, null, null, null, null, 2, 'Little or no withdrawal for business expenses', 156, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Multiple accounts by individual customer/company under various heads in a single branch', null, null, null, null, null, null, 3, 'Multiple accounts by individual customer under various heads', 157, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Multiple customers working as a group', null, null, null, null, null, null, 3, 'Multiple customers working as a group', 158, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Multiple customers working together', null, null, null, null, null, null, 3, 'Multiple customers working together', 159, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Payments have no stated purpose, do not have reference to goods or services, or identify only a contract or invoice number', null, null, null, null, null, null, 3, 'Payments have no stated purpose or reference', 160, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Rotation of funds between account of companies having same authorized signatory', null, null, null, null, null, null, 3, 'Rotation of funds between accounts with same authorized signatory', 161, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Transacting businesses i.e. entities doing business with the company share the same address or there are address related inconsistencies', null, null, null, null, null, null, 3, 'Transacting business entities sharing same address or inconsistencies', 162, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Customer Due Diligence - Transacting parties appear to be affiliated, conduct business out of a residential address or provide only a registered agent’s address', null, null, null, null, null, null, 3, 'Transacting parties conduct business out of residential or registered agent address', 163, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('FICN - Transaction(s) involving Fake Indian Currency Notes(FICN) - greater than', null, null, null, null, null, null, 3, 'Transaction involving Fake Indian Currency Notes (FICN)', 164, 'FLAGGED', 'YES', 'NO', 'NO', 'ACTIVE'),
|
||||
('FICN(India-Nepal) - Cash deposits made in home bank accounts located on Indo-Nepal border areas in Bihar(mainly in districts East and West Champaran) with quick ATM withdrawals(within 2 days) in Malda(West Bengal) area or cash withdrawal from branches', null, null, null, null, null, null, 3, 'FICN links Indo-Nepal border cash deposits and Malda ATM withdrawals', 165, 'FLAGGED', 'YES', 'NO', 'NO', 'ACTIVE'),
|
||||
('FICN(India-Nepal) - Frequent cash deposits made in bank accounts located in Malda if the cash is deposited in various different locations of the country e.g. Delhi/NCR, Karnataka, Gujarat, Tamil Nadu, Maharashtra etc. and withdrawn simultaneously at Malda', null, null, null, null, null, null, 3, 'Frequent cash deposits at different locations withdrawn at Malda', 166, 'FLAGGED', 'YES', 'NO', 'NO', 'ACTIVE'),
|
||||
('Mutilated Indian Currency Notes - Transaction(s) involving mutilated Indian Currency Notes', null, null, null, null, null, null, 2, 'Transaction involving mutilated Indian Currency Notes', 167, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Name Screening - Match with FIU-IND alerts/ lists/ requests', null, null, null, null, null, null, 3, 'Name Screening - Match with FIU-IND alerts/lists/requests', 168, 'BLOCKED', 'YES', 'YES', 'NO', 'ACTIVE'),
|
||||
('Name Screening - Match with other criminal lists', null, null, null, null, null, null, 3, 'Name Screening - Match with other criminal lists', 169, 'BLOCKED', 'YES', 'YES', 'NO', 'ACTIVE'),
|
||||
('Name Screening - Match with other TF List', null, null, null, null, null, null, 3, 'Name Screening - Match with other TF List', 170, 'BLOCKED', 'YES', 'YES', 'NO', 'ACTIVE'),
|
||||
('Name Screening - Match with UAPA List', null, null, null, null, null, null, 3, 'Name Screening - Match with UAPA List', 171, 'BLOCKED', 'YES', 'YES', 'NO', 'ACTIVE'),
|
||||
('Name Screening - Match with UN List', null, null, null, null, null, null, 3, 'Name Screening - Match with UN List', 172, 'BLOCKED', 'YES', 'YES', 'NO', 'ACTIVE'),
|
||||
('Non Customer Monitoring - Foreign exchange transactions for non-account holder transactions', null, null, null, null, null, null, 2, 'Foreign exchange transactions for non-account holder transactions', 173, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Non Customer Monitoring - Large value cash withdrawals against international card', null, null, null, null, null, null, 3, 'Large value cash withdrawals against international card', 174, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Non Customer Monitoring - Repeated small value cash withdrawals against international card', null, null, null, null, null, null, 3, 'Repeated small value cash withdrawals against international card', 175, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Overseas Direct Investment(ODI) - Non-submission of documentary evidence by the Indian Party under suspicious circumstances', null, null, null, null, null, null, 2, 'Non-submission of ODI documentary evidence', 176, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Shell Companies - Frequent transactions with entities located at high risk countries/ jurisdiction', null, null, null, null, null, null, 3, 'Frequent transactions with entities located in high risk countries', 177, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Shell Companies - Frequent transactions with entities located at, off shore financial centres', null, null, null, null, null, null, 3, 'Frequent transactions with entities located in off shore financial centres', 178, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Shell Companies - Immediate transfer of funds and very low minimum balance maintained in account', null, null, null, null, null, null, 3, 'Shell Companies - Immediate transfer of funds with low minimum balance', 179, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Terrorist Financing - Reg North East Insurgency(for Nagaland, Assam and Manipur)', null, null, null, null, null, null, 3, 'Terrorist Financing - Reg North East Insurgency', 180, 'BLOCKED', 'YES', 'YES', 'NO', 'ACTIVE'),
|
||||
('Terrorist Financing - Regarding Maoist Insurgency', null, null, null, null, null, null, 3, 'Terrorist Financing - Regarding Maoist Insurgency', 181, 'BLOCKED', 'YES', 'YES', 'NO', 'ACTIVE'),
|
||||
('Terrorist Financing - Transaction involving a location with terrorist incident', null, null, null, null, null, null, 3, 'Transaction involving a location with terrorist incident', 182, 'FLAGGED', 'YES', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - A customer deviates significantly from its historical pattern of trade activity(i.e. in terms of markets, monetary value, frequency of transactions, volume, or merchandise type)', null, null, null, null, null, null, 3, 'Customer deviates significantly from historical trade pattern', 183, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Advance for supply of goods or services is a major part/percentage of the total value of goods or services', null, null, null, null, null, null, 3, 'Trade Based Money Laundering - Advance for supply is a major part of value', 184, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Advance waivers provided or pre-accepted discrepancy(s) by the applicant and/ or the applicant is over-keen to waive discrepancy', null, null, null, null, null, null, 3, 'Advance waivers provided or applicant over-keen to waive discrepancy', 185, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Amount of advance is not in line with normal international trade for the kind of goods or services', null, null, null, null, null, null, 3, 'Amount of advance not in line with normal international trade', 186, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Booking of ticket abroad and subsequent cancelling and payment made to third party', null, null, null, null, null, null, 3, 'Booking ticket abroad with subsequent cancelling and third party payment', 187, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Circuitous route of shipment/ payment of goods inconsistent with normal geographic trade', null, null, null, null, null, null, 3, 'Circuitous route of shipment or payment inconsistent with trade', 188, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Consignment size is unreasonable compared to customer profile/capacity/size of business', null, null, null, null, null, null, 3, 'Consignment size unreasonable compared to customer profile', 189, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Conversion of financial assets to a virtual currency', null, null, null, null, null, null, 3, 'Conversion of financial assets to a virtual currency', 190, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Description of goods or services provided is vague', null, null, null, null, null, null, 3, 'Description of goods or services provided is vague', 191, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - EDPMS- Export advance received but goods not shipped', null, null, null, null, null, null, 3, 'EDPMS - Export advance received but goods not shipped', 192, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - EDPMS-Exports done but payment not received', null, null, null, null, null, null, 3, 'EDPMS - Exports done but payment not received', 193, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Frequent changes in payment instructions at the last minute or the use of repeatedly amended or frequently extended letters of credit without reasonable justification or for reasons like changes of beneficiary or location', null, null, null, null, null, null, 3, 'Frequent changes in payment instructions at the last minute or amended LC', 194, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - High proportion of high sea sales /merchanting trades', null, null, null, null, null, null, 3, 'High proportion of high sea sales / merchanting trades', 195, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - High value technical/ professional charges paid on account of consultancy', null, null, null, null, null, null, 3, 'High value technical/professional charges paid for consultancy', 196, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - IDPMS- Import advance sent but goods not received', null, null, null, null, null, null, 3, 'IDPMS - Import advance sent but goods not received', 197, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - IDPMS- Import done but payment not sent', null, null, null, null, null, null, 3, 'IDPMS - Import done but payment not sent', 198, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Import of goods from countries where underlying goods are not manufactured', null, null, null, null, null, null, 3, 'Import of goods from countries where underlying goods are not manufactured', 199, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Import payments being made against old bills after lapse of considerable period of time from import of goods, without appropriate justification and documentation', null, null, null, null, null, null, 3, 'Import payments against old bills without appropriate justification', 200, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Importer of goods or services not from same country from where wire(payment for import) originated', null, null, null, null, null, null, 3, 'Importer not from same country from where wire payment originated', 201, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Initial import remittances by customers', null, null, null, null, null, null, 3, 'Initial import remittances by customers', 202, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Inward remittance received from Exchange House for the purpose of trade settlements', null, null, null, null, null, null, 3, 'Inward remittance from Exchange House for trade settlements', 203, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Many proprietorship firms importing from same exporter from a foreign land', null, null, null, null, null, null, 3, 'Many proprietorship firms importing from same exporter from foreign land', 204, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Multiple advance remittances to single or multiple entities by the same entity within permissible ceiling', null, null, null, null, null, null, 3, 'Multiple advance remittances within permissible ceiling', 205, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Originator/beneficiary information missing in wire transfers', null, null, null, null, null, null, 3, 'Originator/beneficiary information missing in wire transfers', 206, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Outward remittance of same value from single customer. More than x transactions on last y days', null, null, null, null, null, null, 3, 'Outward remittance of same value from single customer', 207, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Outward remittance to same overseas party', null, null, null, null, null, null, 3, 'Outward remittance to same overseas party', 208, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Over/under invoicing of goods or services', null, null, null, null, null, null, 3, 'Over/under invoicing of goods or services', 209, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Receipt of multiple payments via internet payment service provider like Paypal, etc', null, null, null, null, null, null, 3, 'Receipt of multiple payments via internet payment service provider', 210, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Re-presentation of third-party trade / official document immediately after a turn-down for discrepancy', null, null, null, null, null, null, 3, 'Re-presentation of third-party trade/official document after turn-down', 211, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Routing of import payments above[X] value through India by a foreign based entity having operations and customer base outside India', null, null, null, null, null, null, 3, 'Routing of import payments through India by foreign entity', 212, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Submission/ payment of round figure bills', null, null, null, null, null, null, 3, 'Submission/ payment of round figure bills', 213, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Trade Based Money Laundering - Substantial inconsistencies between the information originally supplied and that contained in the documents', null, null, null, null, null, null, 3, 'Substantial inconsistencies in documentation supplied', 214, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - The LC contains non-standard clauses or phrases or has unusual characteristics', null, null, null, null, null, null, 3, 'The LC contains non-standard clauses or unusual characteristics', 215, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Transaction has no economic rationale', null, null, null, null, null, null, 3, 'Transaction has no economic rationale', 216, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Transaction inconsistent with business', null, null, null, null, null, null, 3, 'Transaction inconsistent with business', 217, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Transaction is unnecessarily complex', null, null, null, null, null, null, 3, 'Transaction is unnecessarily complex', 218, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Transactions not in-line with normal international trade for the given kind of goods and line of business of the parties involved', null, null, null, null, null, null, 3, 'Transactions not in-line with normal international trade', 219, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Transactions involving third parties which may not be contract parties(consignee and remitter are different)', null, null, null, null, null, null, 3, 'Transactions involving third parties which may not be contract parties', 220, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Transactions related to acquisition or sale of intangibles like specialised software, etc', null, null, null, null, null, null, 3, 'Transactions related to acquisition/sale of intangibles like software', 221, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Underlying goods or services involved in the trade transaction are of sensitive nature and/or the Value/Quantity of goods or services is not easily ascertainable', null, null, null, null, null, null, 3, 'Underlying goods/services of sensitive nature or value not easily ascertainable', 222, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Underlying goods or services not in line with customer''s profile and declared business', null, null, null, null, null, null, 3, 'Underlying goods/services not in line with customer profile and business', 223, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Trade Based Money Laundering - Wash sales or round trip sales - Accounts debited and then immediately credited or vice versa for related purchase/sale', null, null, null, null, null, null, 3, 'Wash sales or round trip sales on account debited/credited', 224, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High value cash transactions by dealer in precious metal or stone', null, null, null, null, null, null, 3, 'High value cash transactions by dealer in precious metal or stone', 225, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High value cash transactions in NPOs', null, null, null, null, null, null, 3, 'High value cash transactions in NPOs', 226, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('High value cash transactions pertaining to accounts of real estate agents and dealers', null, null, null, null, null, null, 3, 'High value cash transactions pertaining to accounts of real estate agents', 227, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Non Customer Monitoring - Foreign exchange transactions for non-account holder transactions(remittance)', null, null, null, null, null, null, 2, 'Non Customer Monitoring - Forex transactions for non-account holder', 228, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Credit Card Monitoring - Majority of credit card repayments in cash(repayments totalling)', null, null, null, null, null, null, 2, 'Credit Card Monitoring - Majority of repayments in cash', 229, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Terrorist Financing - Reg North East Insurgency(for Nagaland, Assam and Manipur) - Deposited Money Into Fix Deposits', null, null, null, null, null, null, 3, 'Terrorist Financing - North East Insurgency fix deposits', 230, 'BLOCKED', 'YES', 'YES', 'NO', 'ACTIVE'),
|
||||
('Terrorist Financing - Reg North East Insurgency(for Nagaland, Assam and Manipur) - withdrawn as cash/ by cheque', null, null, null, null, null, null, 3, 'Terrorist Financing - North East Insurgency cash/cheque withdrawals', 231, 'BLOCKED', 'YES', 'YES', 'NO', 'ACTIVE'),
|
||||
('Terrorist Financing - Reg North East Insurgency(for Nagaland, Assam and Manipur) - business or other legitimate activity', null, null, null, null, null, null, 3, 'Terrorist Financing - North East Insurgency business activity', 232, 'BLOCKED', 'YES', 'YES', 'NO', 'ACTIVE'),
|
||||
('Account Monitoring - Reg. NGOs - Receipt of funds in account', null, null, null, null, null, null, 2, 'Account Monitoring - Reg. NGOs - Receipt of funds in account', 233, 'FLAGGED', 'NO', 'NO', 'NO', 'ACTIVE'),
|
||||
('Terrorist Financing - Regarding Maoist Insurgency - Account funded in cash outside', null, null, null, null, null, null, 3, 'Maoist Insurgency - Account funded in cash outside', 234, 'BLOCKED', 'YES', 'YES', 'NO', 'ACTIVE'),
|
||||
('FICN - Transaction(s) involving Fake Indian Currency Notes(FICN) - aggregating to greater', null, null, null, null, null, null, 3, 'FICN - Transaction involving Fake Indian Currency Notes', 235, 'BLOCKED', 'YES', 'YES', 'NO', 'ACTIVE');
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
<?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">
|
||||
|
||||
<!-- Evaluable transaction-monitoring rule catalog. Distinct from m_client_risk_rule (KYC-time,
|
||||
client-profile scoring) - these rules are checked against live transactions as they post. -->
|
||||
<changeSet author="fineract" id="0433-1">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_aml_transaction_rule"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_aml_transaction_rule">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="rule_code" type="VARCHAR(50)">
|
||||
<constraints nullable="false" unique="true"/>
|
||||
</column>
|
||||
<column name="description" type="VARCHAR(500)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<!-- SAVINGS | LOAN | TRANSFER | ALL -->
|
||||
<column name="applies_to" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="threshold_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<!-- THRESHOLD | STRUCTURING | VELOCITY -->
|
||||
<column name="rule_type" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="window_days" type="INT">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<!-- traceability back to the seeded 221-rule AML catalog in m_client_risk_rule -->
|
||||
<column name="source_client_risk_rule_id" type="BIGINT">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0433-2">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><foreignKeyConstraintExists foreignKeyName="FK_m_aml_transaction_rule_source_risk_rule"/></not>
|
||||
</preConditions>
|
||||
<addForeignKeyConstraint baseTableName="m_aml_transaction_rule" baseColumnNames="source_client_risk_rule_id"
|
||||
referencedTableName="m_client_risk_rule" referencedColumnNames="id"
|
||||
constraintName="FK_m_aml_transaction_rule_source_risk_rule"/>
|
||||
</changeSet>
|
||||
|
||||
<!-- One row per rule hit on a live transaction. Fires regardless of the client's current
|
||||
KYC risk category - risk category affects re-KYC cadence only, never alert suppression. -->
|
||||
<changeSet author="fineract" id="0433-3">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_aml_alert"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_aml_alert">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<!-- SAVINGS | LOAN | TRANSFER -->
|
||||
<column name="transaction_type" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="transaction_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="client_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="rule_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="currency_code" type="VARCHAR(3)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="raised_on" type="DATETIME">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<!-- RAISED | UNDER_REVIEW | WHITELISTED | ESCALATED -->
|
||||
<column name="status" type="VARCHAR(20)" defaultValue="RAISED">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="reviewed_by" type="BIGINT">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="reviewed_on" type="DATETIME">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="review_notes" type="VARCHAR(1000)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<!-- WHITELIST | ESCALATE, set by the first-level AML Officer -->
|
||||
<column name="disposition" type="VARCHAR(20)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="created_by" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_on_utc" type="DATETIME">
|
||||
<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>
|
||||
<column name="version" type="BIGINT" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0433-4">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><foreignKeyConstraintExists foreignKeyName="FK_m_aml_alert_client"/></not>
|
||||
</preConditions>
|
||||
<addForeignKeyConstraint baseTableName="m_aml_alert" baseColumnNames="client_id"
|
||||
referencedTableName="m_client" referencedColumnNames="id"
|
||||
constraintName="FK_m_aml_alert_client"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0433-5">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><foreignKeyConstraintExists foreignKeyName="FK_m_aml_alert_rule"/></not>
|
||||
</preConditions>
|
||||
<addForeignKeyConstraint baseTableName="m_aml_alert" baseColumnNames="rule_id"
|
||||
referencedTableName="m_aml_transaction_rule" referencedColumnNames="id"
|
||||
constraintName="FK_m_aml_alert_rule"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0433-6">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><indexExists indexName="IDX_m_aml_alert_client_status"/></not>
|
||||
</preConditions>
|
||||
<createIndex indexName="IDX_m_aml_alert_client_status" tableName="m_aml_alert">
|
||||
<column name="client_id"/>
|
||||
<column name="status"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<!-- Created when an alert is escalated past first-level review. concluded_on is the Principal
|
||||
Officer's conclusion date - the RBI-mandated 7-working-day FIU filing clock starts here,
|
||||
never at the alert or transaction date. -->
|
||||
<changeSet author="fineract" id="0433-7">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_aml_str_case"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_aml_str_case">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="alert_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="client_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="assigned_po" type="BIGINT">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="investigation_notes" type="VARCHAR(2000)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="source_of_funds_notes" type="VARCHAR(2000)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<!-- NOT_SUSPICIOUS | SUSPICIOUS -->
|
||||
<column name="conclusion" type="VARCHAR(20)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="concluded_by" type="BIGINT">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="concluded_on" type="DATE">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="fiu_due_date" type="DATE">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="str_filed_on" type="DATE">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="str_reference_no" type="VARCHAR(100)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<!-- OPEN | CONCLUDED_CLEAR | CONCLUDED_SUSPICIOUS | FILED -->
|
||||
<column name="status" type="VARCHAR(25)" defaultValue="OPEN">
|
||||
<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="last_modified_by" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="last_modified_on_utc" type="DATETIME">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="version" type="BIGINT" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0433-8">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><foreignKeyConstraintExists foreignKeyName="FK_m_aml_str_case_alert"/></not>
|
||||
</preConditions>
|
||||
<addForeignKeyConstraint baseTableName="m_aml_str_case" baseColumnNames="alert_id"
|
||||
referencedTableName="m_aml_alert" referencedColumnNames="id"
|
||||
constraintName="FK_m_aml_str_case_alert"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0433-9">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><foreignKeyConstraintExists foreignKeyName="FK_m_aml_str_case_client"/></not>
|
||||
</preConditions>
|
||||
<addForeignKeyConstraint baseTableName="m_aml_str_case" baseColumnNames="client_id"
|
||||
referencedTableName="m_client" referencedColumnNames="id"
|
||||
constraintName="FK_m_aml_str_case_client"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0433-10">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><indexExists indexName="IDX_m_aml_str_case_client"/></not>
|
||||
</preConditions>
|
||||
<createIndex indexName="IDX_m_aml_str_case_client" tableName="m_aml_str_case">
|
||||
<column name="client_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<!-- Monthly mechanical CTR batch header - threshold-based, no PO judgment involved. -->
|
||||
<changeSet author="fineract" id="0433-11">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_aml_ctr_report"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_aml_ctr_report">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="report_month" type="VARCHAR(7)">
|
||||
<!-- yyyy-MM -->
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="generated_on" type="DATETIME">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="record_count" type="INT" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="file_reference" type="VARCHAR(150)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<!-- DRAFT | GENERATED | SUBMITTED -->
|
||||
<column name="status" type="VARCHAR(20)" defaultValue="DRAFT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="submitted_on" type="DATETIME">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="created_by" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_on_utc" type="DATETIME">
|
||||
<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>
|
||||
<column name="version" type="BIGINT" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0433-12">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><indexExists indexName="IDX_m_aml_ctr_report_month"/></not>
|
||||
</preConditions>
|
||||
<createIndex indexName="IDX_m_aml_ctr_report_month" tableName="m_aml_ctr_report" unique="true">
|
||||
<column name="report_month"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<!-- Lines feeding a CTR report: individually-large cash transactions, plus is_connected_group
|
||||
rows for clients whose smaller transactions sum above the monthly threshold (structuring). -->
|
||||
<changeSet author="fineract" id="0433-13">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_aml_ctr_report_line"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_aml_ctr_report_line">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="ctr_report_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="transaction_type" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="transaction_id" type="BIGINT">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="client_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="is_connected_group" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0433-14">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><foreignKeyConstraintExists foreignKeyName="FK_m_aml_ctr_report_line_report"/></not>
|
||||
</preConditions>
|
||||
<addForeignKeyConstraint baseTableName="m_aml_ctr_report_line" baseColumnNames="ctr_report_id"
|
||||
referencedTableName="m_aml_ctr_report" referencedColumnNames="id"
|
||||
constraintName="FK_m_aml_ctr_report_line_report"/>
|
||||
</changeSet>
|
||||
|
||||
<!-- Immutable, append-only audit trail answering "why did this client's risk category change".
|
||||
Backs the AML menu's existing "Risk Update History Report" item, which had no data behind it. -->
|
||||
<changeSet author="fineract" id="0433-15">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_client_risk_category_history"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_client_risk_category_history">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="client_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="previous_category" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="new_category" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="reason" type="VARCHAR(500)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<!-- AML_AUTO_ESCALATION | MANUAL -->
|
||||
<column name="triggered_by" type="VARCHAR(30)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="str_case_id" type="BIGINT">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="changed_on" type="DATETIME">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="changed_by" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0433-16">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><foreignKeyConstraintExists foreignKeyName="FK_m_client_risk_category_history_client"/></not>
|
||||
</preConditions>
|
||||
<addForeignKeyConstraint baseTableName="m_client_risk_category_history" baseColumnNames="client_id"
|
||||
referencedTableName="m_client" referencedColumnNames="id"
|
||||
constraintName="FK_m_client_risk_category_history_client"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0433-17">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><foreignKeyConstraintExists foreignKeyName="FK_m_client_risk_category_history_str_case"/></not>
|
||||
</preConditions>
|
||||
<addForeignKeyConstraint baseTableName="m_client_risk_category_history" baseColumnNames="str_case_id"
|
||||
referencedTableName="m_aml_str_case" referencedColumnNames="id"
|
||||
constraintName="FK_m_client_risk_category_history_str_case"/>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?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">
|
||||
|
||||
<!--
|
||||
Starter set of MACHINE-EVALUABLE transaction rules, curated from the 221-rule catalog
|
||||
already seeded in m_client_risk_rule (0430_seed_aml_compliance_rules.xml), whose
|
||||
order_position column is the rule's Srno. Most of the 221 rules are qualitative /
|
||||
KYC-time judgment calls (e.g. "Customer is nervous or over cautious") and are not
|
||||
included here - only ones with a clear, live-transaction-checkable condition are.
|
||||
Threshold numbers marked "illustrative default" mirror the bank's own Rules Parameter
|
||||
screens where a configured value was available (Rules 1, 6, 10, 11, 12, 13); others are
|
||||
reasonable starting points meant to be tuned via the AML rule administration UI.
|
||||
-->
|
||||
<changeSet author="fineract" id="0434-1">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_aml_transaction_rule</sqlCheck>
|
||||
</preConditions>
|
||||
<sql>
|
||||
INSERT INTO m_aml_transaction_rule (rule_code, description, applies_to, threshold_amount, rule_type, window_days, source_client_risk_rule_id, is_active)
|
||||
VALUES
|
||||
('CTR_THRESHOLD_10L', 'Mandatory CTR reporting threshold - single cash transaction of INR 10,00,000 or more', 'ALL', 1000000, 'THRESHOLD', null, null, true),
|
||||
('STRUCTURED_CTR_10L_MONTHLY', 'Connected cash transactions summing to INR 10,00,000 or more within a calendar month (CTR aggregation rule)', 'ALL', 1000000, 'STRUCTURING', 30, null, true),
|
||||
('LARGE_CASH_TXN_25L', 'Large cash transaction', 'ALL', 2500000, 'THRESHOLD', null, (SELECT id FROM m_client_risk_rule WHERE order_position = 6), true),
|
||||
('STRUCTURING_BELOW_10L', 'Splitting of cash deposits just below INR 10,00,000 in multiple accounts in a month', 'SAVINGS', 1000000, 'STRUCTURING', 30, (SELECT id FROM m_client_risk_rule WHERE order_position = 28), true),
|
||||
('STRUCTURING_BELOW_50K', 'Splitting of cash deposits just below INR 50,000', 'SAVINGS', 50000, 'STRUCTURING', 30, (SELECT id FROM m_client_risk_rule WHERE order_position = 29), true),
|
||||
('FREQUENT_LOW_CASH_DEPOSIT', 'Frequent low cash deposits', 'SAVINGS', 50000, 'VELOCITY', 7, (SELECT id FROM m_client_risk_rule WHERE order_position = 30), true),
|
||||
('FREQUENT_LOW_CASH_WITHDRAWAL', 'Frequent low cash withdrawals', 'SAVINGS', 50000, 'VELOCITY', 7, (SELECT id FROM m_client_risk_rule WHERE order_position = 31), true),
|
||||
('FREQUENT_CASH_NEAR_10L', 'Frequent cash deposits just below INR 10,00,000', 'SAVINGS', 1000000, 'STRUCTURING', 30, (SELECT id FROM m_client_risk_rule WHERE order_position = 65), true),
|
||||
('RTGS_HIGH_VALUE', 'Monitoring RTGS Transactions', 'TRANSFER', 2000000, 'THRESHOLD', null, (SELECT id FROM m_client_risk_rule WHERE order_position = 12), true),
|
||||
('JEWELLER_ACCOUNT_LARGE_TXN', 'Monitoring accounts of Jewellers', 'ALL', 2000000, 'THRESHOLD', null, (SELECT id FROM m_client_risk_rule WHERE order_position = 13), true),
|
||||
('CHARITABLE_ORG_CASH_WITHDRAWAL', 'Substantial cash withdrawals in accounts of charitable organization', 'SAVINGS', 50000, 'THRESHOLD', null, (SELECT id FROM m_client_risk_rule WHERE order_position = 10), true),
|
||||
('PEP_ACCOUNT_MONITORING', 'Monitoring accounts of PEP (politically exposed person)', 'ALL', 50000, 'THRESHOLD', null, (SELECT id FROM m_client_risk_rule WHERE order_position = 11), true),
|
||||
('NEW_ACCOUNT_LARGE_TXN', 'New account Large transaction', 'ALL', 500000, 'THRESHOLD', 30, (SELECT id FROM m_client_risk_rule WHERE order_position = 14), true),
|
||||
('RAPID_MOVEMENT_IN_ACCOUNTS', 'Rapid Movement in accounts (same-day credit followed by debit)', 'SAVINGS', null, 'VELOCITY', 1, (SELECT id FROM m_client_risk_rule WHERE order_position = 24), true),
|
||||
('LOAN_REPAYMENT_IN_CASH', 'Repayment of loan in cash', 'LOAN', 200000, 'THRESHOLD', null, (SELECT id FROM m_client_risk_rule WHERE order_position = 73), true);
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
<?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
|
||||
for licensing details.
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<!-- Step 1: permissions -->
|
||||
|
||||
<changeSet id="0435_insert_menu_aml_compliance_permission" author="fineract">
|
||||
<sql>
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'heading_menu', 'MENU_AML_COMPLIANCE', 'MENU', 'AML_COMPLIANCE', false
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM m_permission WHERE code = 'MENU_AML_COMPLIANCE'
|
||||
);
|
||||
</sql>
|
||||
<rollback>
|
||||
DELETE FROM m_permission WHERE code = 'MENU_AML_COMPLIANCE';
|
||||
</rollback>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0435_insert_review_aml_alert_permission" author="fineract">
|
||||
<sql>
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'portfolio', 'REVIEW_AML_ALERT', 'AML_ALERT', 'REVIEW', false
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM m_permission WHERE code = 'REVIEW_AML_ALERT'
|
||||
);
|
||||
</sql>
|
||||
<rollback>
|
||||
DELETE FROM m_permission WHERE code = 'REVIEW_AML_ALERT';
|
||||
</rollback>
|
||||
</changeSet>
|
||||
|
||||
<!--
|
||||
First-level AML Officer disposition on an alert: WHITELIST (close as false positive) or
|
||||
ESCALATE (creates the m_aml_str_case row for Principal Officer review).
|
||||
-->
|
||||
<changeSet id="0435_insert_conclude_aml_str_permission" author="fineract">
|
||||
<sql>
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'portfolio', 'CONCLUDE_AML_STR', 'AML_STR_CASE', 'CONCLUDE', false
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM m_permission WHERE code = 'CONCLUDE_AML_STR'
|
||||
);
|
||||
</sql>
|
||||
<rollback>
|
||||
DELETE FROM m_permission WHERE code = 'CONCLUDE_AML_STR';
|
||||
</rollback>
|
||||
</changeSet>
|
||||
|
||||
<!-- Step 2: roles -->
|
||||
|
||||
<changeSet id="0435_create_aml_officer_role" author="fineract">
|
||||
<sql>
|
||||
INSERT INTO m_role (name, description, is_disabled)
|
||||
SELECT 'AML Officer', 'First-level reviewer of AML transaction-monitoring alerts: triages alerts and either whitelists or escalates to the Principal Officer.', false
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM m_role WHERE LOWER(name) = 'aml officer'
|
||||
);
|
||||
</sql>
|
||||
<rollback>
|
||||
DELETE FROM m_role_permission WHERE role_id = (SELECT id FROM m_role WHERE LOWER(name) = 'aml officer');
|
||||
DELETE FROM m_role WHERE LOWER(name) = 'aml officer';
|
||||
</rollback>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0435_create_principal_officer_role" author="fineract">
|
||||
<sql>
|
||||
INSERT INTO m_role (name, description, is_disabled)
|
||||
SELECT 'Principal Officer', 'Statutory PMLA/RBI-designated officer. Concludes escalated AML alerts as suspicious or not, and is the sole authority for STR filing to FIU-IND.', false
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM m_role WHERE LOWER(name) = 'principal officer'
|
||||
);
|
||||
</sql>
|
||||
<rollback>
|
||||
DELETE FROM m_role_permission WHERE role_id = (SELECT id FROM m_role WHERE LOWER(name) = 'principal officer');
|
||||
DELETE FROM m_role WHERE LOWER(name) = 'principal officer';
|
||||
</rollback>
|
||||
</changeSet>
|
||||
|
||||
<!-- Step 3: grant permissions to roles -->
|
||||
|
||||
<changeSet id="0435_grant_permissions_to_aml_officer" author="fineract">
|
||||
<sql>
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM m_role r, m_permission p
|
||||
WHERE LOWER(r.name) = 'aml officer'
|
||||
AND p.code IN ('MENU_AML_COMPLIANCE', 'REVIEW_AML_ALERT')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp
|
||||
WHERE rp.role_id = r.id AND rp.permission_id = p.id
|
||||
);
|
||||
</sql>
|
||||
<rollback>
|
||||
DELETE FROM m_role_permission
|
||||
WHERE role_id = (SELECT id FROM m_role WHERE LOWER(name) = 'aml officer');
|
||||
</rollback>
|
||||
</changeSet>
|
||||
|
||||
<!--
|
||||
Principal Officer also gets REVIEW_AML_ALERT (can perform first-level review directly on
|
||||
single-tier cases) in addition to the CONCLUDE authority that is exclusive to this role.
|
||||
-->
|
||||
<changeSet id="0435_grant_permissions_to_principal_officer" author="fineract">
|
||||
<sql>
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM m_role r, m_permission p
|
||||
WHERE LOWER(r.name) = 'principal officer'
|
||||
AND p.code IN ('MENU_AML_COMPLIANCE', 'REVIEW_AML_ALERT', 'CONCLUDE_AML_STR')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp
|
||||
WHERE rp.role_id = r.id AND rp.permission_id = p.id
|
||||
);
|
||||
</sql>
|
||||
<rollback>
|
||||
DELETE FROM m_role_permission
|
||||
WHERE role_id = (SELECT id FROM m_role WHERE LOWER(name) = 'principal officer');
|
||||
</rollback>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?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">
|
||||
|
||||
<!--
|
||||
Repairs schema drift on m_client_risk_rule: environments where the table
|
||||
was created before 0428_add_client_risk_rules.xml was authored are missing
|
||||
these columns even though 0428's changesets are recorded as already run
|
||||
(so Liquibase will never retry them). Guarded with columnExists so this
|
||||
is a no-op anywhere the columns are already present.
|
||||
-->
|
||||
<changeSet author="fineract" id="0436-1">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_risk_rule" columnName="legal_form_id"/></not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_risk_rule">
|
||||
<column name="legal_form_id" type="INT"/>
|
||||
<column name="occupation_category_cv_id" type="BIGINT"/>
|
||||
<column name="occupation_cv_id" type="BIGINT"/>
|
||||
<column name="min_monthly_income" type="DECIMAL(19,6)"/>
|
||||
<column name="max_monthly_income" type="DECIMAL(19,6)"/>
|
||||
<column name="min_annual_income" type="DECIMAL(19,6)"/>
|
||||
<column name="max_annual_income" type="DECIMAL(19,6)"/>
|
||||
<column name="min_annual_turnover" type="DECIMAL(19,6)"/>
|
||||
<column name="max_annual_turnover" type="DECIMAL(19,6)"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0436-2">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><foreignKeyConstraintExists foreignKeyName="FK_m_client_risk_rule_occupation_category_cv_id"/></not>
|
||||
</preConditions>
|
||||
<addForeignKeyConstraint baseTableName="m_client_risk_rule" baseColumnNames="occupation_category_cv_id"
|
||||
referencedTableName="m_code_value" referencedColumnNames="id"
|
||||
constraintName="FK_m_client_risk_rule_occupation_category_cv_id" />
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="0436-3">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><foreignKeyConstraintExists foreignKeyName="FK_m_client_risk_rule_occupation_cv_id"/></not>
|
||||
</preConditions>
|
||||
<addForeignKeyConstraint baseTableName="m_client_risk_rule" baseColumnNames="occupation_cv_id"
|
||||
referencedTableName="m_code_value" referencedColumnNames="id"
|
||||
constraintName="FK_m_client_risk_rule_occupation_cv_id" />
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user