/** * 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, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; import { OrganizationService } from 'app/organization/organization.service'; /** * One DDS entry, laid out the way Disbursement & Transfers reviews a request - voucher and status on * top, who made it and when, then the debit and credit entries it moves - and styled by the same * sheet, so a checker reads both queues the same way. * * The checker approves it, which carries it out under the voucher numbered when it was sent, or * rejects it, which leaves nothing done and cancels that number. Neither is offered to the user who * made the entry, and the server refuses it as well. */ @Component({ selector: 'mifosx-dds-approval-review', templateUrl: './dds-approval-review.component.html', styleUrls: ['../../../clerk/disbursement-review/disbursement-review.component.scss'], standalone: true, imports: [ CommonModule, MatDialogModule, MatIconModule ] }) export class DdsApprovalReviewComponent implements OnInit { private organizationService = inject(OrganizationService); private dialogRef = inject(MatDialogRef); data = inject(MAT_DIALOG_DATA); entry: any = this.data.entry; /** The entry in full, with the debit and credit sides it moves. */ detail: any = null; loading = true; working = false; confirmReject = false; done = false; error = ''; /** Set by an approval, in case the list row did not carry the number yet. */ approvedVoucher = ''; ngOnInit(): void { this.load(); } private load(): void { this.organizationService.getDdsApproval(this.entry.id).subscribe({ next: (detail: any) => { this.detail = detail; this.entry = { ...this.entry, ...detail }; this.loading = false; }, error: () => (this.loading = false) }); } get voucherNo(): string { return this.approvedVoucher || this.entry?.voucherNo || ''; } get debitLines(): any[] { return (this.detail?.lines || []).filter((line: any) => line.side === 'DR'); } get creditLines(): any[] { return (this.detail?.lines || []).filter((line: any) => line.side === 'CR'); } total(lines: any[]): number { return lines.reduce((sum: number, line: any) => sum + Number(line.amount || 0), 0); } get isMine(): boolean { return !!this.data.currentUserId && Number(this.entry.makerId) === Number(this.data.currentUserId); } get canAct(): boolean { return this.entry.status === 'PENDING' && !this.isMine && !this.done && !this.working; } statusClass(status: string): string { const map: Record = { PENDING: 'dri-status--pending', APPROVED: 'dri-status--approved', REJECTED: 'dri-status--rejected' }; return map[status] ?? ''; } act(command: 'approve' | 'reject'): void { this.working = true; this.error = ''; this.organizationService.actOnDdsApproval(this.entry.id, command).subscribe({ next: (result: any) => { this.working = false; this.done = true; this.approvedVoucher = result?.voucherNo || ''; this.entry = { ...this.entry, status: command === 'approve' ? 'APPROVED' : 'REJECTED', voucherCancelled: command === 'reject' && !!this.voucherNo }; // The approved entry's sides are now the voucher's own lines. this.load(); }, error: (err: any) => { this.working = false; const body = err?.error; this.error = body?.errors?.[0]?.defaultUserMessage || body?.defaultUserMessage || body?.developerMessage || 'Could not complete this.'; } }); } /** The same form Disbursement & Transfers prints its times in: 29 Sept 2026, 01:25 pm. */ formatDateTime(val: any): string { if (!val) { return '—'; } const d = new Date(String(val).replace(' ', 'T')); return isNaN(d.getTime()) ? String(val) : d.toLocaleString('en-IN', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }); } close(): void { this.dialogRef.close(this.done); } }