oss file updated, profitl loss as per the day month and also with date select
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* 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 * as ExcelJS from 'exceljs';
|
||||
import {
|
||||
MatrixTable,
|
||||
ReturnGeneralInfoRow,
|
||||
ReturnLine,
|
||||
ReturnSignatory,
|
||||
SIGNATORY_FIELDS
|
||||
} from './oss-return.model';
|
||||
|
||||
/** Palette lifted from the RBI CIMS workbooks so exports look like the originals. */
|
||||
export const XL = {
|
||||
banner: 'FF002F5F',
|
||||
bannerText: 'FFFFFFFF',
|
||||
headerGrey: 'FFC0C0C0',
|
||||
derivedBlue: 'FF9CC3F5',
|
||||
auditYellow: 'FFFFFFCC',
|
||||
keyCell: 'FFD9D9D9',
|
||||
border: 'FF000000'
|
||||
};
|
||||
|
||||
const AMOUNT_FMT = '#,##0.00';
|
||||
const COUNT_FMT = '#,##0';
|
||||
|
||||
export function solidFill(argb: string): ExcelJS.Fill {
|
||||
return { type: 'pattern', pattern: 'solid', fgColor: { argb } };
|
||||
}
|
||||
|
||||
/** Dark-blue full-width banner that heads every sheet in the RBI templates. */
|
||||
export function addBanner(sheet: ExcelJS.Worksheet, title: string, lastCol: string): void {
|
||||
const row = sheet.addRow([title]);
|
||||
sheet.mergeCells(`A${row.number}:${lastCol}${row.number}`);
|
||||
row.height = 26;
|
||||
const cell = row.getCell(1);
|
||||
cell.fill = solidFill(XL.banner);
|
||||
cell.font = { bold: true, size: 13, color: { argb: XL.bannerText } };
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' };
|
||||
sheet.addRow([]);
|
||||
}
|
||||
|
||||
export function boxAllCells(
|
||||
sheet: ExcelJS.Worksheet,
|
||||
from: number,
|
||||
to: number,
|
||||
lastColIndex: number
|
||||
): void {
|
||||
for (let r = from; r <= to; r++) {
|
||||
for (let c = 1; c <= lastColIndex; c++) {
|
||||
sheet.getRow(r).getCell(c).border = {
|
||||
top: { style: 'thin', color: { argb: XL.border } },
|
||||
left: { style: 'thin', color: { argb: XL.border } },
|
||||
bottom: { style: 'thin', color: { argb: XL.border } },
|
||||
right: { style: 'thin', color: { argb: XL.border } }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function addSectionHeader(sheet: ExcelJS.Worksheet, label: string, unitLabel?: string): void {
|
||||
const row = sheet.addRow([label, unitLabel ?? '']);
|
||||
row.font = { bold: true };
|
||||
row.eachCell((cell) => (cell.fill = solidFill(XL.headerGrey)));
|
||||
row.getCell(2).alignment = { horizontal: 'right' };
|
||||
}
|
||||
|
||||
/** Renders a labelled block; indents by level and shades derived totals. */
|
||||
export function addLineRows(sheet: ExcelJS.Worksheet, lines: ReturnLine[]): void {
|
||||
for (const line of lines) {
|
||||
const row = sheet.addRow([line.label, line.value]);
|
||||
const labelCell = row.getCell(1);
|
||||
const valueCell = row.getCell(2);
|
||||
|
||||
labelCell.alignment = { wrapText: true, vertical: 'middle', indent: line.level };
|
||||
if (line.level === 0) {
|
||||
labelCell.font = { bold: true };
|
||||
}
|
||||
valueCell.numFmt = line.count ? COUNT_FMT : AMOUNT_FMT;
|
||||
valueCell.alignment = { horizontal: 'right' };
|
||||
if (line.total) {
|
||||
valueCell.fill = solidFill(XL.derivedBlue);
|
||||
valueCell.font = { bold: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a multi-column statement: title band, column header row, then the matrix
|
||||
* rows. Returns the row number after the block so callers can keep stacking.
|
||||
*/
|
||||
export function addMatrixTable(sheet: ExcelJS.Worksheet, table: MatrixTable): void {
|
||||
const colCount = table.columns.length + 1;
|
||||
const start = sheet.rowCount + 1;
|
||||
|
||||
const titleRow = sheet.addRow([table.title, ...(table.unit ? [table.unit] : [])]);
|
||||
if (!table.unit) {
|
||||
sheet.mergeCells(titleRow.number, 1, titleRow.number, colCount);
|
||||
}
|
||||
titleRow.font = { bold: true };
|
||||
for (let c = 1; c <= colCount; c++) {
|
||||
titleRow.getCell(c).fill = solidFill(XL.headerGrey);
|
||||
}
|
||||
|
||||
if (table.subtitle) {
|
||||
const sub = sheet.addRow([table.subtitle]);
|
||||
sheet.mergeCells(sub.number, 1, sub.number, colCount);
|
||||
sub.font = { bold: true, size: 10 };
|
||||
sub.alignment = { wrapText: true };
|
||||
sub.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
}
|
||||
|
||||
const head = sheet.addRow(['Particulars', ...table.columns]);
|
||||
head.font = { bold: true };
|
||||
head.alignment = { wrapText: true, horizontal: 'center', vertical: 'middle' };
|
||||
head.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
|
||||
for (const row of table.rows) {
|
||||
const excelRow = sheet.addRow([row.label, ...row.values]);
|
||||
const labelCell = excelRow.getCell(1);
|
||||
labelCell.alignment = { wrapText: true, vertical: 'middle', indent: row.level };
|
||||
if (row.level === 0 || row.header) {
|
||||
labelCell.font = { bold: true };
|
||||
}
|
||||
if (row.header) {
|
||||
labelCell.fill = solidFill(XL.headerGrey);
|
||||
continue;
|
||||
}
|
||||
for (let c = 2; c <= colCount; c++) {
|
||||
const cell = excelRow.getCell(c);
|
||||
cell.numFmt = row.percent ? '0.00"%"' : AMOUNT_FMT;
|
||||
cell.alignment = { horizontal: 'right' };
|
||||
if (row.total) {
|
||||
cell.fill = solidFill(XL.derivedBlue);
|
||||
cell.font = { bold: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
boxAllCells(sheet, start, sheet.rowCount, colCount);
|
||||
|
||||
for (const note of table.notes ?? []) {
|
||||
const noteRow = sheet.addRow([note]);
|
||||
sheet.mergeCells(noteRow.number, 1, noteRow.number, colCount);
|
||||
noteRow.font = { bold: true, size: 9 };
|
||||
noteRow.alignment = { wrapText: true };
|
||||
noteRow.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
}
|
||||
sheet.addRow([]);
|
||||
}
|
||||
|
||||
/** The General Information sheet is identical in shape across every OSS return. */
|
||||
export function buildGeneralInfoSheet(wb: ExcelJS.Workbook, rows: ReturnGeneralInfoRow[]): void {
|
||||
const sheet = wb.addWorksheet('General Information');
|
||||
sheet.columns = [
|
||||
{ width: 26 },
|
||||
{ width: 58 }
|
||||
];
|
||||
addBanner(sheet, 'General Information', 'B');
|
||||
|
||||
const first = sheet.rowCount + 1;
|
||||
for (const item of rows) {
|
||||
const row = sheet.addRow([item.label, item.value]);
|
||||
row.getCell(1).fill = solidFill(XL.keyCell);
|
||||
// The workbook tints the audit flag yellow and leaves unfilled cells grey.
|
||||
const fill = item.label === 'Audit Flag' ? XL.auditYellow : item.value ? XL.derivedBlue : XL.headerGrey;
|
||||
row.getCell(2).fill = solidFill(fill);
|
||||
}
|
||||
boxAllCells(sheet, first, sheet.rowCount, 2);
|
||||
}
|
||||
|
||||
/** The Signatory sheet is likewise identical across returns. */
|
||||
export function buildSignatorySheet(
|
||||
wb: ExcelJS.Workbook,
|
||||
signatories: ReturnSignatory[],
|
||||
sheetName = 'Signatory'
|
||||
): void {
|
||||
const sheet = wb.addWorksheet(sheetName);
|
||||
sheet.columns = [
|
||||
{ width: 44 },
|
||||
{ width: 24 }
|
||||
];
|
||||
addBanner(sheet, sheetName, 'B');
|
||||
|
||||
for (const person of signatories) {
|
||||
const start = sheet.rowCount + 1;
|
||||
const head = sheet.addRow([person.heading, '']);
|
||||
head.font = { bold: true };
|
||||
head.alignment = { horizontal: 'center' };
|
||||
head.getCell(1).fill = solidFill(XL.keyCell);
|
||||
|
||||
for (const f of SIGNATORY_FIELDS) {
|
||||
const row = sheet.addRow([person[f.key], f.caption]);
|
||||
row.getCell(2).font = { bold: true };
|
||||
row.getCell(2).alignment = { horizontal: 'center' };
|
||||
row.getCell(2).fill = solidFill(XL.keyCell);
|
||||
}
|
||||
boxAllCells(sheet, start, sheet.rowCount, 2);
|
||||
sheet.addRow([]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Serialises the workbook and triggers a browser download. */
|
||||
export async function downloadWorkbook(wb: ExcelJS.Workbook, filename: string): Promise<void> {
|
||||
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;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export function newWorkbook(): ExcelJS.Workbook {
|
||||
const wb = new ExcelJS.Workbook();
|
||||
wb.creator = 'CBSNexora';
|
||||
wb.created = new Date();
|
||||
return wb;
|
||||
}
|
||||
Reference in New Issue
Block a user