Adds a shared Export to Excel utility (investment-export.ts) and wires it into every listing/report screen in the Investment module — Dashboard (break-up + allocation), Reports (all ~20 report shapes), Interest (per active tab), the FD/Bond/Share Holdings registers, and Masters (per active master tab). Entry/transaction forms intentionally have no export button. No sign-off is included here, matching the request to keep that feature reconciliation-only. Also fixes the Reconciliation export flow: the Prepared/Checked/Approved sign-off is now an optional one-off popup shown when "Export to Excel" is clicked (fields are blank by default and downloading works whether or not they're filled), replacing the earlier always-visible sign-off card. Also fixes three reconciliation reports (Sundry Dr/Cr, NEFT/RTGS, UDIR) whose labels contain "/" — an Excel-forbidden worksheet-name character — which was silently aborting their export.
90 lines
3.3 KiB
TypeScript
90 lines
3.3 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/.
|
|
*/
|
|
|
|
// Shared "Export to Excel" builder for every listing/report screen in the Investment module
|
|
// (Dashboard, Reports, Interest, Holdings, Masters) — centralised so the ExcelJS boilerplate
|
|
// (title/meta/header rows, number formatting, safe sheet names, Blob download) is written once
|
|
// and every screen's export always behaves and looks the same.
|
|
|
|
import * as ExcelJS from 'exceljs';
|
|
|
|
export interface ExportSheetSpec {
|
|
/** Worksheet tab name — sanitised for Excel's forbidden characters (\ / ? * [ ]) and length. */
|
|
sheetName: string;
|
|
/** Bold title row, merged across all columns. */
|
|
title: string;
|
|
/** Optional italic subtitle row (e.g. scope/date), merged across all columns. */
|
|
subtitle?: string;
|
|
headers: string[];
|
|
rows: (string | number | null)[][];
|
|
widths?: number[];
|
|
}
|
|
|
|
/** Excel worksheet names can't contain \ / ? * [ ] or exceed 31 chars. */
|
|
function safeSheetName(name: string): string {
|
|
return name.replace(/[\\/?*[\]]/g, '-').slice(0, 31);
|
|
}
|
|
|
|
function buildSheet(wb: ExcelJS.Workbook, spec: ExportSheetSpec): void {
|
|
const sheet = wb.addWorksheet(safeSheetName(spec.sheetName));
|
|
sheet.columns = (spec.widths || spec.headers.map(() => 18)).map((w) => ({ width: w }));
|
|
|
|
const title = sheet.addRow([spec.title]);
|
|
sheet.mergeCells(title.number, 1, title.number, spec.headers.length);
|
|
title.font = { bold: true, size: 14, color: { argb: 'FFFFFFFF' } };
|
|
title.alignment = { horizontal: 'center', vertical: 'middle' };
|
|
title.height = 24;
|
|
title.getCell(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF0F4F3C' } };
|
|
|
|
if (spec.subtitle) {
|
|
const meta = sheet.addRow([spec.subtitle]);
|
|
sheet.mergeCells(meta.number, 1, meta.number, spec.headers.length);
|
|
meta.font = { italic: true, size: 10 };
|
|
}
|
|
sheet.addRow([]);
|
|
|
|
const head = sheet.addRow(spec.headers);
|
|
head.font = { bold: true };
|
|
head.alignment = { wrapText: true, horizontal: 'center' };
|
|
head.eachCell((c) => {
|
|
c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE2E8F0' } };
|
|
c.border = { bottom: { style: 'thin' } };
|
|
});
|
|
|
|
for (const r of spec.rows) {
|
|
const row = sheet.addRow(r);
|
|
r.forEach((v, idx) => {
|
|
if (typeof v === 'number') {
|
|
row.getCell(idx + 1).numFmt = '#,##0.00';
|
|
}
|
|
});
|
|
}
|
|
sheet.views = [{ state: 'frozen', ySplit: spec.subtitle ? 4 : 3 }];
|
|
}
|
|
|
|
/** Builds a (possibly multi-sheet) workbook and triggers a browser download. */
|
|
export async function exportWorkbook(sheets: ExportSheetSpec[], fileName: string): Promise<void> {
|
|
const wb = new ExcelJS.Workbook();
|
|
wb.creator = 'CBSNexora';
|
|
wb.created = new Date();
|
|
sheets.forEach((s) => buildSheet(wb, s));
|
|
|
|
const buffer = await wb.xlsx.writeBuffer();
|
|
const blob = new Blob([buffer], {
|
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
});
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `${fileName.replace(/[^\w]+/g, '-')}.xlsx`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}
|