feat(clearing): add CTS/NACH clearing UI with real NPCI batch sessions
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s

- CTS clearing module: inward batches (validate/pass/return), outward presentment
  capture, positive pay, settlement, reconciliation
- Outward Clearing merged into one screen: Pending Cheques + Batches tabs (a real
  clearing session per NPCI submission, either the 2-hourly job or manual send),
  drill into a batch to settle/dishonour its cheques
- Date-range, text, and quick (Today/Yesterday/All) filters with pagination across
  inward batches, outward presentments, outward batches, and batch detail
- NACH clearing module (mandates, batches, transaction decisioning)
- Shell/sidenav wiring for both new modules

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-07-21 15:57:45 +05:30
co-authored by Claude Sonnet 5
parent ed2a40a8bf
commit 6d205c0ed2
55 changed files with 6801 additions and 8 deletions
@@ -0,0 +1,54 @@
/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/**
* Maps a clearing status/decision string to one of the three shared .pill classes
* (ok / bad / neutral). Shared by every CTS, NACH and settlement/reconciliation screen so the
* colour language stays consistent across the whole clearing feature.
*/
export function pillClass(status: string | null | undefined): 'ok' | 'bad' | 'neutral' {
const s = (status || '').toUpperCase();
const ok = [
'PASSED',
'PASS',
'POSTED',
'MATCHED',
'SETTLED',
'SENT',
'SENT_TO_CLEARING_HOUSE',
'PROCESSED',
'VALIDATED',
'ACTIVE',
'CLEARED',
'HONOURED',
'COMPLETED'
];
const bad = [
'RETURNED',
'RETURN',
'REJECTED',
'MISMATCH',
'DISHONOURED',
'CANCELLED',
'FAILED',
'FAIL',
'UNROUTABLE'
];
if (ok.includes(s)) {
return 'ok';
}
if (bad.includes(s)) {
return 'bad';
}
return 'neutral';
}
/** Extracts the most specific backend error message from a Fineract-style error response. */
export function clearingError(err: any, fallback = 'Request failed'): string {
return err?.error?.errors?.[0]?.defaultUserMessage ?? err?.error?.defaultUserMessage ?? fallback;
}
@@ -0,0 +1,77 @@
/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/** Angular Imports */
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
/**
* Shared clearing services used across CTS and NACH — return-reason-code master, settlement-batch posting,
* and reconciliation.
*/
@Injectable({
providedIn: 'root'
})
export class ClearingService {
private http = inject(HttpClient);
// ----- Return reason codes -----
/** appliesTo: 'CTS' | 'NACH'. Returns a raw array of reason codes. */
getReturnReasonCodes(appliesTo: string): Observable<any> {
return this.http.get('/clearing/return-reason-codes', {
params: new HttpParams().set('appliesTo', appliesTo)
});
}
// ----- Settlement batches -----
/** Server-side paginated — returns { pageItems, totalFilteredRecords }. */
getSettlementBatches(clearingType?: string, status?: string, offset = 0, limit = 20): Observable<any> {
let params = new HttpParams().set('offset', offset).set('limit', limit);
if (clearingType) params = params.set('clearingType', clearingType);
if (status) params = params.set('status', status);
return this.http.get('/clearing/settlement-batches', { params });
}
getSettlementBatch(id: string | number): Observable<any> {
return this.http.get(`/clearing/settlement-batches/${id}`);
}
/** payload: { clearingType, sourceBatchId }. */
prepareSettlement(payload: any): Observable<any> {
return this.http.post('/clearing/settlement-batches', payload);
}
postSettlement(id: string | number): Observable<any> {
return this.http.post(
`/clearing/settlement-batches/${id}`,
{},
{ params: new HttpParams().set('command', 'post') }
);
}
rejectSettlement(id: string | number, payload: any): Observable<any> {
return this.http.post(`/clearing/settlement-batches/${id}`, payload, {
params: new HttpParams().set('command', 'reject')
});
}
// ----- Reconciliations -----
getReconciliations(clearingType?: string, offset = 0, limit = 20): Observable<any> {
let params = new HttpParams().set('offset', offset).set('limit', limit);
if (clearingType) params = params.set('clearingType', clearingType);
return this.http.get('/clearing/reconciliations', { params });
}
runReconciliation(payload: any): Observable<any> {
return this.http.post('/clearing/reconciliations', payload);
}
}