/** * 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 { Injectable, inject } from '@angular/core'; import { AuthenticationService } from './authentication/authentication.service'; import { GL_BRANCHES, GlBranch } from 'app/gl-reports/gl-reports-data'; /** * Resolves what branch data the logged-in user is allowed to see. * * Head Office sees every branch and can drill into any one of them; a branch login is * locked to its own book. Previously this logic was copy-pasted into the Cash/Clearing * Summary and the NPA report with slightly different branch code lists; this service is * the single place it lives so new reports cannot drift. * * The logged-in office is matched to a branch by name, since session credentials carry * only officeId and officeName. Office names are seeded to match the GL branch names * exactly (see Liquibase 0442). */ @Injectable({ providedIn: 'root' }) export class BranchScopeService { private auth = inject(AuthenticationService); /** Sentinel used when a branch login cannot be matched to a known branch. */ static readonly UNRESOLVED = '__unresolved__'; get isHeadOffice(): boolean { return this.auth.isHeadOfficeUser(); } /** The office name on the session, for labelling the scope in report headers. */ get officeName(): string { return this.auth.getCredentials()?.officeName || 'Branch Office'; } /** * Branch code the user is confined to, or null for Head Office. * * Deliberately returns the UNRESOLVED sentinel rather than falling back to the first * branch when the office cannot be matched: showing an unrecognised branch someone * else's figures would be worse than showing them nothing. */ get ownBranchCode(): string | null { if (this.isHeadOffice) { return null; } const name = this.officeName.toLowerCase().trim(); const match = GL_BRANCHES.find((b) => b.code !== 'HO' && name === b.name.toLowerCase()); // Fall back to a substring match for offices named e.g. "Andheri (E) Branch". const loose = match ?? GL_BRANCHES.find((b) => b.code !== 'HO' && name.includes(b.name.toLowerCase())); return loose?.code ?? BranchScopeService.UNRESOLVED; } /** True when the user may choose which branch to view. */ get canSelectBranch(): boolean { return this.isHeadOffice; } /** Branches offered in a selector — everything for Head Office, nothing for a branch login. */ get selectableBranches(): GlBranch[] { return this.isHeadOffice ? GL_BRANCHES : []; } /** Branch code a report should start on. '' means all branches consolidated. */ get defaultBranchCode(): string { return this.isHeadOffice ? '' : (this.ownBranchCode ?? ''); } /** * Forces a requested branch code into what the user is actually allowed to see. * Call this wherever a branch code reaches a data query, so a stale or hand-edited * selection cannot widen the user's scope. */ enforce(requested: string): string { return this.isHeadOffice ? requested : (this.ownBranchCode ?? BranchScopeService.UNRESOLVED); } /** True when a branch login's office matched no known branch, so it sees no data. */ get isUnresolved(): boolean { return this.ownBranchCode === BranchScopeService.UNRESOLVED; } /** Human label for the current scope, for report headers. */ scopeLabel(selectedCode: string): string { if (!this.isHeadOffice) { return this.officeName; } if (!selectedCode) { return 'All Branches (Consolidated)'; } return GL_BRANCHES.find((b) => b.code === selectedCode)?.name ?? selectedCode; } }