feat(clearing): add CTS/NACH clearing UI with real NPCI batch sessions
- 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:
co-authored by
Claude Sonnet 5
parent
ed2a40a8bf
commit
6d205c0ed2
+128
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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 { Component, OnInit, inject } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import {
|
||||
MatDialogRef,
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions
|
||||
} from '@angular/material/dialog';
|
||||
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/** Custom Services */
|
||||
import { CtsClearingService } from '../../ctsclearing.service';
|
||||
import { ClearingService } from '../../../clearing-network/clearing.service';
|
||||
import { clearingError } from '../../../clearing-network/clearing-status';
|
||||
|
||||
/**
|
||||
* Manual decision on a single inward CTS cheque — PASS (with an optional note) or RETURN (with a return
|
||||
* reason code loaded from the CTS reason-code master, plus a free-text reason).
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-item-decision-dialog',
|
||||
templateUrl: './item-decision-dialog.component.html',
|
||||
styleUrls: ['./item-decision-dialog.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogTitle,
|
||||
CdkScrollable,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatButtonModule
|
||||
]
|
||||
})
|
||||
export class ItemDecisionDialogComponent implements OnInit {
|
||||
dialogRef = inject<MatDialogRef<ItemDecisionDialogComponent>>(MatDialogRef);
|
||||
data = inject(MAT_DIALOG_DATA);
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private ctsService = inject(CtsClearingService);
|
||||
private clearingService = inject(ClearingService);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
|
||||
private notify(message: string, isError = false): void {
|
||||
this.snackBar.open(message, 'Dismiss', { duration: isError ? 6000 : 3000 });
|
||||
}
|
||||
|
||||
item: any = this.data?.item || {};
|
||||
mode: 'PASS' | 'RETURN' = 'PASS';
|
||||
busy = false;
|
||||
reasonCodes: any[] = [];
|
||||
|
||||
passForm: UntypedFormGroup;
|
||||
returnForm: UntypedFormGroup;
|
||||
|
||||
constructor() {
|
||||
this.passForm = this.formBuilder.group({
|
||||
note: ['']
|
||||
});
|
||||
this.returnForm = this.formBuilder.group({
|
||||
returnReasonCode: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
reason: ['']
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.clearingService.getReturnReasonCodes('CTS').subscribe((res: any) => {
|
||||
const list = res?.pageItems ?? (Array.isArray(res) ? res : []);
|
||||
this.reasonCodes = list.filter((c: any) => c.active !== false);
|
||||
});
|
||||
}
|
||||
|
||||
setMode(mode: 'PASS' | 'RETURN'): void {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
if (this.busy) {
|
||||
return;
|
||||
}
|
||||
if (this.mode === 'PASS') {
|
||||
this.busy = true;
|
||||
const payload = { note: this.passForm.value.note || undefined, locale: 'en' };
|
||||
this.ctsService.passInwardItem(this.item.id, payload).subscribe({
|
||||
next: () => {
|
||||
this.busy = false;
|
||||
this.notify('Cheque passed');
|
||||
this.dialogRef.close({ actioned: true });
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.busy = false;
|
||||
this.notify(clearingError(err, 'Could not pass the cheque'), true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (this.returnForm.invalid) {
|
||||
return;
|
||||
}
|
||||
this.busy = true;
|
||||
const raw = this.returnForm.value;
|
||||
const payload = { returnReasonCode: raw.returnReasonCode, reason: raw.reason || undefined, locale: 'en' };
|
||||
this.ctsService.returnInwardItem(this.item.id, payload).subscribe({
|
||||
next: () => {
|
||||
this.busy = false;
|
||||
this.notify('Cheque returned');
|
||||
this.dialogRef.close({ actioned: true });
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.busy = false;
|
||||
this.notify(clearingError(err, 'Could not return the cheque'), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user