/** * 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/. */ import { Component, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { VoucherStatusComponent, VoucherStatus } from '../voucher-status/voucher-status.component'; export interface VoucherDetailDialogData { /** Already-loaded detail from CashierService.voucherDetail() — the caller fetches, this dialog * only renders, so one loading state lives in the row's own click handler rather than being * duplicated inside every dialog that could ever show a voucher. */ detail: { voucherNo: string; voucherStatus?: VoucherStatus; revokedByVoucherNo?: string | null; replacesVoucherNo?: string | null; correctionNote?: string | null; voucherType: string | null; amount: number | null; branchName: string | null; createdBy: string | null; createdAt: string | null; totalDebit: number; totalCredit: number; balanced: boolean; lines: { lineNo: number; entryType: 'DR' | 'CR'; partyName: string | null; accountNo: string | null; amount: number; description: string | null; }[]; /** GL entries a native platform command posted for the same underlying event, outside this * voucher entirely — a share redemption's Dr Share Capital / Cr Share Sundry pair, posted at * manager approval, before this voucher's own Dr Sundry / Cr Cash at Vault settlement leg even * exists. Empty for every voucher that already is the whole transaction on its own. */ relatedLines: { stage: string; entryType: 'DR' | 'CR'; partyName: string | null; accountNo: string | null; amount: number; description: string | null; }[]; }; } /** The full slip behind a summarised register row — every debit and credit line a voucher is * made of, not just the first side of each that a table row has room for. */ @Component({ selector: 'mifosx-voucher-detail-dialog', standalone: true, imports: [ CommonModule, MatDialogModule, MatButtonModule, MatIconModule, VoucherStatusComponent ], template: `
receipt_long

{{ detail.voucherNo }}

{{ detail.voucherType || 'Voucher' }} · {{ detail.branchName || 'Head Office' }}

Raised by {{ detail.createdBy || '—' }} @if (detail.createdAt) { {{ detail.createdAt | date: 'dd MMM yyyy, h:mm a' }} } @if (!detail.balanced) { warning Debit and credit do not balance }
@for (line of detail.lines; track line.lineNo) { @if (line.description) { } } @empty { } @if (detail.lines.length > 0) { }
Side Party Account No. Amount
{{ line.entryType === 'DR' ? 'Debit' : 'Credit' }} {{ line.partyName || '—' }} {{ line.accountNo || '—' }} ₹{{ line.amount | number: '1.2-2' }}
{{ line.description }}
This voucher's individual lines were not recorded.
Total ₹{{ detail.totalDebit | number: '1.2-2' }} / ₹{{ detail.totalCredit | number: '1.2-2' }}
@if (detail.relatedLines.length > 0) { }
`, styles: [ ` .vd-shell { display: flex; flex-direction: column; gap: 16px; padding: 20px 24px 16px; min-width: 420px; max-width: 560px; } .vd-header { display: flex; align-items: flex-start; justify-content: space-between; } .vd-header__title { display: flex; align-items: center; gap: 12px; } .vd-header__title h2 { margin: 0; font-size: 18px; font-weight: 600; } .vd-header__title p { margin: 2px 0 0; font-size: 13px; color: rgba(0, 0, 0, 0.6); } .vd-icon { color: #1976d2; } .vd-meta { display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 13px; color: rgba(0, 0, 0, 0.65); } .vd-unbalanced { display: inline-flex; align-items: center; gap: 4px; color: #b91c1c; font-weight: 600; } .vd-table { width: 100%; border-collapse: collapse; font-size: 13px; } .vd-table th { text-align: left; text-transform: uppercase; font-size: 11px; letter-spacing: 0.03em; color: rgba(0, 0, 0, 0.5); padding: 6px 8px; border-bottom: 1px solid rgba(0, 0, 0, 0.1); } .vd-table td { padding: 8px; border-bottom: 1px solid rgba(0, 0, 0, 0.06); } .vd-table .num { text-align: right; font-variant-numeric: tabular-nums; } .vd-acct { color: rgba(0, 0, 0, 0.55); } .vd-side { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; } .vd-side--dr { background: #fee2e2; color: #b91c1c; } .vd-side--cr { background: #dcfce7; color: #15803d; } .vd-desc-row td { padding-top: 0; border-bottom: 1px solid rgba(0, 0, 0, 0.06); } .vd-desc { font-size: 12px; color: rgba(0, 0, 0, 0.5); font-style: italic; } .vd-empty { text-align: center; color: rgba(0, 0, 0, 0.5); padding: 16px 8px; } .vd-table tfoot td { font-weight: 600; border-bottom: none; border-top: 1px solid rgba(0, 0, 0, 0.1); } .vd-related { border-top: 1px dashed rgba(0, 0, 0, 0.15); padding-top: 12px; } .vd-related__title { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; color: #6d28d9; margin-bottom: 8px; } .vd-table--related td { background: #faf5ff; } .vd-related__hint { margin: 6px 0 0; font-size: 12px; color: rgba(0, 0, 0, 0.5); } .vd-actions { display: flex; justify-content: flex-end; } ` ] }) export class VoucherDetailDialogComponent { dialogRef = inject(MatDialogRef); private data = inject(MAT_DIALOG_DATA); detail = this.data.detail; }