144 lines
4.1 KiB
TypeScript
144 lines
4.1 KiB
TypeScript
/**
|
|
* 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 { FormsModule } from '@angular/forms';
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
import { MatIconModule } from '@angular/material/icon';
|
|
import { catchError, of } from 'rxjs';
|
|
import { CashierService } from '../cashier.service';
|
|
|
|
/** One side of a voucher: a debit or a credit against a single account. */
|
|
interface VoucherLine {
|
|
lineNo: number;
|
|
entryType: 'DR' | 'CR';
|
|
glAccountId: number | null;
|
|
savingsAccountId: number | null;
|
|
loanAccountId: number | null;
|
|
partyName: string | null;
|
|
accountNo: string | null;
|
|
amount: number;
|
|
description: string | null;
|
|
}
|
|
|
|
interface Voucher {
|
|
voucherNo: string;
|
|
voucherType: string | null;
|
|
referenceType: string | null;
|
|
referenceId: number | null;
|
|
amount: number | null;
|
|
branchOfficeId: number | null;
|
|
branchName: string | null;
|
|
createdBy: string | null;
|
|
createdAt: string | null;
|
|
verificationStatus: string | null;
|
|
lines: VoucherLine[];
|
|
totalDebit: number;
|
|
totalCredit: number;
|
|
balanced: boolean;
|
|
}
|
|
|
|
/**
|
|
* One voucher, as a slip: which accounts were debited, which credited, and for how much.
|
|
*
|
|
* Looked up by voucher number rather than listed, because that is how a voucher is reached — from a
|
|
* paper slip in someone's hand, or from a number quoted on a statement.
|
|
*/
|
|
@Component({
|
|
selector: 'mifosx-voucher-view',
|
|
templateUrl: './voucher-view.component.html',
|
|
styleUrls: [
|
|
'../cashier-shared.scss',
|
|
'./voucher-view.component.scss'
|
|
],
|
|
standalone: true,
|
|
imports: [
|
|
CommonModule,
|
|
FormsModule,
|
|
MatIconModule
|
|
]
|
|
})
|
|
export class VoucherViewComponent {
|
|
private service = inject(CashierService);
|
|
private route = inject(ActivatedRoute);
|
|
private router = inject(Router);
|
|
|
|
voucherNo = '';
|
|
voucher: Voucher | null = null;
|
|
loading = false;
|
|
/** Shown in place of the slip — a bad number and a server error read differently to the user. */
|
|
error: string | null = null;
|
|
|
|
constructor() {
|
|
// A voucher number in the URL means someone arrived from a link or a statement rather than by
|
|
// typing, so the slip is fetched straight away instead of showing them an empty search box.
|
|
const fromUrl = this.route.snapshot.queryParamMap.get('voucherNo');
|
|
if (fromUrl) {
|
|
this.voucherNo = fromUrl;
|
|
this.search();
|
|
}
|
|
}
|
|
|
|
search(): void {
|
|
const trimmed = (this.voucherNo || '').trim();
|
|
if (!trimmed) {
|
|
this.error = 'Enter a voucher number.';
|
|
this.voucher = null;
|
|
return;
|
|
}
|
|
|
|
this.loading = true;
|
|
this.error = null;
|
|
|
|
// Kept in the URL so the slip can be linked to and survives a refresh.
|
|
this.router.navigate([], {
|
|
relativeTo: this.route,
|
|
queryParams: { voucherNo: trimmed },
|
|
replaceUrl: true
|
|
});
|
|
|
|
this.service
|
|
.voucherDetail(trimmed)
|
|
.pipe(
|
|
catchError((err: any) => {
|
|
this.error =
|
|
err?.error?.errors?.[0]?.defaultUserMessage ??
|
|
err?.error?.developerMessage ??
|
|
`Voucher ${trimmed} could not be loaded.`;
|
|
return of(null);
|
|
})
|
|
)
|
|
.subscribe((res: Voucher | null) => {
|
|
this.loading = false;
|
|
this.voucher = res;
|
|
});
|
|
}
|
|
|
|
/** Debits first, then credits — the order a slip is read in. */
|
|
get debitLines(): VoucherLine[] {
|
|
return (this.voucher?.lines ?? []).filter((l) => l.entryType === 'DR');
|
|
}
|
|
|
|
get creditLines(): VoucherLine[] {
|
|
return (this.voucher?.lines ?? []).filter((l) => l.entryType === 'CR');
|
|
}
|
|
|
|
/**
|
|
* A voucher raised before its sides were recorded. The voucher is real and its number valid — only
|
|
* the posting behind it was never captured — so this reads as an older record, not a broken one.
|
|
*/
|
|
get hasNoLines(): boolean {
|
|
return !!this.voucher && this.voucher.lines.length === 0;
|
|
}
|
|
|
|
print(): void {
|
|
window.print();
|
|
}
|
|
}
|