done
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user