feat(ctsclearing): validation comparison popup, decided-item read-only view, and dialog-close fixes
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s

Adds a "Comparison Details" popup on the inward decision dialog showing
exactly what each automatic rule compared (amount figures vs words,
instrument date vs its valid-until window, balance vs amount, RBI
Positive Pay threshold, IQA sub-checks) instead of just the pass/fail
pill, mirroring InwardClearingValidationService.java's actual logic.
For an already-decided item, the stale/postdated "as of" date now shows
the decision date rather than today's live date.

Once a cheque is decided, the batch table's Validation column collapses
to a single outcome pill instead of permanently showing all 7 rule
badges, and the item-decision dialog itself switches to a read-only
decision summary (outcome, decisioned-on, note, settlement status)
instead of a re-openable Pass/Return form — the "Decide" action becomes
"View" for decided rows.

Also fixes two silently-broken directives: matTooltip and mat-dialog-close
were used throughout the decision dialogs without MatTooltipModule /
MatDialogClose being imported into the standalone components, so every
tooltip and every close button did nothing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-07-22 18:29:18 +05:30
co-authored by Claude Sonnet 5
parent 0207cf85a3
commit a22f8e7e45
8 changed files with 799 additions and 73 deletions
@@ -10,14 +10,17 @@
import { Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';
import { FormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import {
MatDialog,
MatDialogRef,
MAT_DIALOG_DATA,
MatDialogTitle,
MatDialogContent,
MatDialogActions
MatDialogActions,
MatDialogClose
} from '@angular/material/dialog';
import { CdkScrollable } from '@angular/cdk/scrolling';
import { MatButtonModule } from '@angular/material/button';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatSnackBar } from '@angular/material/snack-bar';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -31,7 +34,8 @@ import type { GalleryItem } from 'lightgallery/lg-utils';
import { CtsClearingService } from '../../ctsclearing.service';
import { ClearingService } from '../../../clearing-network/clearing.service';
import { SignatoryService, AccountSignatoryInfo, JointHolder } from '../../../clearing-network/signatory.service';
import { clearingError } from '../../../clearing-network/clearing-status';
import { clearingError, pillClass } from '../../../clearing-network/clearing-status';
import { ValidationDetailDialogComponent } from './validation-detail-dialog/validation-detail-dialog.component';
interface RequiredSigner {
clientId: number;
@@ -72,8 +76,10 @@ const OVERLAY_DEFAULT_WIDTH = 160;
CdkScrollable,
MatDialogContent,
MatDialogActions,
MatDialogClose,
MatButtonModule,
MatCheckboxModule
MatCheckboxModule,
MatTooltipModule
]
})
export class ItemDecisionDialogComponent implements OnInit, OnDestroy {
@@ -84,6 +90,7 @@ export class ItemDecisionDialogComponent implements OnInit, OnDestroy {
private clearingService = inject(ClearingService);
private signatoryService = inject(SignatoryService);
private snackBar = inject(MatSnackBar);
private dialog = inject(MatDialog);
@ViewChild('lightboxRoot', { static: true }) lightboxRoot: ElementRef<HTMLElement>;
@ViewChild('chequeStage') chequeStageEl?: ElementRef<HTMLElement>;
@@ -165,6 +172,9 @@ export class ItemDecisionDialogComponent implements OnInit, OnDestroy {
this.destroyLightbox();
}
/** Shared status-string -> pill-color mapping, same one the batch list table already uses for iqaStatus/decision. */
pillClass = pillClass;
/** ok / bad / neutral for a single boolean validation rule, respecting whether true is the good outcome. */
ruleClass(rule: { key: string; goodWhenTrue: boolean }): 'ok' | 'bad' | 'neutral' {
const value = this.item?.[rule.key];
@@ -175,6 +185,17 @@ export class ItemDecisionDialogComponent implements OnInit, OnDestroy {
return good ? 'ok' : 'bad';
}
/** What each automatic rule actually compared (amount vs words, instrument date vs the valid-until window,
* balance vs amount, etc.) instead of just the pass/fail pill — opened on demand, not always-on clutter. */
openValidationDetail(): void {
this.dialog.open(ValidationDetailDialogComponent, {
data: { item: this.item, accountInfo: this.accountInfo },
width: '560px',
maxWidth: '95vw',
maxHeight: '85vh'
});
}
private loadImages(): void {
this.ctsService.getInwardItemImage(this.item.id, 'front').subscribe({
next: (url: string) => (this.frontImageUrl = url),
@@ -558,6 +579,22 @@ export class ItemDecisionDialogComponent implements OnInit, OnDestroy {
};
}
/** null when the account's balance couldn't be resolved at all — the automatic "Balance" check pill already
* covers that outcome, this just makes the actual sufficiency legible at a glance rather than requiring
* the clerk to mentally subtract Available Balance minus Amount themselves. */
get balanceSufficient(): boolean | null {
const available = this.accountInfo?.availableBalance;
if (
available === null ||
available === undefined ||
this.item?.amount === null ||
this.item?.amount === undefined
) {
return null;
}
return available >= this.item.amount;
}
get modeOfOperationLabel(): string {
const labels: { [key: string]: string } = {
EITHER_OR_SURVIVOR: 'Either or Survivor — any one signatory',