149 lines
4.6 KiB
TypeScript
149 lines
4.6 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, 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<DdsApprovalReviewComponent>);
|
|
data = inject<any>(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<string, string> = {
|
|
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);
|
|
}
|
|
}
|