From 38884610d29362e772b41daf690722965b8e0043 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Tue, 6 Oct 2026 22:36:15 +0530 Subject: [PATCH] feat(shares): preview the approval checklist before submit, fix settlement GL option The Clerk Dashboard's standalone Share Withdrawal Request form never offered a GL settlement destination -- it always submitted SAVINGS, silently, while the Cashier's Transaction Entry screen already let a clerk pick any postable GL account on its CR row. Added the same "Settlement Destination" choice (Savings / GL Account) with a searchable GL selector here too, so both entry points to the same backend endpoint behave the same way. Both share-withdrawal confirm dialogs (savings settlement and GL settlement, in transaction-entry.component.ts) now call the backend's new preview-validate endpoint before showing the confirm dialog, so a clerk sees the same checklist a manager will see on approval -- a missing GL mapping, an unverified KYC, an open AML alert -- before committing to the request, not after a manager has already spent time reviewing it. A failing check is shown, not blocked: real hard rules are still enforced server-side at approve(). Guarded with a 5s timeout and a fallback message so a slow or unavailable check can never leave the dialog looking stuck. error-handler.interceptor.ts: a document/signature attachment whose row survives but whose file is missing from the content store now resolves to a silent 404 (matching the existing image-fetch handling) instead of surfacing a raw error. Background GET failures (500/501/503) on calls a component already recovers from (photo, attachment, notes, standing instructions, ...) now surface as a corner toast instead of a blocking modal that has to be dismissed -- a write action's failure (POST/PUT/DELETE) still gets the centred, must-acknowledge dialog, since that is the one thing the user was waiting on. Also surfaces the backend's real message on a 500 instead of a hardcoded generic sentence. Backend counterpart: ClerkShareWithdrawalRequestApiResource (preview-validate endpoint, shared buildShareWithdrawalChecks()) and DocumentReadPlatformServiceImpl (missing-file 404) land separately. Co-Authored-By: Claude Opus 5 (1M context) --- .../cashier/clerk-share-withdrawal.service.ts | 6 ++ .../transaction-entry.component.ts | 56 ++++++++++++- .../share-withdrawal-request.component.html | 44 +++++++++- .../share-withdrawal-request.component.ts | 84 +++++++++++++++++-- .../core/http/error-handler.interceptor.ts | 32 +++++-- 5 files changed, 202 insertions(+), 20 deletions(-) diff --git a/src/app/cashier/clerk-share-withdrawal.service.ts b/src/app/cashier/clerk-share-withdrawal.service.ts index ff93d4294..d8eee1d70 100644 --- a/src/app/cashier/clerk-share-withdrawal.service.ts +++ b/src/app/cashier/clerk-share-withdrawal.service.ts @@ -51,6 +51,12 @@ export class ClerkShareWithdrawalService { return this.http.post(`/clerk/share-withdrawal-requests/${id}/validate`, {}); } + /** Runs the exact same checklist approve() will run, against a draft that hasn't been submitted + * yet — so the clerk sees it in the confirm dialog, before committing to the request. */ + previewValidate(data: any): Observable { + return this.http.post('/clerk/share-withdrawal-requests/preview-validate', data); + } + approve(id: number): Observable { return this.http.post(`/clerk/share-withdrawal-requests/${id}/approve`, {}); } diff --git a/src/app/cashier/transaction-entry/transaction-entry.component.ts b/src/app/cashier/transaction-entry/transaction-entry.component.ts index 940287102..1cb271588 100644 --- a/src/app/cashier/transaction-entry/transaction-entry.component.ts +++ b/src/app/cashier/transaction-entry/transaction-entry.component.ts @@ -11,7 +11,7 @@ import { CommonModule } from '@angular/common'; import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpClient, HttpParams } from '@angular/common/http'; -import { catchError, debounceTime, distinctUntilChanged, map } from 'rxjs/operators'; +import { catchError, debounceTime, distinctUntilChanged, map, timeout } from 'rxjs/operators'; import { forkJoin, of, Subject } from 'rxjs'; import { MatDialog, MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; @@ -3425,6 +3425,7 @@ export class TransactionEntryComponent implements OnInit, OnDestroy { meetingDate: shareRow.meetingDate, description: shareRow.description || null, settlementPaymentTypeId: savingsRow.paymentTypeId, + settlementMode: 'SAVINGS', settlementRowSnapshot }; @@ -3455,11 +3456,60 @@ export class TransactionEntryComponent implements OnInit, OnDestroy { ? 'Pending Manager approval — redemption, settlement and account closure all post together on approval.' : 'Pending Manager approval — redemption and settlement both post together on approval.' ]; - this.showConfirmAlert('info', 'Confirm Share Withdrawal Request', confirmLines, 'Submit for Approval', () => + this.showShareWithdrawalConfirm(payload, confirmLines, () => this.doSubmitShareRedemptionTransfer(payload, shareRow, shares, linkedAcNo) ); } + /** + * Shows the confirm dialog with the same checklist the Manager will see when approving this + * request — fetched live from the backend (`previewValidate`) rather than re-implemented here, + * so it can never show the clerk something different from what the approver actually sees. A + * failing check is shown, not blocked: approve() enforces its own hard rules separately (e.g. + * Share Advance Collection applied), this list is advisory so problems surface to whoever can + * still fix them, before a manager's time is spent on a request that was always going to fail. + */ + private showShareWithdrawalConfirm( + payload: Record, + baseLines: string[], + onConfirm: () => void + ): void { + this.clerkShareWithdrawalService + .previewValidate(payload) + .pipe( + // A request that never resolves (slow network, server busy) must not leave the Submit + // button looking dead — the dialog still has to open, just without the checklist. + timeout(5000), + catchError(() => of({ __previewFailed: true })) + ) + .subscribe((res: any) => { + const checks: { label: string; pass: boolean; detail: string }[] = res?.checks ?? []; + // A failed call (most likely the backend not yet carrying the /preview-validate endpoint) + // says so explicitly rather than just omitting the section — a silently empty checklist + // looks identical to "everything passed, nothing to show", which is the one reading this + // must never give. + const checkLines = res?.__previewFailed ? [ + '', + '⚠ Validation checklist unavailable right now — the request can still be submitted.' + ] : checks.length ? [ + '', + 'Validation Checks (same as Manager approval):', + ...checks.map((c) => `${c.pass ? '✓' : '⚠'} ${c.label}: ${c.detail}`) + ] : []; + // baseLines ends with a blank line followed by the "Pending Manager approval…" footer + // sentence, which must stay last so the modal renders it as a footnote rather than a + // key/value row — so the checklist is spliced in just before that pair, not appended after. + const footerStart = + baseLines.length >= 2 && baseLines[baseLines.length - 2] === '' ? baseLines.length - 2 : baseLines.length; + const lines = [ + ...baseLines.slice(0, footerStart), + ...checkLines, + ...baseLines.slice(footerStart) + ]; + this.showConfirmAlert('info', 'Confirm Share Withdrawal Request', lines, 'Submit for Approval', onConfirm); + }); + } + /** * Share redemption settled to a GL account the clerk picks on the CR row, instead of the * member's linked savings account — mirrors submitShareRedemptionTransfer() above, but the @@ -3571,7 +3621,7 @@ export class TransactionEntryComponent implements OnInit, OnDestroy { ? 'Pending Manager approval — redemption, settlement and account closure all post together on approval.' : 'Pending Manager approval — redemption and settlement both post together on approval.' ]; - this.showConfirmAlert('info', 'Confirm Share Withdrawal Request', confirmLines, 'Submit for Approval', () => + this.showShareWithdrawalConfirm(payload, confirmLines, () => this.doSubmitShareGlSettlementTransfer(payload, shareRow, shares, glRow.acNo) ); } diff --git a/src/app/clerk/share-withdrawal-request/share-withdrawal-request.component.html b/src/app/clerk/share-withdrawal-request/share-withdrawal-request.component.html index b47ead9e9..4de97690b 100644 --- a/src/app/clerk/share-withdrawal-request/share-withdrawal-request.component.html +++ b/src/app/clerk/share-withdrawal-request/share-withdrawal-request.component.html @@ -129,6 +129,31 @@ * Date of Meeting + + @if (!isReview && settlementMode === 'GL') { + + }