/** * 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 { FormsModule } from '@angular/forms'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { of } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { SharesService } from '../../shares.service'; import { shareReportCsv, shareReportPrint } from '../share-report.util'; export interface MailingRow { clientId: number; folioNumber: string; clientName: string; mobileNo: string; officeName: string; addressLine1: string; addressLine2: string; street: string; city: string; district: string; postalCode: string; area: string; sharesHeld: number; shareValue: number; shareAccountNos: string; addressable: boolean; } /** Assembles one member's address into the lines an envelope actually carries. */ export function addressLines(row: MailingRow): string[] { return [ row.addressLine1, row.addressLine2, row.street, [ row.city, row.district ] .filter(Boolean) .join(', '), row.postalCode ? `PIN ${row.postalCode}` : '' ] .map((line) => (line || '').trim()) .filter(Boolean); } /** * Share Label — printable address labels for shareholding members. * *
* Laid out as a grid sized to ordinary label sheets and printed straight from the browser, so a * mailing run needs no separate mail-merge tool. Members with no usable address are listed * separately rather than printed as a blank label: a blank label is a wasted sheet and a member who * silently never hears from the society. */ @Component({ selector: 'mifosx-share-label', templateUrl: './share-label.component.html', styleUrls: [ '../../../letter-mgmt/certificates/certificates.shared.scss', '../share-report.shared.scss' ], imports: [ CommonModule, FormsModule, FaIconComponent ] }) export class ShareLabelComponent implements OnInit { private sharesService = inject(SharesService); loading = true; loadError = ''; rows: MailingRow[] = []; totals: any = null; search = ''; areaFilter = ''; /** Labels per row on the sheet — 2 and 3 cover the sheets a branch actually buys. */ columns = 3; showFolio = true; showHolding = false; ngOnInit(): void { this.load(); } load(): void { this.loading = true; this.loadError = ''; this.sharesService .getShareMailingList({}) .pipe( catchError((err) => { this.loadError = this.readError(err, 'The mailing list could not be loaded.'); return of(null); }) ) .subscribe((res: any) => { this.rows = res?.rows || []; this.totals = res?.totals || null; this.loading = false; }); } get areaOptions(): string[] { return [...new Set(this.rows.map((r) => r.area).filter(Boolean))].sort(); } /** Only members who can actually be posted to — the rest are listed below, not printed. */ get printableRows(): MailingRow[] { return this.filteredRows.filter((r) => r.addressable); } get unaddressableRows(): MailingRow[] { return this.filteredRows.filter((r) => !r.addressable); } get filteredRows(): MailingRow[] { const term = this.search.trim().toLowerCase(); return this.rows.filter((r) => { if (this.areaFilter && r.area !== this.areaFilter) { return false; } if (!term) { return true; } return [ r.clientName, r.folioNumber, r.area, r.postalCode ] .filter(Boolean) .some((field) => String(field).toLowerCase().includes(term)); }); } lines(row: MailingRow): string[] { return addressLines(row); } clearFilters(): void { this.search = ''; this.areaFilter = ''; } exportCsv(): void { shareReportCsv( 'share-mailing-labels', [ 'Member', 'Folio No', 'Address', 'City', 'District', 'Postal Code', 'Mobile', 'Shares Held', 'Addressable' ], this.filteredRows.map((r) => [ r.clientName, r.folioNumber, this.lines(r).join(' / '), r.city, r.district, r.postalCode, r.mobileNo, r.sharesHeld, r.addressable ? 'Yes' : 'No' ]) ); } print(): void { shareReportPrint(); } private readError(err: any, fallback: string): string { return ( err?.error?.errors?.[0]?.defaultUserMessage || err?.error?.defaultUserMessage || err?.error?.developerMessage || fallback ); } }