Files
cbsnexora/src/app/ctsclearing/inward-batch-detail/inward-batch-detail.component.html
T
Jaanu423andClaude Sonnet 5 0207cf85a3
Publish Image in Docker Hub / build (push) Canceled after 0s
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s
feat(ctsclearing): single-panel signature verification for inward cheques
Adds a rich decide dialog for clerks verifying inward CTS cheques: cheque
images, all automatic validation results, and a mandatory signature-check
gate built from the account's mode of operation and joint holders (backed
by a new SignatoryService reusing the existing signature/document fetch
pattern from the cashier module).

Also adds a fullscreen compare mode: the cheque opens large with a shelf
of every mandatory signer's stored signature, which can be dragged onto
the cheque, resized, made semi-transparent, and zoomed, for a real
side-by-side signature match instead of squinting at a thumbnail.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 12:25:20 +05:30

241 lines
9.8 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
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/.
-->
<div class="container">
<div class="page-header">
<h2>{{ 'labels.inputs.Inward Batch' | translate }} #{{ batch.id }}</h2>
<button mat-raised-button [routerLink]="['/ctsclearing', 'inward']">{{ 'labels.buttons.Back' | translate }}</button>
</div>
<mat-card class="info-card">
<mat-card-content>
<div class="info-grid">
<div>
<span class="k">{{ 'labels.inputs.Batch Reference' | translate }}</span
><span>{{ batch.batchReference }}</span>
</div>
<div>
<span class="k">{{ 'labels.inputs.Session Date' | translate }}</span
><span>{{ batch.sessionDate ? (batch.sessionDate | dateFormat) : '–' }}</span>
</div>
<div>
<span class="k">{{ 'labels.inputs.Presentment Type' | translate }}</span
><span>{{ batch.presentmentType }}</span>
</div>
<div>
<span class="k">{{ 'labels.inputs.Items' | translate }}</span
><span>{{ batch.itemCount ?? batch.totalCount ?? items.length }}</span>
</div>
<div>
<span class="k">{{ 'labels.inputs.Total Amount' | translate }}</span
><span>{{ batch.totalAmount ?? '–' }}</span>
</div>
<div>
<span class="k">{{ 'labels.inputs.Status' | translate }}</span>
<span
><span class="pill" [ngClass]="pillClass(batch.status)">{{ batch.status }}</span></span
>
</div>
@if (batch.presentingBankName || batch.presentingBankCode) {
<div>
<span class="k">{{ 'labels.inputs.Presenting Bank' | translate }}</span
><span
>{{ batch.presentingBankName || '–' }} <span class="muted">{{ batch.presentingBankCode }}</span></span
>
</div>
}
@if (batch.returnTatDeadline) {
<div>
<span class="k">{{ 'labels.inputs.Return TAT Deadline' | translate }}</span
><span>{{ batch.returnTatDeadline | dateFormat }}</span>
</div>
}
</div>
</mat-card-content>
</mat-card>
<div class="action-toolbar">
<button mat-raised-button color="primary" (click)="runValidation()" [disabled]="busy">
<fa-icon icon="check-circle"></fa-icon>&nbsp;{{ 'labels.buttons.Run Validation' | translate }}
</button>
<button mat-raised-button (click)="prepareSettlement()" [disabled]="busy">
<fa-icon icon="money-check"></fa-icon>&nbsp;{{ 'labels.buttons.Prepare Settlement' | translate }}
</button>
</div>
<div class="table-wrapper mat-elevation-z1">
<table mat-table [dataSource]="items">
<ng-container matColumnDef="chequeNumber">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Cheque Number' | translate }}</th>
<td mat-cell *matCellDef="let it">
{{ it.chequeNumber }}
@if (it.instrumentStopPaymentFlag) {
<span class="pill bad" matTooltip="Stop payment was flagged on the depositing side">STOP</span>
}
@if (it.micrCode) {
<span class="muted">{{ it.micrCode }}</span>
}
@if (it.ifsc) {
<span class="muted">{{ it.ifsc }}</span>
}
@if (it.ctsReferenceNumber) {
<span class="muted">{{ it.ctsReferenceNumber }}</span>
}
@if (it.instrumentDate) {
<span class="muted">Dated {{ it.instrumentDate | dateFormat }}</span>
}
@if (it.instrumentTypeDescription || it.instrumentType) {
<span class="muted">{{ it.instrumentTypeDescription || it.instrumentType }}</span>
}
</td>
</ng-container>
<ng-container matColumnDef="drawer">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Account' | translate }}</th>
<td mat-cell *matCellDef="let it">
{{ it.accountNo || it.accountId || '–' }}
@if (it.clientName) {
<span class="muted">{{ it.clientName }}</span>
}
@if (it.drawerAccountNumberMasked) {
<span class="muted">Drawer a/c {{ it.drawerAccountNumberMasked }}</span>
}
@if (it.draweeBankName) {
<span class="muted">{{ it.draweeBankName }}</span>
}
</td>
</ng-container>
<ng-container matColumnDef="payeeName">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Payee Name' | translate }}</th>
<td mat-cell *matCellDef="let it">{{ it.payeeName }}</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef class="num">{{ 'labels.inputs.Amount' | translate }}</th>
<td mat-cell *matCellDef="let it" class="num">
{{ it.amount }}
@if (it.amountInWords) {
<div class="muted">{{ it.amountInWords }}</div>
}
</td>
</ng-container>
<ng-container matColumnDef="image">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Image' | translate }}</th>
<td mat-cell *matCellDef="let it">
<div class="image-cell">
@if (getItemImageUrl(it, 'front')) {
<img
[src]="getItemImageUrl(it, 'front')"
class="cheque-thumb"
alt="front"
matTooltip="Front image — click to zoom"
(click)="openImageZoom(it, 'front')"
/>
} @else {
<input #frontFile type="file" accept="image/*" hidden (change)="uploadItemImage(it, 'front', $event)" />
<button mat-icon-button (click)="frontFile.click()" matTooltip="Upload front image">
<fa-icon icon="plus"></fa-icon>
</button>
}
@if (getItemImageUrl(it, 'back')) {
<img
[src]="getItemImageUrl(it, 'back')"
class="cheque-thumb"
alt="back"
matTooltip="Back image — click to zoom"
(click)="openImageZoom(it, 'back')"
/>
} @else {
<input #backFile type="file" accept="image/*" hidden (change)="uploadItemImage(it, 'back', $event)" />
<button mat-icon-button (click)="backFile.click()" matTooltip="Upload back image">
<fa-icon icon="plus"></fa-icon>
</button>
}
</div>
</td>
</ng-container>
<ng-container matColumnDef="checks">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Validation' | translate }}</th>
<td mat-cell *matCellDef="let it">
<div class="check-badges">
@for (rule of rules; track rule.key) {
<span class="pill" [ngClass]="ruleClass(it, rule)" [title]="rule.label">{{ rule.label }}</span>
}
</div>
@if (it.validationDetail) {
<div class="validation-detail muted">{{ it.validationDetail }}</div>
}
@if (it.iqaStatus && it.iqaStatus !== 'NA') {
<span class="pill" [ngClass]="pillClass(it.iqaStatus)" matTooltip="Image Quality Assessment"
>IQA: {{ it.iqaStatus }}</span
>
}
@if (it.iqaDetail) {
<div class="muted">{{ it.iqaDetail }}</div>
}
@if (it.iqaSubChecks) {
<div class="muted">{{ it.iqaSubChecks }}</div>
}
</td>
</ng-container>
<ng-container matColumnDef="decision">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Decision' | translate }}</th>
<td mat-cell *matCellDef="let it">
@if (it.decision) {
<span class="pill" [ngClass]="pillClass(it.decision)">{{ it.decision }}</span>
} @else {
<span class="pill neutral">{{ it.itemStatus || 'PENDING' }}</span>
}
@if (it.returnReasonCode) {
<div class="muted">{{ it.returnReasonCode }}</div>
}
</td>
</ng-container>
<ng-container matColumnDef="hold">
<th mat-header-cell *matHeaderCellDef class="num">{{ 'labels.inputs.Hold' | translate }}</th>
<td mat-cell *matCellDef="let it" class="num">
@if (it.holdPlaced) {
{{ it.holdAmount ?? '✓' }}
} @else {
–
}
</td>
</ng-container>
<ng-container matColumnDef="returnDeadline">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Return Deadline' | translate }}</th>
<td mat-cell *matCellDef="let it">
{{ it.returnDeadline ? (it.returnDeadline | dateFormat) : '–' }}
@if (it.originalSerialNumber) {
<span class="muted">Orig # {{ it.originalSerialNumber }}</span>
}
@if (it.depositDate) {
<span class="muted">Deposited {{ it.depositDate | dateFormat }}</span>
}
@if (it.depositAccountNumber) {
<span class="muted">Deposit a/c {{ it.depositAccountNumber }}</span>
}
@if (it.presentingBranchCode) {
<span class="muted">Branch {{ it.presentingBranchCode }}</span>
}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let it">
<button mat-button color="primary" (click)="decide(it)">{{ 'labels.buttons.Decide' | translate }}</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columns"></tr>
<tr mat-row *matRowDef="let row; columns: columns"></tr>
</table>
@if (items.length === 0) {
<div class="empty-state">{{ 'labels.text.No records to display' | translate }}</div>
}
</div>
<div #lightboxRoot style="display: none"></div>
</div>