oss file updated, profitl loss as per the day month and also with date select
This commit is contained in:
+376
-299
@@ -42,8 +42,12 @@ export interface CtrTransaction {
|
||||
declaration: string;
|
||||
/** Customer name resolved from KC1 (UI convenience, not in actual TC2 schema) */
|
||||
customerName?: string;
|
||||
complianceStatus?: 'PENDING' | 'WHITELISTED' | 'SUSPICIOUS';
|
||||
}
|
||||
|
||||
/** Outcome of the Principal Officer's investigation into an STR case */
|
||||
export type PoFinding = 'CLEAN' | 'SUSPICIOUS';
|
||||
|
||||
/** STR record — workflow extension on top of TC2 */
|
||||
export interface StrTransaction extends CtrTransaction {
|
||||
detectedOn: string;
|
||||
@@ -58,6 +62,8 @@ export interface StrTransaction extends CtrTransaction {
|
||||
narrative: string;
|
||||
status: StrStatus;
|
||||
poRemarks?: string;
|
||||
/** Set once the Principal Officer concludes the investigation */
|
||||
poFinding?: PoFinding;
|
||||
}
|
||||
|
||||
/** KC1 — Individual customer card (subset of the 70+ fields, only what UI shows) */
|
||||
@@ -127,7 +133,12 @@ export const FIU_BANK = {
|
||||
cbwtrThreshold: 500000 // CBWTR threshold ₹5 L
|
||||
};
|
||||
|
||||
/** ── Sample CTR transactions (FINnet TC2-style) ── */
|
||||
/**
|
||||
* ── CTR transactions (FINnet TC2-style) ──
|
||||
* Only CASH transactions that breach the RBI/PMLA reporting limit of ₹10,00,000
|
||||
* (single txn, or connected transactions aggregating to ₹10 L in a calendar month)
|
||||
* belong here. Nothing below the threshold and no non-cash instruments.
|
||||
*/
|
||||
export const CTR_TXNS: CtrTransaction[] = [
|
||||
{
|
||||
reportType: 'CTR',
|
||||
@@ -135,423 +146,489 @@ export const CTR_TXNS: CtrTransaction[] = [
|
||||
branchCode: '1',
|
||||
accountNumber: '110001101000838',
|
||||
txnBranchCode: '1',
|
||||
depositOrWithdrawal: 'Withdrawal',
|
||||
depositOrWithdrawal: 'Deposit',
|
||||
txnId: 1,
|
||||
txnDate: '20-01-2026',
|
||||
txnTime: '16:51:28',
|
||||
txnAmount: 1500000,
|
||||
instrumentType: 'Cheque',
|
||||
instrumentId: '24304',
|
||||
txnDate: '05-01-2026',
|
||||
txnTime: '10:22:41',
|
||||
txnAmount: 1450000,
|
||||
instrumentType: 'Cash',
|
||||
instrumentId: 'CASH',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'RAVI KUMAR SHARMA'
|
||||
customerName: 'ANAND KRISHNAN'
|
||||
},
|
||||
{
|
||||
reportType: 'CTR',
|
||||
reportRefNo: 1,
|
||||
branchCode: '1',
|
||||
accountNumber: '110001101000838',
|
||||
txnBranchCode: '1',
|
||||
depositOrWithdrawal: 'Withdrawal',
|
||||
reportRefNo: 2,
|
||||
branchCode: '3',
|
||||
accountNumber: '110001204000712',
|
||||
txnBranchCode: '3',
|
||||
depositOrWithdrawal: 'Deposit',
|
||||
txnId: 2,
|
||||
txnDate: '27-01-2026',
|
||||
txnTime: '16:14:22',
|
||||
txnAmount: 932000,
|
||||
instrumentType: 'Cheque',
|
||||
instrumentId: '24305',
|
||||
txnDate: '09-01-2026',
|
||||
txnTime: '12:47:09',
|
||||
txnAmount: 3200000,
|
||||
instrumentType: 'Cash',
|
||||
instrumentId: 'CASH',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'RAVI KUMAR SHARMA'
|
||||
},
|
||||
{
|
||||
reportType: 'CTR',
|
||||
reportRefNo: 2,
|
||||
branchCode: '1',
|
||||
accountNumber: '110001352000357',
|
||||
txnBranchCode: '1',
|
||||
depositOrWithdrawal: 'Withdrawal',
|
||||
txnId: 3,
|
||||
txnDate: '02-01-2026',
|
||||
txnTime: '15:05:36',
|
||||
txnAmount: 600000,
|
||||
instrumentType: 'Cheque',
|
||||
instrumentId: '100039',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'PRIYA PATEL'
|
||||
},
|
||||
{
|
||||
reportType: 'CTR',
|
||||
reportRefNo: 2,
|
||||
branchCode: '1',
|
||||
accountNumber: '110001352000357',
|
||||
txnBranchCode: '1',
|
||||
depositOrWithdrawal: 'Withdrawal',
|
||||
txnId: 4,
|
||||
txnDate: '07-01-2026',
|
||||
txnTime: '14:14:17',
|
||||
txnAmount: 700000,
|
||||
instrumentType: 'Cheque',
|
||||
instrumentId: '100040',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'PRIYA PATEL'
|
||||
customerName: 'MEERA SUNDARAM'
|
||||
},
|
||||
{
|
||||
reportType: 'CTR',
|
||||
reportRefNo: 3,
|
||||
branchCode: '7',
|
||||
accountNumber: '110001352000420',
|
||||
txnBranchCode: '7',
|
||||
depositOrWithdrawal: 'Deposit',
|
||||
txnId: 5,
|
||||
txnDate: '12-01-2026',
|
||||
txnTime: '11:28:50',
|
||||
txnAmount: 1200000,
|
||||
branchCode: '6',
|
||||
accountNumber: '110001307000455',
|
||||
txnBranchCode: '6',
|
||||
depositOrWithdrawal: 'Withdrawal',
|
||||
txnId: 3,
|
||||
txnDate: '13-01-2026',
|
||||
txnTime: '15:03:55',
|
||||
txnAmount: 1075000,
|
||||
instrumentType: 'Cash',
|
||||
instrumentId: 'CASH',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'VIKRAM REDDY'
|
||||
customerName: 'HARPREET SINGH BEDI'
|
||||
},
|
||||
{
|
||||
reportType: 'CTR',
|
||||
reportRefNo: 4,
|
||||
branchCode: '4',
|
||||
accountNumber: '110001352000511',
|
||||
txnBranchCode: '4',
|
||||
branchCode: '2',
|
||||
accountNumber: '110001408000933',
|
||||
txnBranchCode: '2',
|
||||
depositOrWithdrawal: 'Deposit',
|
||||
txnId: 6,
|
||||
txnDate: '18-01-2026',
|
||||
txnTime: '10:14:05',
|
||||
txnAmount: 2500000,
|
||||
txnId: 4,
|
||||
txnDate: '16-01-2026',
|
||||
txnTime: '11:19:27',
|
||||
txnAmount: 5600000,
|
||||
instrumentType: 'Cash',
|
||||
instrumentId: 'CASH',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'NIRAV SHAH'
|
||||
customerName: 'FAROOQ ABDULLAH KHAN'
|
||||
},
|
||||
{
|
||||
reportType: 'CTR',
|
||||
reportRefNo: 5,
|
||||
branchCode: '11',
|
||||
accountNumber: '110001352000604',
|
||||
txnBranchCode: '11',
|
||||
branchCode: '9',
|
||||
accountNumber: '110001509000268',
|
||||
txnBranchCode: '9',
|
||||
depositOrWithdrawal: 'Withdrawal',
|
||||
txnId: 7,
|
||||
txnDate: '24-01-2026',
|
||||
txnTime: '13:42:11',
|
||||
txnAmount: 1100000,
|
||||
instrumentType: 'Cheque',
|
||||
instrumentId: '88123',
|
||||
nonCustomerRef: 'NCR-001',
|
||||
relationshipFlag: 'Non-Customer',
|
||||
thirdPartyPan: 'AKHPV2244R',
|
||||
declaration: 'NA',
|
||||
customerName: 'KAVITA VERMA'
|
||||
}
|
||||
];
|
||||
|
||||
/** ── Sample STR transactions ── */
|
||||
export const STR_TXNS: StrTransaction[] = [
|
||||
{
|
||||
reportType: 'STR',
|
||||
reportRefNo: 101,
|
||||
branchCode: '1',
|
||||
accountNumber: '110001101000841',
|
||||
txnBranchCode: '1',
|
||||
depositOrWithdrawal: 'Deposit',
|
||||
txnId: 8,
|
||||
txnDate: '15-01-2026',
|
||||
txnTime: '11:32:00',
|
||||
txnAmount: 950000,
|
||||
txnId: 5,
|
||||
txnDate: '20-01-2026',
|
||||
txnTime: '14:35:12',
|
||||
txnAmount: 2850000,
|
||||
instrumentType: 'Cash',
|
||||
instrumentId: 'CASH',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'ARJUN DESHMUKH',
|
||||
detectedOn: '16-01-2026',
|
||||
flaggedBy: 'AML Engine — Rule R-007',
|
||||
riskCategory: 'structuring',
|
||||
customerName: 'DEEPIKA RANGANATHAN'
|
||||
},
|
||||
{
|
||||
reportType: 'CTR',
|
||||
reportRefNo: 6,
|
||||
branchCode: '5',
|
||||
accountNumber: '110001610000841',
|
||||
txnBranchCode: '5',
|
||||
depositOrWithdrawal: 'Deposit',
|
||||
txnId: 6,
|
||||
txnDate: '23-01-2026',
|
||||
txnTime: '09:58:33',
|
||||
txnAmount: 1000000,
|
||||
instrumentType: 'Cash',
|
||||
instrumentId: 'CASH',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'RAJENDRA PRASAD YADAV'
|
||||
},
|
||||
{
|
||||
reportType: 'CTR',
|
||||
reportRefNo: 7,
|
||||
branchCode: '12',
|
||||
accountNumber: '110001711000527',
|
||||
txnBranchCode: '12',
|
||||
depositOrWithdrawal: 'Deposit',
|
||||
txnId: 7,
|
||||
txnDate: '27-01-2026',
|
||||
txnTime: '16:41:08',
|
||||
txnAmount: 4300000,
|
||||
instrumentType: 'Cash',
|
||||
instrumentId: 'CASH',
|
||||
nonCustomerRef: 'NCR-014',
|
||||
relationshipFlag: 'Non-Customer',
|
||||
thirdPartyPan: 'BXKPT7788M',
|
||||
declaration: 'NA',
|
||||
customerName: 'SHABANA TARIQ MIRZA'
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* ── STR cases ──
|
||||
* Raised either by the AML rules engine or by an officer flagging a CTR row as
|
||||
* suspicious. Each case is investigated by the Principal Officer, who concludes
|
||||
* whether the funds are legitimate (CLEAN) or linked to terror financing.
|
||||
*/
|
||||
export const STR_TXNS: StrTransaction[] = [
|
||||
{
|
||||
reportType: 'STR',
|
||||
reportRefNo: 101,
|
||||
branchCode: '8',
|
||||
accountNumber: '110001812000390',
|
||||
txnBranchCode: '8',
|
||||
depositOrWithdrawal: 'Deposit',
|
||||
txnId: 101,
|
||||
txnDate: '11-01-2026',
|
||||
txnTime: '13:08:21',
|
||||
txnAmount: 2750000,
|
||||
instrumentType: 'Cash',
|
||||
instrumentId: 'CASH',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'IMRAN SHEIKH QURESHI',
|
||||
detectedOn: '12-01-2026',
|
||||
flaggedBy: 'AML Engine — Rule R-031',
|
||||
riskCategory: 'sanctions-hit',
|
||||
narrative:
|
||||
'Three sub-₹10L cash deposits within 5 days totalling ₹28.4L; pattern indicative of structuring to avoid CTR threshold.',
|
||||
'Cash deposit of ₹27.5L followed within 48 hours by outward remittances to three beneficiaries whose names partially match the UAPA Schedule IV designated-persons list. Awaiting Principal Officer investigation.',
|
||||
status: 'po-review'
|
||||
},
|
||||
{
|
||||
reportType: 'STR',
|
||||
reportRefNo: 102,
|
||||
branchCode: '7',
|
||||
accountNumber: '110001101000922',
|
||||
txnBranchCode: '7',
|
||||
branchCode: '4',
|
||||
accountNumber: '110001915000174',
|
||||
txnBranchCode: '4',
|
||||
depositOrWithdrawal: 'Deposit',
|
||||
txnId: 9,
|
||||
txnDate: '21-01-2026',
|
||||
txnTime: '14:18:30',
|
||||
txnAmount: 4800000,
|
||||
instrumentType: 'NEFT',
|
||||
instrumentId: 'N1264821',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'LAKSHMI IYER',
|
||||
detectedOn: '22-01-2026',
|
||||
flaggedBy: 'Branch Compliance Officer',
|
||||
riskCategory: 'dormant-revival',
|
||||
narrative:
|
||||
'Account dormant for 18 months suddenly received ₹48L NEFT followed by 4 immediate cash withdrawals of ₹2L each.',
|
||||
status: 'filed',
|
||||
poRemarks: 'STR filed with FIU-IND. Acknowledgement no: STR-2026-9821.'
|
||||
},
|
||||
{
|
||||
reportType: 'STR',
|
||||
reportRefNo: 103,
|
||||
branchCode: '2',
|
||||
accountNumber: '110001302001012',
|
||||
txnBranchCode: '2',
|
||||
depositOrWithdrawal: 'Withdrawal',
|
||||
txnId: 10,
|
||||
txnDate: '23-01-2026',
|
||||
txnTime: '15:42:18',
|
||||
txnAmount: 1800000,
|
||||
instrumentType: 'RTGS',
|
||||
instrumentId: 'R889013',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'SAMEER KAPOOR',
|
||||
detectedOn: '24-01-2026',
|
||||
flaggedBy: 'AML Engine — Rule R-014',
|
||||
riskCategory: 'high-risk-country',
|
||||
narrative:
|
||||
'RTGS transfer to beneficiary in jurisdiction listed under FATF grey list (Cayman Islands intermediary). KYC documentation incomplete.',
|
||||
status: 'draft'
|
||||
},
|
||||
{
|
||||
reportType: 'STR',
|
||||
reportRefNo: 104,
|
||||
branchCode: '13',
|
||||
accountNumber: '110001352001145',
|
||||
txnBranchCode: '13',
|
||||
depositOrWithdrawal: 'Deposit',
|
||||
txnId: 11,
|
||||
txnDate: '08-01-2026',
|
||||
txnTime: '12:55:42',
|
||||
txnAmount: 6500000,
|
||||
txnId: 102,
|
||||
txnDate: '07-01-2026',
|
||||
txnTime: '10:44:52',
|
||||
txnAmount: 1980000,
|
||||
instrumentType: 'Cash',
|
||||
instrumentId: 'CASH',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'MANMOHAN SINGH GILL',
|
||||
detectedOn: '09-01-2026',
|
||||
flaggedBy: 'AML Engine — Rule R-022',
|
||||
customerName: 'GOPAL VENKATARAMAN',
|
||||
detectedOn: '08-01-2026',
|
||||
flaggedBy: 'Branch Compliance Officer',
|
||||
riskCategory: 'unusual-pattern',
|
||||
narrative:
|
||||
'Account avg monthly turnover ₹1.5L; received ₹65L cash in single deposit, source-of-funds documentation pending.',
|
||||
'Declared annual income ₹6L but deposited ₹19.8L cash in a single visit. Customer produced sale deed for ancestral agricultural land.',
|
||||
status: 'draft',
|
||||
poFinding: 'CLEAN',
|
||||
poRemarks:
|
||||
'Source of funds verified against registered sale deed dated 02-01-2026 and buyer bank statement. Legitimate agricultural land sale — no adverse findings. Case closed, no STR filed with FIU-IND.'
|
||||
},
|
||||
{
|
||||
reportType: 'STR',
|
||||
reportRefNo: 103,
|
||||
branchCode: '10',
|
||||
accountNumber: '110002016000628',
|
||||
txnBranchCode: '10',
|
||||
depositOrWithdrawal: 'Withdrawal',
|
||||
txnId: 103,
|
||||
txnDate: '19-01-2026',
|
||||
txnTime: '16:27:40',
|
||||
txnAmount: 3400000,
|
||||
instrumentType: 'Cash',
|
||||
instrumentId: 'CASH',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'ABDUL RAHMAN BAIG',
|
||||
detectedOn: '20-01-2026',
|
||||
flaggedBy: 'AML Engine — Rule R-045',
|
||||
riskCategory: 'high-risk-country',
|
||||
narrative:
|
||||
'Account credited by 14 small inward remittances from a FATF high-risk jurisdiction over 9 days, then emptied via ₹34L cash withdrawal at a border-district branch. Classic terror-funding placement-and-dispersal pattern.',
|
||||
status: 'filed',
|
||||
poFinding: 'SUSPICIOUS',
|
||||
poRemarks:
|
||||
'Investigation confirmed layered inward remittances from a designated high-risk corridor with no commercial rationale. Beneficial owner could not evidence source. STR filed with FIU-IND under PMLA s.12; acknowledgement STR-2026-10457. Account frozen and reported to the Nodal Officer.'
|
||||
},
|
||||
{
|
||||
reportType: 'STR',
|
||||
reportRefNo: 104,
|
||||
branchCode: '1',
|
||||
accountNumber: '110002117000315',
|
||||
txnBranchCode: '1',
|
||||
depositOrWithdrawal: 'Deposit',
|
||||
txnId: 104,
|
||||
txnDate: '24-01-2026',
|
||||
txnTime: '11:52:14',
|
||||
txnAmount: 2300000,
|
||||
instrumentType: 'Cash',
|
||||
instrumentId: 'CASH',
|
||||
nonCustomerRef: '',
|
||||
relationshipFlag: 'Customer',
|
||||
thirdPartyPan: '',
|
||||
declaration: 'NA',
|
||||
customerName: 'SUNITA CHAUDHARY',
|
||||
detectedOn: '25-01-2026',
|
||||
flaggedBy: 'AML Engine — Rule R-018',
|
||||
riskCategory: 'structuring',
|
||||
narrative:
|
||||
'Six cash deposits of ₹3.8L each across four branches on the same day, aggregating ₹23L — deliberate splitting to stay under the ₹10L single-transaction CTR limit.',
|
||||
status: 'po-review'
|
||||
}
|
||||
];
|
||||
|
||||
/** ── KC1 sample individual customers ── */
|
||||
/** ── KC1 individual customer cards (one per CTR report reference) ── */
|
||||
export const KC1_INDIVIDUALS: KycIndividual[] = [
|
||||
{
|
||||
reportRefNo: 1,
|
||||
ucic: '10078',
|
||||
pan: 'AAQPS8560J',
|
||||
ckycNumber: '60011225033445',
|
||||
firstName: 'RAVI',
|
||||
middleName: 'KUMAR',
|
||||
lastName: 'SHARMA',
|
||||
fatherName: 'RAJESH KUMAR SHARMA',
|
||||
motherName: 'SUNITA DEVI',
|
||||
ucic: '30115',
|
||||
pan: 'AFTPK4471B',
|
||||
ckycNumber: '60031115447722',
|
||||
firstName: 'ANAND',
|
||||
middleName: '',
|
||||
lastName: 'KRISHNAN',
|
||||
fatherName: 'SUBRAMANIAN KRISHNAN',
|
||||
motherName: 'VASANTHA KRISHNAN',
|
||||
gender: 'Male',
|
||||
dob: '10-08-1985',
|
||||
mobileNumber: '91-9892011001',
|
||||
email: 'ravi.sharma@example.com',
|
||||
primaryAddress: 'FLAT 402 SUNRISE TOWERS MAROL ANDHERI EAST',
|
||||
state: 'MH',
|
||||
city: 'MUMBAI',
|
||||
pincode: '400069',
|
||||
dob: '14-06-1979',
|
||||
mobileNumber: '91-9840133015',
|
||||
email: 'anand.krishnan@example.com',
|
||||
primaryAddress: 'NO 18 SECOND STREET ADYAR',
|
||||
state: 'TN',
|
||||
city: 'CHENNAI',
|
||||
pincode: '600020',
|
||||
customerType: 'Individual',
|
||||
annualIncome: 750000,
|
||||
occupation: 'Service',
|
||||
onboardingDate: '15-03-2020',
|
||||
lastKycDate: '12-04-2025',
|
||||
annualIncome: 2200000,
|
||||
occupation: 'Business',
|
||||
onboardingDate: '22-07-2016',
|
||||
lastKycDate: '18-06-2025',
|
||||
riskLevel: 'Medium',
|
||||
reportType: 'CTR',
|
||||
otherIdentifierName: 'AADHAAR',
|
||||
otherIdentifierNumber: 'XXXXXXXX1234'
|
||||
otherIdentifierNumber: 'XXXXXXXX4471'
|
||||
},
|
||||
{
|
||||
reportRefNo: 2,
|
||||
ucic: '10079',
|
||||
pan: 'AKLPS9933P',
|
||||
ckycNumber: '60022144558899',
|
||||
firstName: 'PRIYA',
|
||||
ucic: '30228',
|
||||
pan: 'AJSPS6612D',
|
||||
ckycNumber: '60032228661133',
|
||||
firstName: 'MEERA',
|
||||
middleName: '',
|
||||
lastName: 'PATEL',
|
||||
fatherName: 'HARISH PATEL',
|
||||
motherName: 'MEENA PATEL',
|
||||
lastName: 'SUNDARAM',
|
||||
fatherName: 'RAGHAVAN SUNDARAM',
|
||||
motherName: 'KALYANI SUNDARAM',
|
||||
gender: 'Female',
|
||||
dob: '22-03-1990',
|
||||
mobileNumber: '91-9822055002',
|
||||
email: 'priya.patel@example.com',
|
||||
primaryAddress: 'B-12 SHIVAJI NAGAR FC ROAD',
|
||||
state: 'MH',
|
||||
city: 'PUNE',
|
||||
pincode: '411005',
|
||||
dob: '29-11-1986',
|
||||
mobileNumber: '91-9886044028',
|
||||
email: 'meera.sundaram@example.com',
|
||||
primaryAddress: 'FLAT 9C BRIGADE HERITAGE WHITEFIELD',
|
||||
state: 'KA',
|
||||
city: 'BENGALURU',
|
||||
pincode: '560066',
|
||||
customerType: 'Individual',
|
||||
annualIncome: 480000,
|
||||
occupation: 'Self-Employed',
|
||||
onboardingDate: '10-09-2023',
|
||||
lastKycDate: '15-03-2025',
|
||||
riskLevel: 'Low',
|
||||
annualIncome: 3600000,
|
||||
occupation: 'Business',
|
||||
onboardingDate: '11-01-2019',
|
||||
lastKycDate: '05-05-2025',
|
||||
riskLevel: 'High',
|
||||
reportType: 'CTR',
|
||||
otherIdentifierName: 'AADHAAR',
|
||||
otherIdentifierNumber: 'XXXXXXXX2210'
|
||||
otherIdentifierNumber: 'XXXXXXXX6612'
|
||||
},
|
||||
{
|
||||
reportRefNo: 3,
|
||||
ucic: '20101',
|
||||
pan: 'AAEPR9352L',
|
||||
ucic: '30334',
|
||||
pan: 'AMBPB9027F',
|
||||
ckycNumber: '',
|
||||
firstName: 'VIKRAM',
|
||||
middleName: '',
|
||||
lastName: 'REDDY',
|
||||
fatherName: 'SURESH REDDY',
|
||||
motherName: 'LAKSHMI REDDY',
|
||||
firstName: 'HARPREET',
|
||||
middleName: 'SINGH',
|
||||
lastName: 'BEDI',
|
||||
fatherName: 'GURMEET SINGH BEDI',
|
||||
motherName: 'JASWINDER KAUR',
|
||||
gender: 'Male',
|
||||
dob: '07-07-1982',
|
||||
mobileNumber: '91-9845022004',
|
||||
dob: '03-02-1974',
|
||||
mobileNumber: '91-9815066034',
|
||||
email: '',
|
||||
primaryAddress: 'NO 23 4TH CROSS JAYANAGAR 5TH BLOCK',
|
||||
state: 'KA',
|
||||
city: 'BENGALURU',
|
||||
pincode: '560041',
|
||||
primaryAddress: 'HOUSE 221 SECTOR 34A CHANDIGARH ROAD',
|
||||
state: 'PB',
|
||||
city: 'LUDHIANA',
|
||||
pincode: '141001',
|
||||
customerType: 'Individual',
|
||||
annualIncome: 950000,
|
||||
occupation: 'Profession',
|
||||
onboardingDate: '12-11-2021',
|
||||
lastKycDate: '20-02-2025',
|
||||
annualIncome: 1450000,
|
||||
occupation: 'Business',
|
||||
onboardingDate: '30-08-2012',
|
||||
lastKycDate: '14-03-2025',
|
||||
riskLevel: 'Medium',
|
||||
reportType: 'CTR',
|
||||
otherIdentifierName: 'AADHAAR',
|
||||
otherIdentifierNumber: 'XXXXXXXX6622'
|
||||
otherIdentifierNumber: 'XXXXXXXX9027'
|
||||
},
|
||||
{
|
||||
reportRefNo: 4,
|
||||
ucic: '40301',
|
||||
pan: 'AHKPS5544Z',
|
||||
ckycNumber: '60040301229988',
|
||||
firstName: 'NIRAV',
|
||||
middleName: '',
|
||||
lastName: 'SHAH',
|
||||
fatherName: 'JAYESH SHAH',
|
||||
motherName: 'RITA SHAH',
|
||||
ucic: '30447',
|
||||
pan: 'ANQPK3358H',
|
||||
ckycNumber: '60034447335844',
|
||||
firstName: 'FAROOQ',
|
||||
middleName: 'ABDULLAH',
|
||||
lastName: 'KHAN',
|
||||
fatherName: 'ISHTIAQ AHMED KHAN',
|
||||
motherName: 'NASREEN BEGUM',
|
||||
gender: 'Male',
|
||||
dob: '18-05-1992',
|
||||
mobileNumber: '91-9824012010',
|
||||
email: 'nirav.shah@example.com',
|
||||
primaryAddress: 'A-401 ATLANTIS APARTMENTS SATELLITE ROAD',
|
||||
state: 'GJ',
|
||||
city: 'AHMEDABAD',
|
||||
pincode: '380015',
|
||||
dob: '17-09-1981',
|
||||
mobileNumber: '91-9701077047',
|
||||
email: 'farooq.khan@example.com',
|
||||
primaryAddress: '12-2-417 MEHDIPATNAM MAIN ROAD',
|
||||
state: 'TG',
|
||||
city: 'HYDERABAD',
|
||||
pincode: '500028',
|
||||
customerType: 'Individual',
|
||||
annualIncome: 1800000,
|
||||
occupation: 'Service',
|
||||
onboardingDate: '14-02-2018',
|
||||
lastKycDate: '20-12-2024',
|
||||
annualIncome: 890000,
|
||||
occupation: 'Business',
|
||||
onboardingDate: '05-04-2020',
|
||||
lastKycDate: '09-01-2025',
|
||||
riskLevel: 'High',
|
||||
reportType: 'CTR',
|
||||
otherIdentifierName: 'PASSPORT',
|
||||
otherIdentifierNumber: 'M5544321'
|
||||
otherIdentifierNumber: 'P3358214'
|
||||
},
|
||||
{
|
||||
reportRefNo: 5,
|
||||
ucic: '50402',
|
||||
pan: 'AKHPV2244R',
|
||||
ckycNumber: '',
|
||||
firstName: 'KAVITA',
|
||||
ucic: '30552',
|
||||
pan: 'APLPR7783C',
|
||||
ckycNumber: '60035552778355',
|
||||
firstName: 'DEEPIKA',
|
||||
middleName: '',
|
||||
lastName: 'VERMA',
|
||||
fatherName: 'DEEPAK VERMA',
|
||||
motherName: 'USHA VERMA',
|
||||
lastName: 'RANGANATHAN',
|
||||
fatherName: 'MOHAN RANGANATHAN',
|
||||
motherName: 'SHOBHA RANGANATHAN',
|
||||
gender: 'Female',
|
||||
dob: '01-04-1988',
|
||||
mobileNumber: '91-9415055014',
|
||||
email: '',
|
||||
primaryAddress: '45 CIVIL LINES MALL ROAD',
|
||||
state: 'UP',
|
||||
city: 'KANPUR',
|
||||
pincode: '208001',
|
||||
dob: '08-12-1991',
|
||||
mobileNumber: '91-9739088052',
|
||||
email: 'deepika.r@example.com',
|
||||
primaryAddress: 'VILLA 7 PALM MEADOWS SARJAPUR ROAD',
|
||||
state: 'KA',
|
||||
city: 'BENGALURU',
|
||||
pincode: '560035',
|
||||
customerType: 'Individual',
|
||||
annualIncome: 540000,
|
||||
occupation: 'Service',
|
||||
onboardingDate: '08-09-2022',
|
||||
lastKycDate: '15-09-2024',
|
||||
annualIncome: 2750000,
|
||||
occupation: 'Profession',
|
||||
onboardingDate: '19-06-2021',
|
||||
lastKycDate: '27-07-2025',
|
||||
riskLevel: 'Low',
|
||||
reportType: 'CTR',
|
||||
otherIdentifierName: 'AADHAAR',
|
||||
otherIdentifierNumber: 'XXXXXXXX2244'
|
||||
otherIdentifierNumber: 'XXXXXXXX7783'
|
||||
},
|
||||
{
|
||||
reportRefNo: 6,
|
||||
ucic: '30669',
|
||||
pan: 'AQWPY2194K',
|
||||
ckycNumber: '',
|
||||
firstName: 'RAJENDRA',
|
||||
middleName: 'PRASAD',
|
||||
lastName: 'YADAV',
|
||||
fatherName: 'BHOLA NATH YADAV',
|
||||
motherName: 'KAMLA DEVI',
|
||||
gender: 'Male',
|
||||
dob: '25-05-1968',
|
||||
mobileNumber: '91-9451099069',
|
||||
email: '',
|
||||
primaryAddress: '78 GOMTI NAGAR VIBHUTI KHAND',
|
||||
state: 'UP',
|
||||
city: 'LUCKNOW',
|
||||
pincode: '226010',
|
||||
customerType: 'Individual',
|
||||
annualIncome: 640000,
|
||||
occupation: 'Agriculture',
|
||||
onboardingDate: '17-10-2014',
|
||||
lastKycDate: '22-11-2024',
|
||||
riskLevel: 'Low',
|
||||
reportType: 'CTR',
|
||||
otherIdentifierName: 'AADHAAR',
|
||||
otherIdentifierNumber: 'XXXXXXXX2194'
|
||||
},
|
||||
{
|
||||
reportRefNo: 7,
|
||||
ucic: '30774',
|
||||
pan: 'BXKPT7788M',
|
||||
ckycNumber: '',
|
||||
firstName: 'SHABANA',
|
||||
middleName: 'TARIQ',
|
||||
lastName: 'MIRZA',
|
||||
fatherName: 'TARIQ HUSSAIN MIRZA',
|
||||
motherName: 'RUKHSANA MIRZA',
|
||||
gender: 'Female',
|
||||
dob: '11-03-1989',
|
||||
mobileNumber: '91-9958100074',
|
||||
email: '',
|
||||
primaryAddress: '54 ZAKIR NAGAR OKHLA',
|
||||
state: 'DL',
|
||||
city: 'NEW DELHI',
|
||||
pincode: '110025',
|
||||
customerType: 'Individual',
|
||||
annualIncome: 720000,
|
||||
occupation: 'Self-Employed',
|
||||
onboardingDate: '02-02-2023',
|
||||
lastKycDate: '16-08-2025',
|
||||
riskLevel: 'High',
|
||||
reportType: 'CTR',
|
||||
otherIdentifierName: 'AADHAAR',
|
||||
otherIdentifierNumber: 'XXXXXXXX7788'
|
||||
}
|
||||
];
|
||||
|
||||
/** ── KC2 sample entity customers ── */
|
||||
/** ── KC2 entity customer cards ── */
|
||||
export const KC2_ENTITIES: KycEntity[] = [
|
||||
{
|
||||
reportRefNo: 11,
|
||||
ucic: 'E10101',
|
||||
entityName: 'BHARAT TRADING COMPANY',
|
||||
panNumber: 'ABCFB1234N',
|
||||
gstin: '27ABCFB1234N1ZT',
|
||||
mobileNumber: '91-9892011010',
|
||||
address: 'GALA 22 LOWER PAREL',
|
||||
state: 'MH',
|
||||
city: 'MUMBAI',
|
||||
pincode: '400013',
|
||||
dateOfIncorporation: '24-03-2008',
|
||||
reportRefNo: 2,
|
||||
ucic: 'E30228',
|
||||
entityName: 'SUNDARAM BULLION TRADERS',
|
||||
panNumber: 'AAGFS3391Q',
|
||||
gstin: '29AAGFS3391Q1ZP',
|
||||
mobileNumber: '91-9886044028',
|
||||
address: 'SHOP 14 COMMERCIAL STREET SHIVAJI NAGAR',
|
||||
state: 'KA',
|
||||
city: 'BENGALURU',
|
||||
pincode: '560001',
|
||||
dateOfIncorporation: '11-01-2019',
|
||||
customerType: 'Sole Proprietor',
|
||||
riskLevel: 'High',
|
||||
onboardingDate: '24-03-2008',
|
||||
lastKycDate: '03-09-2025',
|
||||
onboardingDate: '11-01-2019',
|
||||
lastKycDate: '05-05-2025',
|
||||
isSoleProprietor: true,
|
||||
ownerUcic: '10078',
|
||||
ownerName: 'RAVI KUMAR SHARMA',
|
||||
ownerUcic: '30228',
|
||||
ownerName: 'MEERA SUNDARAM',
|
||||
reportType: 'CTR'
|
||||
},
|
||||
{
|
||||
reportRefNo: 12,
|
||||
ucic: 'E10102',
|
||||
entityName: 'NEXTGEN TECH PVT LTD',
|
||||
panNumber: 'AABCN5566K',
|
||||
gstin: '27AABCN5566K1Z2',
|
||||
mobileNumber: '91-9824012011',
|
||||
address: 'TOWER B SUNRISE BUSINESS PARK ANDHERI EAST',
|
||||
state: 'MH',
|
||||
city: 'MUMBAI',
|
||||
pincode: '400093',
|
||||
dateOfIncorporation: '18-09-2015',
|
||||
reportRefNo: 4,
|
||||
ucic: 'E30447',
|
||||
entityName: 'DECCAN SCRAP & METALS PVT LTD',
|
||||
panNumber: 'AABCD8842R',
|
||||
gstin: '36AABCD8842R1ZN',
|
||||
mobileNumber: '91-9701077047',
|
||||
address: 'PLOT 61 IDA JEEDIMETLA PHASE II',
|
||||
state: 'TG',
|
||||
city: 'HYDERABAD',
|
||||
pincode: '500055',
|
||||
dateOfIncorporation: '05-04-2020',
|
||||
customerType: 'Private Limited',
|
||||
riskLevel: 'Medium',
|
||||
onboardingDate: '18-09-2015',
|
||||
lastKycDate: '12-08-2025',
|
||||
riskLevel: 'High',
|
||||
onboardingDate: '05-04-2020',
|
||||
lastKycDate: '09-01-2025',
|
||||
isSoleProprietor: false,
|
||||
ownerUcic: '',
|
||||
ownerName: '',
|
||||
|
||||
+273
-26
@@ -71,17 +71,19 @@
|
||||
<div class="fiu-kpi fiu-kpi--amber">
|
||||
<div class="fiu-kpi-ic"><fa-icon icon="exclamation-triangle"></fa-icon></div>
|
||||
<div>
|
||||
<div class="fiu-kpi-n">{{ pendingStr | number }}</div>
|
||||
<div class="fiu-kpi-n">{{ liveStrPending | number }}</div>
|
||||
<div class="fiu-kpi-l">STR Pending</div>
|
||||
<div class="fiu-kpi-sub">draft / PO review</div>
|
||||
<div class="fiu-kpi-sub">
|
||||
{{ liveStrOpen }} to investigate · {{ liveStrAwaitingFiling }} awaiting filing
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fiu-kpi fiu-kpi--green">
|
||||
<div class="fiu-kpi-ic"><fa-icon icon="check-circle"></fa-icon></div>
|
||||
<div>
|
||||
<div class="fiu-kpi-n">{{ filedStr | number }}</div>
|
||||
<div class="fiu-kpi-n">{{ liveStrFiled | number }}</div>
|
||||
<div class="fiu-kpi-l">STR Filed</div>
|
||||
<div class="fiu-kpi-sub">acknowledged by FIU</div>
|
||||
<div class="fiu-kpi-sub">{{ liveStrCleared }} concluded clear</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fiu-kpi fiu-kpi--purple">
|
||||
@@ -112,7 +114,12 @@
|
||||
</div>
|
||||
<div class="dc-meta">Filing window: <strong>within 7 days of detection</strong></div>
|
||||
<div class="dc-meta">
|
||||
Pending: <strong>{{ pendingStr }}</strong> · Filed: <strong>{{ filedStr }}</strong>
|
||||
To investigate: <strong>{{ liveStrOpen }}</strong> · Awaiting filing:
|
||||
<strong>{{ liveStrAwaitingFiling }}</strong>
|
||||
</div>
|
||||
<div class="dc-meta">
|
||||
Filed: <strong>{{ liveStrFiled }}</strong> · Concluded clear:
|
||||
<strong>{{ liveStrCleared }}</strong>
|
||||
</div>
|
||||
<button class="dc-btn" (click)="showTab('str')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
|
||||
</div>
|
||||
@@ -129,12 +136,12 @@
|
||||
<!-- CTR -->
|
||||
@if (activeTab === 'ctr') {
|
||||
<div class="fiu-section">
|
||||
<!-- LIVE: cash-related AML alerts awaiting AML Officer review -->
|
||||
<!-- LIVE: cash-related CTR threshold alerts -->
|
||||
<div class="fiu-form-meta">
|
||||
<div class="fiu-form-title">
|
||||
<h3>Live Cash Alerts</h3>
|
||||
<h3>Live CTR Threshold Alerts</h3>
|
||||
<p>
|
||||
Raised by the transaction-monitoring rule engine — review and whitelist or escalate to Principal Officer
|
||||
Mechanical cash transaction threshold breaches — review and whitelist or escalate to Principal Officer
|
||||
</p>
|
||||
</div>
|
||||
<select class="fiu-inp" [(ngModel)]="amlAlertStatusFilter" (ngModelChange)="loadAmlAlerts()">
|
||||
@@ -156,12 +163,13 @@
|
||||
<th class="ta-r">Amount</th>
|
||||
<th>Raised On</th>
|
||||
<th>Status</th>
|
||||
<th class="ta-c">Routes To</th>
|
||||
<th>Notes</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (alert of amlAlerts; track alert.id) {
|
||||
@for (alert of ctrAlerts; track alert.id) {
|
||||
<tr>
|
||||
<td>{{ alert.id }}</td>
|
||||
<td>{{ alert.clientDisplayName }}</td>
|
||||
@@ -172,6 +180,9 @@
|
||||
<td>
|
||||
<span [class]="amlStatusClass(alert.status)">{{ alert.status }}</span>
|
||||
</td>
|
||||
<td class="ta-c">
|
||||
<span [class]="alertRouteClass(alert.status)">{{ alertRouteLabel(alert.status) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
@if (alert.status === 'RAISED' || alert.status === 'UNDER_REVIEW') {
|
||||
<input
|
||||
@@ -195,12 +206,17 @@
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td colspan="9" class="fiu-empty">No live alerts.</td>
|
||||
<td colspan="10" class="fiu-empty">No live alerts.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="fiu-tbl-legend">
|
||||
<span class="report-pill report-pending">Pending</span> awaiting Whitelist/Escalate decision ·
|
||||
<span class="report-pill report-ctr">CTR only</span> whitelisted — cleared, no STR needed ·
|
||||
<span class="report-pill report-str">→ STR</span> escalated to Principal Officer as an STR case
|
||||
</p>
|
||||
|
||||
<!-- LIVE: monthly CTR reports (mechanical, threshold-based) -->
|
||||
<div class="fiu-form-meta">
|
||||
@@ -295,6 +311,8 @@
|
||||
<th>Instrument</th>
|
||||
<th>Branch</th>
|
||||
<th class="ta-c">Relationship</th>
|
||||
<th class="ta-c">Filed Under</th>
|
||||
<th>Compliance Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -327,21 +345,131 @@
|
||||
t.relationshipFlag
|
||||
}}</span>
|
||||
</td>
|
||||
<td class="ta-c">
|
||||
<span class="report-pill report-ctr" title="Always included in the monthly CTR / TC2 file">CTR</span>
|
||||
@if (t.complianceStatus === 'SUSPICIOUS') {
|
||||
<span class="report-pill report-str" title="Also escalated to an STR case for Principal Officer review">+ STR</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (!t.complianceStatus || t.complianceStatus === 'PENDING') {
|
||||
<div class="fiu-action-group" style="gap: 4px;">
|
||||
<button class="fiu-btn" style="padding: 4px 8px; font-size: 11px; color: var(--f-green); border-color: var(--f-green); background: var(--f-green-l);" (click)="whitelistCtrTxn(t)">
|
||||
Whitelist
|
||||
</button>
|
||||
<button class="fiu-btn" style="padding: 4px 8px; font-size: 11px; color: var(--f-red); border-color: var(--f-red); background: var(--f-red-l);" (click)="flagCtrTxnSuspicious(t)">
|
||||
Flag STR
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<span [class]="t.complianceStatus === 'WHITELISTED' ? 'status-pill status-whitelisted' : 'status-pill status-escalated'">
|
||||
{{ t.complianceStatus }}
|
||||
</span>
|
||||
@if (t.complianceStatus === 'WHITELISTED') {
|
||||
<div class="muted" style="margin-top: 3px;">Stays in CTR only</div>
|
||||
} @else {
|
||||
<button class="fiu-link-btn" (click)="openStrCase(t)">View in STR tab →</button>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td colspan="9" class="fiu-empty">No transactions match the filters.</td>
|
||||
<td colspan="11" class="fiu-empty">No transactions match the filters.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="fiu-tbl-legend">
|
||||
<span class="report-pill report-ctr">CTR</span> every cash txn ≥ threshold stays in the monthly CTR / TC2
|
||||
file ·
|
||||
<span class="report-pill report-str">+ STR</span> also flagged as suspicious and escalated to a separate STR
|
||||
case (see the STR tab)
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- STR -->
|
||||
@if (activeTab === 'str') {
|
||||
<div class="fiu-section">
|
||||
<!-- LIVE: suspicious AML alerts awaiting AML Officer review (Structuring, PEP, etc.) -->
|
||||
<div class="fiu-form-meta">
|
||||
<div class="fiu-form-title">
|
||||
<h3>Live AML Alerts (Awaiting Triage)</h3>
|
||||
<p>
|
||||
Flagged by the monitoring engine for suspicious behavioral patterns — triage and whitelist or escalate
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fiu-tbl-wrap">
|
||||
<table class="fiu-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Client</th>
|
||||
<th>Type</th>
|
||||
<th>Rule</th>
|
||||
<th class="ta-r">Amount</th>
|
||||
<th>Raised On</th>
|
||||
<th>Status</th>
|
||||
<th class="ta-c">Routes To</th>
|
||||
<th>Notes</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (alert of amlSuspiciousAlerts; track alert.id) {
|
||||
<tr>
|
||||
<td>{{ alert.id }}</td>
|
||||
<td>{{ alert.clientDisplayName }}</td>
|
||||
<td>{{ alert.transactionType }}</td>
|
||||
<td>{{ alert.ruleCode }} — {{ alert.ruleDescription }}</td>
|
||||
<td class="ta-r mono">₹{{ alert.amount | number: '1.0-0' }}</td>
|
||||
<td>{{ alert.raisedOn | date: 'medium' }}</td>
|
||||
<td>
|
||||
<span [class]="amlStatusClass(alert.status)">{{ alert.status }}</span>
|
||||
</td>
|
||||
<td class="ta-c">
|
||||
<span [class]="alertRouteClass(alert.status, false)">{{ alertRouteLabel(alert.status, false) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
@if (alert.status === 'RAISED' || alert.status === 'UNDER_REVIEW') {
|
||||
<input
|
||||
type="text"
|
||||
class="fiu-inp"
|
||||
placeholder="Review notes"
|
||||
[(ngModel)]="amlReviewNotes[alert.id]"
|
||||
/>
|
||||
} @else {
|
||||
<span class="muted">{{ alert.reviewNotes }}</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (alert.status === 'RAISED' || alert.status === 'UNDER_REVIEW') {
|
||||
<div class="fiu-action-group" *mifosxHasPermission="'REVIEW_AML_ALERT'">
|
||||
<button class="fiu-btn fiu-btn-g" (click)="whitelistAlert(alert)">Whitelist</button>
|
||||
<button class="fiu-btn fiu-btn-xml" (click)="escalateAlert(alert)">Escalate</button>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td colspan="10" class="fiu-empty">No suspicious behavioral alerts.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="fiu-tbl-legend">
|
||||
<span class="report-pill report-pending">Pending</span> awaiting Whitelist/Escalate decision ·
|
||||
<span class="report-pill report-cleared">Cleared</span> whitelisted — no report filed ·
|
||||
<span class="report-pill report-str">→ STR</span> escalated to Principal Officer as an STR case
|
||||
</p>
|
||||
|
||||
<hr style="border: 0; border-top: 1px solid var(--f-g200); margin: 8px 0;" />
|
||||
|
||||
<!-- LIVE: STR cases with full Principal Officer actions -->
|
||||
<div class="fiu-form-meta">
|
||||
<div class="fiu-form-title">
|
||||
@@ -407,6 +535,17 @@
|
||||
<button class="fiu-btn fiu-btn-g" (click)="closeStrCaseDetail()">Close</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="case-outcome"
|
||||
[class.case-outcome--clear]="selectedStrCase.status === 'CONCLUDED_CLEAR'"
|
||||
[class.case-outcome--suspicious]="
|
||||
selectedStrCase.status === 'CONCLUDED_SUSPICIOUS' || selectedStrCase.status === 'FILED'
|
||||
"
|
||||
>
|
||||
<span [class]="amlStatusClass(selectedStrCase.status)">{{ selectedStrCase.status }}</span>
|
||||
<span>{{ strCaseOutcome(selectedStrCase) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="case-form" *mifosxHasPermission="'CONCLUDE_AML_STR'">
|
||||
<div class="form-row">
|
||||
<label for="fiuInvestigationNotes">Investigation notes (source-of-transaction history reviewed)</label>
|
||||
@@ -416,17 +555,96 @@
|
||||
<label for="fiuSourceOfFundsNotes">Source-of-funds notes</label>
|
||||
<textarea id="fiuSourceOfFundsNotes" rows="2" [(ngModel)]="concludeSourceOfFundsNotes"></textarea>
|
||||
</div>
|
||||
<div class="form-row conclusion-row">
|
||||
<label>
|
||||
<input type="radio" name="fiuConclusion" value="NOT_SUSPICIOUS" [(ngModel)]="concludeConclusion" />
|
||||
Not suspicious (whitelist)
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="fiuConclusion" value="SUSPICIOUS" [(ngModel)]="concludeConclusion" />
|
||||
Suspicious (file STR)
|
||||
</label>
|
||||
<!-- Verification checklist — the verdict is derived, not chosen. -->
|
||||
<div class="checklist">
|
||||
<div class="checklist-head">
|
||||
<span>Principal Officer verification — mark each point as found</span>
|
||||
<span class="checklist-progress" [class.done]="allAnswered">
|
||||
{{ answeredCount }} / {{ strChecklist.length }} answered
|
||||
</span>
|
||||
</div>
|
||||
<table class="checklist-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Verification point</th>
|
||||
<th class="ta-c col-yes">Genuine</th>
|
||||
<th class="ta-c col-no">Not satisfied</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (item of strChecklist; track item.key) {
|
||||
<tr
|
||||
[class.row-yes]="strFindings[item.key] === true"
|
||||
[class.row-no]="strFindings[item.key] === false"
|
||||
[class.row-pending]="strFindings[item.key] === null"
|
||||
>
|
||||
<td>
|
||||
<div class="chk-label">{{ item.label }}</div>
|
||||
<div class="chk-hint">{{ item.hint }}</div>
|
||||
</td>
|
||||
<td class="ta-c">
|
||||
<button
|
||||
type="button"
|
||||
class="chk-box chk-box--yes"
|
||||
[class.on]="strFindings[item.key] === true"
|
||||
[attr.aria-pressed]="strFindings[item.key] === true"
|
||||
[attr.aria-label]="'Mark ' + item.label + ' as genuine'"
|
||||
(click)="setFinding(item.key, true)"
|
||||
>
|
||||
<fa-icon icon="check-circle"></fa-icon>
|
||||
</button>
|
||||
</td>
|
||||
<td class="ta-c">
|
||||
<button
|
||||
type="button"
|
||||
class="chk-box chk-box--no"
|
||||
[class.on]="strFindings[item.key] === false"
|
||||
[attr.aria-pressed]="strFindings[item.key] === false"
|
||||
[attr.aria-label]="'Mark ' + item.label + ' as not satisfied'"
|
||||
(click)="setFinding(item.key, false)"
|
||||
>
|
||||
<fa-icon icon="exclamation-triangle"></fa-icon>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button class="fiu-btn fiu-btn-xml" (click)="concludeStrCase()">Conclude</button>
|
||||
|
||||
<!-- Derived verdict -->
|
||||
<div
|
||||
class="verdict"
|
||||
[class.verdict--clear]="allAnswered && derivedConclusion === 'NOT_SUSPICIOUS'"
|
||||
[class.verdict--suspicious]="allAnswered && derivedConclusion === 'SUSPICIOUS'"
|
||||
>
|
||||
@if (!allAnswered) {
|
||||
<fa-icon icon="info-circle"></fa-icon>
|
||||
<span>
|
||||
Answer every verification point to determine the outcome —
|
||||
{{ strChecklist.length - answeredCount }} still pending.
|
||||
</span>
|
||||
} @else if (derivedConclusion === 'NOT_SUSPICIOUS') {
|
||||
<fa-icon icon="check-circle"></fa-icon>
|
||||
<span>
|
||||
<strong>Not suspicious.</strong> All {{ strChecklist.length }} points found genuine — the case will
|
||||
be closed and nothing filed with FIU-IND.
|
||||
</span>
|
||||
} @else {
|
||||
<fa-icon icon="exclamation-triangle"></fa-icon>
|
||||
<span>
|
||||
<strong>Suspicious — STR must be filed.</strong>
|
||||
{{ failedFindings.length }} of {{ strChecklist.length }} points not satisfied:
|
||||
{{ failedFindings[0].label }}@if (failedFindings.length > 1) {
|
||||
<span> and {{ failedFindings.length - 1 }} more</span>
|
||||
}.
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<button class="fiu-btn fiu-btn-xml" [disabled]="!allAnswered" (click)="concludeStrCase()">
|
||||
Conclude as {{ allAnswered ? (derivedConclusion === 'SUSPICIOUS' ? 'Suspicious' : 'Not Suspicious') : '…' }}
|
||||
</button>
|
||||
|
||||
@if (selectedStrCase.status === 'CONCLUDED_SUSPICIOUS') {
|
||||
<div class="file-str-row">
|
||||
@@ -478,7 +696,12 @@
|
||||
|
||||
<div class="fiu-str-list">
|
||||
@for (s of filteredStr; track s.reportRefNo) {
|
||||
<div class="str-card" [class.str-card--filed]="s.status === 'filed'">
|
||||
<div
|
||||
class="str-card"
|
||||
[id]="'str-card-' + s.reportRefNo"
|
||||
[class.str-card--filed]="s.status === 'filed'"
|
||||
[class.str-card--highlight]="highlightedStrRef === s.reportRefNo"
|
||||
>
|
||||
<div class="str-card-h">
|
||||
<div class="str-card-id">
|
||||
<span class="str-ref">STR-{{ s.reportRefNo }}</span>
|
||||
@@ -521,6 +744,27 @@
|
||||
<p>{{ s.poRemarks }}</p>
|
||||
</div>
|
||||
}
|
||||
@if (s.poFinding) {
|
||||
<div class="str-finding" [class.str-finding--suspicious]="s.poFinding === 'SUSPICIOUS'">
|
||||
<label>
|
||||
<fa-icon [icon]="s.poFinding === 'CLEAN' ? 'check-circle' : 'exclamation-triangle'"></fa-icon>
|
||||
Principal Officer Finding
|
||||
</label>
|
||||
<p>{{ poFindingLabel(s.poFinding) }}</p>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="str-po-action" *mifosxHasPermission="'CONCLUDE_AML_STR'">
|
||||
<label>Principal Officer investigation — is the source of funds legitimate?</label>
|
||||
<div class="fiu-action-group">
|
||||
<button class="fiu-btn fiu-btn-clean" (click)="concludeLocalStr(s, 'CLEAN')">
|
||||
Clean — close case
|
||||
</button>
|
||||
<button class="fiu-btn fiu-btn-xml" (click)="concludeLocalStr(s, 'SUSPICIOUS')">
|
||||
Suspicious — file STR with FIU-IND
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
} @empty {
|
||||
@@ -626,10 +870,13 @@
|
||||
<div class="fiu-form-meta">
|
||||
<div class="fiu-form-title">
|
||||
<h3>FINnet 2.0 Submission Files</h3>
|
||||
<p>6 CSV files in FIU-IND prescribed schema, plus STR XML</p>
|
||||
<p>
|
||||
6 CSV files in FIU-IND prescribed schema, plus STR XML. Counts reflect any filters set on the CTR and
|
||||
STR tabs, so every file in the bundle covers the same transactions.
|
||||
</p>
|
||||
</div>
|
||||
<button class="fiu-btn fiu-btn-xls" (click)="exportAll()">
|
||||
<fa-icon icon="file-csv"></fa-icon> Download All
|
||||
<fa-icon icon="file-csv"></fa-icon> Download All 7
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -641,7 +888,7 @@
|
||||
</div>
|
||||
<div class="file-card">
|
||||
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>TC2.csv</div>
|
||||
<p>Branch / cash transactions ({{ ctrTxns.length }} rows)</p>
|
||||
<p>Branch / cash transactions ({{ filteredCtr.length }} rows)</p>
|
||||
<button class="fiu-btn fiu-btn-csv" (click)="exportTc2()">
|
||||
<fa-icon icon="file-csv"></fa-icon> Download
|
||||
</button>
|
||||
@@ -676,7 +923,7 @@
|
||||
</div>
|
||||
<div class="file-card file-card--xml">
|
||||
<div class="file-card-h"><fa-icon icon="file-alt"></fa-icon>STR.xml</div>
|
||||
<p>Suspicious Transaction Report ({{ filedStr + pendingStr }} suspects)</p>
|
||||
<p>Suspicious Transaction Report ({{ strSuspects.length }} suspects — cases concluded clean are excluded)</p>
|
||||
<button class="fiu-btn fiu-btn-xml" (click)="exportStrXml()">
|
||||
<fa-icon icon="file-alt"></fa-icon> Download XML
|
||||
</button>
|
||||
|
||||
@@ -386,6 +386,100 @@
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
.fiu-link-btn {
|
||||
display: inline-block;
|
||||
margin-top: 3px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
font-family: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--f-red);
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: #9a2f22;
|
||||
}
|
||||
|
||||
&--muted {
|
||||
color: var(--f-g500);
|
||||
|
||||
&:hover {
|
||||
color: var(--f-red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* PRINCIPAL OFFICER INVESTIGATION */
|
||||
.str-po-action {
|
||||
background: var(--f-amber-l);
|
||||
border: 1px dashed #fcd34d;
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
> label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: #92400e;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
}
|
||||
|
||||
.fiu-btn-clean {
|
||||
background: var(--f-green);
|
||||
border-color: var(--f-green);
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background: #166534;
|
||||
border-color: #166534;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.str-finding {
|
||||
background: var(--f-green-l);
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
|
||||
label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: var(--f-green);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #14532d;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
&--suspicious {
|
||||
background: var(--f-red-l);
|
||||
|
||||
label {
|
||||
color: var(--f-red);
|
||||
}
|
||||
|
||||
p {
|
||||
color: #7f1d1d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* INPUTS */
|
||||
.fiu-inp {
|
||||
width: 100%;
|
||||
@@ -538,6 +632,50 @@
|
||||
}
|
||||
}
|
||||
|
||||
.report-pill {
|
||||
display: inline-block;
|
||||
padding: 2px 9px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
|
||||
&.report-ctr {
|
||||
background: var(--f-blue-l);
|
||||
color: var(--f-blue);
|
||||
}
|
||||
|
||||
&.report-str {
|
||||
background: var(--f-red-l);
|
||||
color: var(--f-red);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
&.report-pending {
|
||||
background: var(--f-amber-l);
|
||||
color: var(--f-amber);
|
||||
}
|
||||
|
||||
&.report-cleared {
|
||||
background: var(--f-green-l);
|
||||
color: var(--f-green);
|
||||
}
|
||||
}
|
||||
|
||||
.fiu-tbl-legend {
|
||||
margin: -6px 2px 0;
|
||||
font-size: 11px;
|
||||
color: var(--f-g500);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.report-pill {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.rel-pill {
|
||||
display: inline-block;
|
||||
padding: 2px 9px;
|
||||
@@ -697,6 +835,11 @@
|
||||
&--filed {
|
||||
border-color: #86efac;
|
||||
}
|
||||
|
||||
&--highlight {
|
||||
border-color: var(--f-red);
|
||||
box-shadow: 0 0 0 3px rgb(192 57 43 / 20%);
|
||||
}
|
||||
}
|
||||
|
||||
.str-card-h {
|
||||
@@ -956,6 +1099,29 @@
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.case-outcome {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin: 12px 0;
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
background: var(--f-g100);
|
||||
font-size: 13px;
|
||||
color: var(--f-g700);
|
||||
|
||||
&--clear {
|
||||
background: var(--f-green-l);
|
||||
color: #14532d;
|
||||
}
|
||||
|
||||
&--suspicious {
|
||||
background: var(--f-red-l);
|
||||
color: #7f1d1d;
|
||||
}
|
||||
}
|
||||
|
||||
.case-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -995,6 +1161,181 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Principal Officer verification checklist ───────────────────────────── */
|
||||
.checklist {
|
||||
border: 1px solid var(--f-g200);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.checklist-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 14px;
|
||||
background: var(--f-g50);
|
||||
border-bottom: 1px solid var(--f-g200);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: var(--f-g700);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.checklist-progress {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
background: var(--f-amber-l);
|
||||
color: var(--f-amber);
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
|
||||
&.done {
|
||||
background: var(--f-green-l);
|
||||
color: var(--f-green);
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-tbl {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th {
|
||||
padding: 8px 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: var(--f-g500);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
background: var(--f-g50);
|
||||
border-bottom: 1px solid var(--f-g200);
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
th.col-yes {
|
||||
color: var(--f-green);
|
||||
width: 96px;
|
||||
}
|
||||
|
||||
th.col-no {
|
||||
color: var(--f-red);
|
||||
width: 112px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 9px 12px;
|
||||
border-bottom: 1px solid var(--f-g100);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Answered rows tint so an unresolved finding is obvious at a glance. */
|
||||
.row-yes td {
|
||||
background: rgb(220 252 231 / 45%);
|
||||
}
|
||||
|
||||
.row-no td {
|
||||
background: rgb(253 236 234 / 60%);
|
||||
}
|
||||
|
||||
.row-pending td {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.chk-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--f-g900);
|
||||
}
|
||||
|
||||
.chk-hint {
|
||||
font-size: 11px;
|
||||
color: var(--f-g500);
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* Two-sided toggle: one box on the genuine side, one on the not-satisfied side. */
|
||||
.chk-box {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 7px;
|
||||
border: 2px solid var(--f-g300);
|
||||
background: #fff;
|
||||
color: var(--f-g300);
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
&--yes:hover {
|
||||
border-color: var(--f-green);
|
||||
color: var(--f-green);
|
||||
}
|
||||
|
||||
&--no:hover {
|
||||
border-color: var(--f-red);
|
||||
color: var(--f-red);
|
||||
}
|
||||
|
||||
&--yes.on {
|
||||
border-color: var(--f-green);
|
||||
background: var(--f-green);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&--no.on {
|
||||
border-color: var(--f-red);
|
||||
background: var(--f-red);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
/* Derived verdict banner */
|
||||
.verdict {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 11px 15px;
|
||||
border-radius: 9px;
|
||||
font-size: 13px;
|
||||
background: var(--f-g100);
|
||||
border: 1px solid var(--f-g200);
|
||||
color: var(--f-g700);
|
||||
|
||||
&--clear {
|
||||
background: var(--f-green-l);
|
||||
border-color: #86efac;
|
||||
color: #14532d;
|
||||
}
|
||||
|
||||
&--suspicious {
|
||||
background: var(--f-red-l);
|
||||
border-color: #fca5a5;
|
||||
color: #7f1d1d;
|
||||
}
|
||||
}
|
||||
|
||||
.case-form .fiu-btn-xml:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.file-str-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
+344
-30
@@ -20,6 +20,7 @@ import {
|
||||
KC2_ENTITIES,
|
||||
KycEntity,
|
||||
KycIndividual,
|
||||
PoFinding,
|
||||
StrStatus,
|
||||
StrTransaction,
|
||||
STR_TXNS
|
||||
@@ -36,6 +37,9 @@ import {
|
||||
]
|
||||
})
|
||||
export class FiuComponent implements OnInit {
|
||||
/** Bump when CTR_TXNS / STR_TXNS change so cached rows are re-seeded. */
|
||||
private static readonly SEED_VERSION = '2026-01-cash-threshold';
|
||||
|
||||
private amlService = inject(AmlService);
|
||||
private authenticationService = inject(AuthenticationService);
|
||||
/** Active tab */
|
||||
@@ -56,6 +60,9 @@ export class FiuComponent implements OnInit {
|
||||
strFilterStatus: '' | StrStatus = '';
|
||||
strFilterRisk: string = '';
|
||||
|
||||
/** Ref of the STR card to highlight after jumping across from the CTR tab */
|
||||
highlightedStrRef: number | null = null;
|
||||
|
||||
/** Toast */
|
||||
toastMsg = '';
|
||||
toastVisible = false;
|
||||
@@ -76,6 +83,89 @@ export class FiuComponent implements OnInit {
|
||||
concludeSourceOfFundsNotes = '';
|
||||
fileReferenceNo = '';
|
||||
|
||||
/**
|
||||
* The Principal Officer's verification checklist. Each point is answered
|
||||
* "Genuine" or "Not satisfied"; the case conclusion is derived from the answers
|
||||
* rather than picked by hand, so a case with an unresolved finding cannot be
|
||||
* whitelisted by accident.
|
||||
*/
|
||||
readonly strChecklist: { key: string; label: string; hint: string }[] = [
|
||||
{
|
||||
key: 'sourceOfFunds',
|
||||
label: 'Source of funds established and documented',
|
||||
hint: 'Salary slips, sale deed, business receipts or other credible evidence on file'
|
||||
},
|
||||
{
|
||||
key: 'profileConsistent',
|
||||
label: 'Transaction consistent with the customer’s declared profile',
|
||||
hint: 'Value and frequency in line with declared income, occupation and turnover'
|
||||
},
|
||||
{
|
||||
key: 'kycCurrent',
|
||||
label: 'KYC records complete and within re-KYC validity',
|
||||
hint: 'Identity, address and beneficial-ownership records verified and current'
|
||||
},
|
||||
{
|
||||
key: 'screeningClear',
|
||||
label: 'Sanctions, PEP and adverse-media screening clear',
|
||||
hint: 'No UNSCC / MHA listing, no PEP linkage, no negative news on the customer'
|
||||
},
|
||||
{
|
||||
key: 'patternConsistent',
|
||||
label: 'Pattern consistent with the account’s own history',
|
||||
hint: 'No structuring, sudden dormancy revival or rapid pass-through observed'
|
||||
},
|
||||
{
|
||||
key: 'counterpartyKnown',
|
||||
label: 'Counterparty identifiable and legitimate',
|
||||
hint: 'Remitter / beneficiary traceable and holding a plausible commercial relationship'
|
||||
},
|
||||
{
|
||||
key: 'documentsGenuine',
|
||||
label: 'Supporting documents verified as genuine',
|
||||
hint: 'Invoices, contracts and bank statements independently corroborated'
|
||||
},
|
||||
{
|
||||
key: 'noLawEnforcement',
|
||||
label: 'No law-enforcement or regulatory reference pending',
|
||||
hint: 'No summons, freeze order or LEA request outstanding against the account'
|
||||
}
|
||||
];
|
||||
|
||||
/** null = not yet answered, true = genuine, false = not satisfied. */
|
||||
strFindings: Record<string, boolean | null> = {};
|
||||
|
||||
private resetChecklist(): void {
|
||||
this.strFindings = {};
|
||||
for (const item of this.strChecklist) {
|
||||
this.strFindings[item.key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
setFinding(key: string, genuine: boolean): void {
|
||||
this.strFindings[key] = genuine;
|
||||
this.concludeConclusion = this.derivedConclusion;
|
||||
}
|
||||
|
||||
get answeredCount(): number {
|
||||
return this.strChecklist.filter((i) => this.strFindings[i.key] !== null).length;
|
||||
}
|
||||
get allAnswered(): boolean {
|
||||
return this.answeredCount === this.strChecklist.length;
|
||||
}
|
||||
/** Points the officer marked as not satisfied — these are what force an STR. */
|
||||
get failedFindings(): { key: string; label: string; hint: string }[] {
|
||||
return this.strChecklist.filter((i) => this.strFindings[i.key] === false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything genuine means the case is cleared; a single unsatisfied point
|
||||
* means it must be filed as suspicious.
|
||||
*/
|
||||
get derivedConclusion(): 'NOT_SUSPICIOUS' | 'SUSPICIOUS' {
|
||||
return this.failedFindings.length > 0 ? 'SUSPICIOUS' : 'NOT_SUSPICIOUS';
|
||||
}
|
||||
|
||||
get isPrincipalOfficer(): boolean {
|
||||
return this.authenticationService.isPrincipalOfficerUser();
|
||||
}
|
||||
@@ -88,6 +178,84 @@ export class FiuComponent implements OnInit {
|
||||
this.loadAmlAlerts();
|
||||
this.loadAmlCtrReports();
|
||||
this.loadAmlStrCases();
|
||||
|
||||
// Bumping SEED_VERSION discards previously cached rows so a new data set takes effect.
|
||||
if (localStorage.getItem('fiu_seed_version') !== FiuComponent.SEED_VERSION) {
|
||||
localStorage.setItem('fiu_seed_version', FiuComponent.SEED_VERSION);
|
||||
localStorage.setItem('fiu_ctr_txns', JSON.stringify(this.ctrTxns));
|
||||
localStorage.setItem('fiu_str_cases', JSON.stringify(this.strTxns));
|
||||
return;
|
||||
}
|
||||
|
||||
const storedCtr = localStorage.getItem('fiu_ctr_txns');
|
||||
if (storedCtr) {
|
||||
this.ctrTxns = JSON.parse(storedCtr);
|
||||
} else {
|
||||
localStorage.setItem('fiu_ctr_txns', JSON.stringify(this.ctrTxns));
|
||||
}
|
||||
|
||||
const storedStr = localStorage.getItem('fiu_str_cases');
|
||||
if (storedStr) {
|
||||
this.strTxns = JSON.parse(storedStr);
|
||||
} else {
|
||||
localStorage.setItem('fiu_str_cases', JSON.stringify(this.strTxns));
|
||||
}
|
||||
}
|
||||
|
||||
whitelistCtrTxn(txn: CtrTransaction): void {
|
||||
txn.complianceStatus = 'WHITELISTED';
|
||||
localStorage.setItem('fiu_ctr_txns', JSON.stringify(this.ctrTxns));
|
||||
this.toast(`Transaction for ${txn.customerName} whitelisted — stays in the CTR report only`);
|
||||
}
|
||||
|
||||
flagCtrTxnSuspicious(txn: CtrTransaction): void {
|
||||
txn.complianceStatus = 'SUSPICIOUS';
|
||||
localStorage.setItem('fiu_ctr_txns', JSON.stringify(this.ctrTxns));
|
||||
|
||||
const newRef = Math.max(100, ...this.strTxns.map((s) => s.reportRefNo)) + 1;
|
||||
const newStrCase: StrTransaction = {
|
||||
...txn,
|
||||
reportType: 'STR',
|
||||
reportRefNo: newRef,
|
||||
detectedOn: new Date().toLocaleDateString('en-GB').replace(/\//g, '-'),
|
||||
flaggedBy: 'AML Officer',
|
||||
riskCategory: 'unusual-pattern',
|
||||
narrative:
|
||||
`Cash ${txn.depositOrWithdrawal.toLowerCase()} of ₹${txn.txnAmount.toLocaleString('en-IN')} breached the ` +
|
||||
`₹${this.bank.cashThreshold.toLocaleString('en-IN')} CTR limit and was flagged as suspicious by the AML Officer. ` +
|
||||
`Referred to the Principal Officer to establish whether the source of funds is legitimate or linked to terror financing.`,
|
||||
status: 'po-review',
|
||||
poFinding: undefined
|
||||
};
|
||||
|
||||
this.strTxns.unshift(newStrCase);
|
||||
localStorage.setItem('fiu_str_cases', JSON.stringify(this.strTxns));
|
||||
this.toast(`STR-${newRef} opened for ${txn.customerName} — awaiting Principal Officer investigation`);
|
||||
}
|
||||
|
||||
/** Principal Officer closes an STR case as legitimate, or confirms it and files an STR. */
|
||||
concludeLocalStr(rec: StrTransaction, finding: PoFinding): void {
|
||||
rec.poFinding = finding;
|
||||
if (finding === 'CLEAN') {
|
||||
rec.status = 'rejected';
|
||||
rec.poRemarks =
|
||||
'Source-of-funds documentation reviewed and verified by the Principal Officer. No link to terror financing or ' +
|
||||
'any other predicate offence. Case closed — no STR filed with FIU-IND.';
|
||||
this.toast(`STR-${rec.reportRefNo} closed as clean — no STR filed`);
|
||||
} else {
|
||||
rec.status = 'filed';
|
||||
rec.poRemarks =
|
||||
'Principal Officer investigation could not establish a legitimate source of funds. STR filed with FIU-IND under ' +
|
||||
'PMLA s.12 within the 7-day window; account placed under enhanced monitoring and reported to the Nodal Officer.';
|
||||
this.toast(`STR-${rec.reportRefNo} confirmed suspicious — filed with FIU-IND`);
|
||||
}
|
||||
localStorage.setItem('fiu_str_cases', JSON.stringify(this.strTxns));
|
||||
}
|
||||
|
||||
poFindingLabel(finding: PoFinding): string {
|
||||
return finding === 'CLEAN'
|
||||
? 'Clean — source of funds verified as legitimate. No STR filed.'
|
||||
: 'Suspicious — STR filed with FIU-IND and account escalated.';
|
||||
}
|
||||
|
||||
loadAmlAlerts(): void {
|
||||
@@ -104,23 +272,35 @@ export class FiuComponent implements OnInit {
|
||||
this.amlService.getStrCases().subscribe((data) => (this.amlStrCases = data || []));
|
||||
}
|
||||
|
||||
/** Surfaces a failed backend call — without this a rejected request looks like a dead button. */
|
||||
private apiError(action: string) {
|
||||
return (err: any) => this.toast(err?.error?.defaultUserMessage || `${action} failed — please retry`);
|
||||
}
|
||||
|
||||
whitelistAlert(alert: any): void {
|
||||
this.amlService.reviewAlert(alert.id, 'WHITELIST', this.amlReviewNotes[alert.id] || '').subscribe(() => {
|
||||
this.toast(`Alert #${alert.id} whitelisted`);
|
||||
this.loadAmlAlerts();
|
||||
this.amlService.reviewAlert(alert.id, 'WHITELIST', this.amlReviewNotes[alert.id] || '').subscribe({
|
||||
next: () => {
|
||||
this.toast(`Alert #${alert.id} whitelisted — no STR raised`);
|
||||
this.loadAmlAlerts();
|
||||
},
|
||||
error: this.apiError(`Whitelisting alert #${alert.id}`)
|
||||
});
|
||||
}
|
||||
|
||||
escalateAlert(alert: any): void {
|
||||
this.amlService.reviewAlert(alert.id, 'ESCALATE', this.amlReviewNotes[alert.id] || '').subscribe(() => {
|
||||
this.toast(`Alert #${alert.id} escalated to Principal Officer`);
|
||||
this.loadAmlAlerts();
|
||||
this.loadAmlStrCases();
|
||||
this.amlService.reviewAlert(alert.id, 'ESCALATE', this.amlReviewNotes[alert.id] || '').subscribe({
|
||||
next: () => {
|
||||
this.toast(`Alert #${alert.id} escalated — now an STR case for the Principal Officer`);
|
||||
this.loadAmlAlerts();
|
||||
this.loadAmlStrCases();
|
||||
},
|
||||
error: this.apiError(`Escalating alert #${alert.id}`)
|
||||
});
|
||||
}
|
||||
|
||||
generateCtrReport(): void {
|
||||
if (!this.ctrGenerateMonth) {
|
||||
this.toast('Pick a reporting month first');
|
||||
return;
|
||||
}
|
||||
this.amlService.generateCtrReport(this.ctrGenerateMonth).subscribe({
|
||||
@@ -128,16 +308,32 @@ export class FiuComponent implements OnInit {
|
||||
this.toast(`CTR report generated for ${this.ctrGenerateMonth}`);
|
||||
this.loadAmlCtrReports();
|
||||
},
|
||||
error: (err) => this.toast(err?.error?.defaultUserMessage || 'CTR report generation failed')
|
||||
error: this.apiError('CTR report generation')
|
||||
});
|
||||
}
|
||||
|
||||
selectStrCase(strCase: any): void {
|
||||
this.selectedStrCase = strCase;
|
||||
this.concludeConclusion = 'NOT_SUSPICIOUS';
|
||||
// Preserve an existing verdict instead of silently resetting it to NOT_SUSPICIOUS.
|
||||
this.concludeConclusion = strCase.conclusion === 'SUSPICIOUS' ? 'SUSPICIOUS' : 'NOT_SUSPICIOUS';
|
||||
this.concludeInvestigationNotes = strCase.investigationNotes || '';
|
||||
this.concludeSourceOfFundsNotes = strCase.sourceOfFundsNotes || '';
|
||||
this.fileReferenceNo = '';
|
||||
this.fileReferenceNo = strCase.strReferenceNo || '';
|
||||
this.resetChecklist();
|
||||
}
|
||||
|
||||
/** Plain-language outcome for a live STR case, shown in the investigation panel. */
|
||||
strCaseOutcome(strCase: any): string {
|
||||
switch (strCase?.status) {
|
||||
case 'CONCLUDED_CLEAR':
|
||||
return 'Concluded not suspicious — case closed, nothing filed with FIU-IND.';
|
||||
case 'CONCLUDED_SUSPICIOUS':
|
||||
return `Concluded suspicious — STR must be filed with FIU-IND by ${strCase.fiuDueDate || 'the due date'}.`;
|
||||
case 'FILED':
|
||||
return `STR filed with FIU-IND${strCase.strReferenceNo ? ` under reference ${strCase.strReferenceNo}` : ''}.`;
|
||||
default:
|
||||
return 'Open — awaiting the Principal Officer’s conclusion.';
|
||||
}
|
||||
}
|
||||
|
||||
closeStrCaseDetail(): void {
|
||||
@@ -148,32 +344,60 @@ export class FiuComponent implements OnInit {
|
||||
if (!this.selectedStrCase) {
|
||||
return;
|
||||
}
|
||||
if (!this.allAnswered) {
|
||||
const pending = this.strChecklist.length - this.answeredCount;
|
||||
this.toast(`Answer all verification points first — ${pending} still pending`);
|
||||
return;
|
||||
}
|
||||
if (!this.concludeInvestigationNotes.trim()) {
|
||||
this.toast('Record your investigation notes before concluding');
|
||||
return;
|
||||
}
|
||||
// The verdict always follows the checklist, never a stale radio selection.
|
||||
this.concludeConclusion = this.derivedConclusion;
|
||||
// Fold the unsatisfied points into the notes so the STR carries its own reasons.
|
||||
const failed = this.failedFindings;
|
||||
const notes = failed.length
|
||||
? `${this.concludeInvestigationNotes.trim()}\n\nUnsatisfied verification points:\n` +
|
||||
failed.map((f) => `• ${f.label}`).join('\n')
|
||||
: `${this.concludeInvestigationNotes.trim()}\n\nAll ${this.strChecklist.length} verification points found genuine.`;
|
||||
|
||||
this.amlService
|
||||
.concludeStrCase(
|
||||
this.selectedStrCase.id,
|
||||
this.concludeConclusion,
|
||||
this.concludeInvestigationNotes,
|
||||
notes,
|
||||
this.concludeSourceOfFundsNotes
|
||||
)
|
||||
.subscribe((updated) => {
|
||||
this.toast(
|
||||
this.concludeConclusion === 'SUSPICIOUS'
|
||||
? `STR case #${updated.id} concluded suspicious — FIU due date ${updated.fiuDueDate}`
|
||||
: `STR case #${updated.id} concluded not suspicious`
|
||||
);
|
||||
this.selectedStrCase = updated;
|
||||
this.loadAmlStrCases();
|
||||
.subscribe({
|
||||
next: (updated) => {
|
||||
this.toast(
|
||||
this.concludeConclusion === 'SUSPICIOUS'
|
||||
? `STR case #${updated.id} concluded suspicious — file with FIU-IND by ${updated.fiuDueDate}`
|
||||
: `STR case #${updated.id} concluded not suspicious — no filing required`
|
||||
);
|
||||
this.selectedStrCase = updated;
|
||||
this.loadAmlStrCases();
|
||||
},
|
||||
error: this.apiError(`Concluding STR case #${this.selectedStrCase.id}`)
|
||||
});
|
||||
}
|
||||
|
||||
fileStrCase(): void {
|
||||
if (!this.selectedStrCase || !this.fileReferenceNo) {
|
||||
if (!this.selectedStrCase) {
|
||||
return;
|
||||
}
|
||||
this.amlService.fileStr(this.selectedStrCase.id, this.fileReferenceNo).subscribe((updated) => {
|
||||
this.toast(`STR case #${updated.id} recorded as filed (ref ${updated.strReferenceNo})`);
|
||||
this.selectedStrCase = updated;
|
||||
this.loadAmlStrCases();
|
||||
if (!this.fileReferenceNo.trim()) {
|
||||
this.toast('Enter the FIU-IND acknowledgement number first');
|
||||
return;
|
||||
}
|
||||
this.amlService.fileStr(this.selectedStrCase.id, this.fileReferenceNo).subscribe({
|
||||
next: (updated) => {
|
||||
this.toast(`STR case #${updated.id} recorded as filed (ref ${updated.strReferenceNo})`);
|
||||
this.selectedStrCase = updated;
|
||||
this.loadAmlStrCases();
|
||||
},
|
||||
error: this.apiError(`Filing STR case #${this.selectedStrCase.id}`)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,10 +405,57 @@ export class FiuComponent implements OnInit {
|
||||
return 'status-pill status-' + (status || '').toLowerCase().replace(/_/g, '-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Where a live AML alert will end up once triaged — drives the "Routes To" column.
|
||||
* `isCtrRule` distinguishes mechanical CTR-threshold alerts (whitelisting just keeps them
|
||||
* in the CTR file) from behavioral alerts (whitelisting clears them with no report at all).
|
||||
*/
|
||||
alertRouteLabel(status: string, isCtrRule: boolean = true): string {
|
||||
switch (status) {
|
||||
case 'WHITELISTED':
|
||||
return isCtrRule ? 'CTR only' : 'Cleared';
|
||||
case 'ESCALATED':
|
||||
return '→ STR';
|
||||
default:
|
||||
return 'Pending';
|
||||
}
|
||||
}
|
||||
|
||||
alertRouteClass(status: string, isCtrRule: boolean = true): string {
|
||||
switch (status) {
|
||||
case 'WHITELISTED':
|
||||
return isCtrRule ? 'report-pill report-ctr' : 'report-pill report-cleared';
|
||||
case 'ESCALATED':
|
||||
return 'report-pill report-str';
|
||||
default:
|
||||
return 'report-pill report-pending';
|
||||
}
|
||||
}
|
||||
|
||||
showTab(tab: 'dashboard' | 'ctr' | 'str' | 'customers' | 'files'): void {
|
||||
this.activeTab = tab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Jump from a flagged CTR row to its STR card. Clears any status/risk filter that
|
||||
* would hide the case, then scrolls it into view and highlights it.
|
||||
*/
|
||||
openStrCase(txn: CtrTransaction): void {
|
||||
const match = this.strTxns.find((s) => s.txnId === txn.txnId);
|
||||
if (!match) {
|
||||
this.toast('No STR case found for this transaction');
|
||||
return;
|
||||
}
|
||||
this.activeTab = 'str';
|
||||
this.resetStrFilters();
|
||||
this.highlightedStrRef = match.reportRefNo;
|
||||
setTimeout(() => {
|
||||
document
|
||||
.getElementById(`str-card-${match.reportRefNo}`)
|
||||
?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------- KPIs --------------------
|
||||
get totalReports(): number {
|
||||
return new Set(this.ctrTxns.map((t) => t.reportRefNo)).size;
|
||||
@@ -201,10 +472,39 @@ export class FiuComponent implements OnInit {
|
||||
get filedStr(): number {
|
||||
return this.strTxns.filter((s) => s.status === 'filed').length;
|
||||
}
|
||||
|
||||
// ── Live STR case counts (backend `amlStrCases`) — these are the cases the
|
||||
// Principal Officer actually works through the Investigate → Conclude → File flow.
|
||||
get liveStrOpen(): number {
|
||||
return this.amlStrCases.filter((c) => c.status === 'OPEN').length;
|
||||
}
|
||||
get liveStrAwaitingFiling(): number {
|
||||
return this.amlStrCases.filter((c) => c.status === 'CONCLUDED_SUSPICIOUS').length;
|
||||
}
|
||||
get liveStrCleared(): number {
|
||||
return this.amlStrCases.filter((c) => c.status === 'CONCLUDED_CLEAR').length;
|
||||
}
|
||||
get liveStrFiled(): number {
|
||||
return this.amlStrCases.filter((c) => c.status === 'FILED').length;
|
||||
}
|
||||
/** Still needs PO action: untouched, or concluded suspicious but not yet filed with FIU-IND. */
|
||||
get liveStrPending(): number {
|
||||
return this.liveStrOpen + this.liveStrAwaitingFiling;
|
||||
}
|
||||
get totalCustomers(): number {
|
||||
return this.kc1.length + this.kc2.length;
|
||||
}
|
||||
|
||||
get ctrAlerts(): any[] {
|
||||
const ctrRuleCodes = ['CTR_THRESHOLD_10L', 'STRUCTURED_CTR_10L_MONTHLY', 'LARGE_CASH_TXN_25L'];
|
||||
return this.amlAlerts.filter((a) => ctrRuleCodes.includes(a.ruleCode));
|
||||
}
|
||||
|
||||
get amlSuspiciousAlerts(): any[] {
|
||||
const ctrRuleCodes = ['CTR_THRESHOLD_10L', 'STRUCTURED_CTR_10L_MONTHLY', 'LARGE_CASH_TXN_25L'];
|
||||
return this.amlAlerts.filter((a) => !ctrRuleCodes.includes(a.ruleCode));
|
||||
}
|
||||
|
||||
// -------------------- Filtered lists --------------------
|
||||
get filteredCtr(): CtrTransaction[] {
|
||||
const acc = this.ctrFilterAccount.trim();
|
||||
@@ -224,6 +524,14 @@ export class FiuComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cases that actually belong in an STR filing. A case the Principal Officer
|
||||
* concluded clean is not a suspect and must never reach FIU-IND.
|
||||
*/
|
||||
get strSuspects(): StrTransaction[] {
|
||||
return this.filteredStr.filter((s) => s.poFinding !== 'CLEAN' && s.status !== 'rejected');
|
||||
}
|
||||
|
||||
resetCtrFilters(): void {
|
||||
this.ctrFilterMode = '';
|
||||
this.ctrFilterAccount = '';
|
||||
@@ -523,11 +831,11 @@ export class FiuComponent implements OnInit {
|
||||
const rows = [headers];
|
||||
// Build one aggregate row per (reportRefNo, accountNumber) pair
|
||||
const seen = new Set<string>();
|
||||
for (const t of this.ctrTxns) {
|
||||
for (const t of this.filteredCtr) {
|
||||
const key = `${t.reportRefNo}|${t.accountNumber}`;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
const sameKey = this.ctrTxns.filter(
|
||||
const sameKey = this.filteredCtr.filter(
|
||||
(x) => x.reportRefNo === t.reportRefNo && x.accountNumber === t.accountNumber
|
||||
);
|
||||
const debits = sameKey.filter((x) => x.depositOrWithdrawal === 'Withdrawal');
|
||||
@@ -576,7 +884,7 @@ export class FiuComponent implements OnInit {
|
||||
];
|
||||
const rows = [headers];
|
||||
const seen = new Set<string>();
|
||||
for (const t of this.ctrTxns) {
|
||||
for (const t of this.filteredCtr) {
|
||||
const key = `${t.reportRefNo}|${t.accountNumber}`;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
@@ -599,12 +907,17 @@ export class FiuComponent implements OnInit {
|
||||
|
||||
/** STR XML stub (FINnet 2.0 STR XML) */
|
||||
exportStrXml(): void {
|
||||
const suspects = this.strSuspects;
|
||||
if (!suspects.length) {
|
||||
this.toast('No suspect cases to export — nothing matches the current filters');
|
||||
return;
|
||||
}
|
||||
const xml: string[] = [];
|
||||
xml.push('<?xml version="1.0" encoding="UTF-8"?>');
|
||||
xml.push(`<STR xmlns="http://fiuind.gov.in/finnet2/str" reportingMonth="${this.bank.reportingMonth}">`);
|
||||
xml.push(` <REBank buid="${this.bank.reBuid}" name="${this.bank.name}" />`);
|
||||
xml.push(` <PrincipalOfficer name="${this.bank.principalOfficer}" email="${this.bank.principalOfficerEmail}" />`);
|
||||
for (const s of this.filteredStr) {
|
||||
for (const s of suspects) {
|
||||
xml.push(` <Suspect ref="${s.reportRefNo}" status="${s.status}">`);
|
||||
xml.push(` <Account>${s.accountNumber}</Account>`);
|
||||
xml.push(
|
||||
@@ -624,10 +937,10 @@ export class FiuComponent implements OnInit {
|
||||
a.download = `STR-${this.bank.reportingMonth.replace(/\s+/g, '-')}.xml`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
this.toast(`STR XML exported (${this.filteredStr.length} suspects)`);
|
||||
this.toast(`STR XML exported (${suspects.length} suspects)`);
|
||||
}
|
||||
|
||||
/** Bundle: all 6 CSVs zipped — emitted as separate downloads sequentially */
|
||||
/** Bundle: the 6 CSVs plus the STR XML — emitted as separate downloads sequentially */
|
||||
exportAll(): void {
|
||||
setTimeout(() => this.exportTc1(), 0);
|
||||
setTimeout(() => this.exportTc2(), 200);
|
||||
@@ -635,6 +948,7 @@ export class FiuComponent implements OnInit {
|
||||
setTimeout(() => this.exportKc2(), 600);
|
||||
setTimeout(() => this.exportAccountDetail(), 800);
|
||||
setTimeout(() => this.exportAccountPersonRelation(), 1000);
|
||||
setTimeout(() => this.exportStrXml(), 1200);
|
||||
}
|
||||
|
||||
private escapeXml(s: string): string {
|
||||
|
||||
Reference in New Issue
Block a user