/** * 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 { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { GlAccount } from 'app/gl-reports/gl-reports-data'; import { GlDateRange, glValueForCodes } from 'app/gl-reports/gl-reports.compute'; import { RETURN_SCALE, glAccountsForPicker } from './gl-mapping'; export interface GlMappingDialogData { returnCode: string; lineLabel: string; /** The figure currently printed on the return, in thousands. */ filedValue: number | null; selected: string[]; range: GlDateRange; branchCode: string; } /** * Lets a user pin one or more GL accounts to a single return line, showing live what * the ledger would produce for the selection against the figure currently on the return. */ @Component({ selector: 'mifosx-gl-mapping-dialog', standalone: true, imports: [ CommonModule, FormsModule, MatDialogModule, FaIconComponent ], templateUrl: './gl-mapping-dialog.component.html', styleUrls: ['./gl-mapping-dialog.component.scss'] }) export class GlMappingDialogComponent { private dialogRef = inject(MatDialogRef); data = inject(MAT_DIALOG_DATA); readonly accounts: GlAccount[] = glAccountsForPicker(); search = ''; typeFilter = ''; selected: string[] = []; readonly types = [ 'ASSET', 'LIABILITY', 'INCOME', 'EXPENSE', 'CONTINGENT' ]; constructor() { this.selected = [...(this.data.selected ?? [])]; } get filtered(): GlAccount[] { const q = this.search.trim().toLowerCase(); return this.accounts.filter((a) => { if (this.typeFilter && a.type !== this.typeFilter) { return false; } if (!q) { return true; } return a.code.includes(q) || a.name.toLowerCase().includes(q) || a.group.toLowerCase().includes(q); }); } isSelected(code: string): boolean { return this.selected.includes(code); } toggle(code: string): void { this.selected = this.isSelected(code) ? this.selected.filter((c) => c !== code) : [ ...this.selected, code ]; } removeSelected(code: string): void { this.selected = this.selected.filter((c) => c !== code); } clearAll(): void { this.selected = []; } accountName(code: string): string { return this.accounts.find((a) => a.code === code)?.name ?? code; } /** Live ledger figure for the current selection, in the return's scale. */ get derivedValue(): number { return glValueForCodes(this.selected, this.data.range, this.data.branchCode) / RETURN_SCALE; } get variance(): number | null { return this.data.filedValue == null ? null : this.derivedValue - this.data.filedValue; } get matchesFiled(): boolean { return this.variance !== null && Math.abs(this.variance) < 0.005; } save(): void { this.dialogRef.close(this.selected); } cancel(): void { this.dialogRef.close(undefined); } }