report-generator-backend/src/controllers/ReadingReportController.ts
jorming.chong 148182c680
Some checks are pending
Test / test (push) Waiting to run
first commit
2026-01-07 09:29:07 +08:00

185 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Logger } from '@jig-software/logger';
import { Controller, Inject } from '@jig-software/sdk';
import ExcelJS from 'exceljs';
import path from 'path';
import { readingReportContract } from '../shared';
import { createFileOutput } from '@jig-software/trest-core';
import fs from 'fs';
interface ReadingStats {
min?: string;
max?: string;
avg?: string;
}
interface DeviceData {
building: string;
floor: string;
device_name: string;
min_max_avg: Record<string, ReadingStats>;
readings: Record<string, Record<string, string>>;
}
interface ReportParams {
start_date?: string;
end_date?: string;
time_step?: string;
}
export class ReadingReportController extends Controller {
@Inject()
protected logger!: Logger;
constructor() {
super();
this.logger.setTag(this.constructor.name);
this.implement(readingReportContract, (r) =>
r.handlers({
generateReadingsReport: {
handler: async (FullReportSchema) => {
const data: DeviceData[] = FullReportSchema.data;
const reportParams: ReportParams = FullReportSchema.report_params;
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Event Records');
// ---------------- Logos ----------------
const jecLogoId = workbook.addImage({
filename: path.resolve('asset/jec_logo.jpg'),
extension: 'jpeg',
});
const fastLogoId = workbook.addImage({
filename: path.resolve('asset/fast_logo.jpg'),
extension: 'jpeg',
});
worksheet.addImage(jecLogoId, {
tl: { col: 0, row: 0 },
ext: { width: 120, height: 60 },
});
worksheet.addImage(fastLogoId, {
tl: { col: 7, row: 0 },
ext: { width: 120, height: 60 },
});
// ---------------- Title ----------------
worksheet.mergeCells('B1:G2');
const titleCell = worksheet.getCell('B1');
titleCell.value = 'Event Report';
titleCell.alignment = { horizontal: 'center', vertical: 'middle' };
titleCell.font = { bold: true, size: 18 };
// ---------------- Fixed params ----------------
const fixedParams = [
['Start Date:', reportParams.start_date ?? ''],
['End Date:', reportParams.end_date ?? ''],
['Time Step:', reportParams.time_step ?? ''],
];
fixedParams.forEach(([label, value], idx) => {
worksheet.getCell(4 + idx, 2).value = label;
worksheet.getCell(4 + idx, 3).value = value;
});
// ---------------- Headers ----------------
const allDates = Array.from(
new Set(
data.flatMap((device) =>
Object.values(device.readings).flatMap((r) => Object.keys(r)),
),
),
).sort();
const headers = [
'Building',
'Floor',
'Device Name',
'Reading Type',
'Minimum',
'Maximum',
'Average',
...allDates,
];
const headerRowIndex = 7;
const headerRow = worksheet.getRow(headerRowIndex);
headerRow.values = headers;
headerRow.eachCell((cell) => {
cell.font = { bold: true };
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFD9D9D9' },
};
});
// ---------------- Data ----------------
let currentRow = headerRowIndex + 1;
for (const device of data) {
const types = Object.keys(device.readings);
const startRow = currentRow;
const endRow = currentRow + types.length - 1;
if (types.length > 1) {
worksheet.mergeCells(startRow, 1, endRow, 1);
worksheet.mergeCells(startRow, 2, endRow, 2);
worksheet.mergeCells(startRow, 3, endRow, 3);
}
worksheet.getCell(startRow, 1).value = device.building;
worksheet.getCell(startRow, 2).value = device.floor;
worksheet.getCell(startRow, 3).value = device.device_name;
for (const type of types) {
const row = worksheet.getRow(currentRow);
row.getCell(4).value = type;
const stats = device.min_max_avg?.[type] ?? {};
row.getCell(5).value = stats.min ?? '-';
row.getCell(6).value = stats.max ?? '-';
row.getCell(7).value = stats.avg ?? '-';
allDates.forEach((date, idx) => {
row.getCell(8 + idx).value =
device.readings[type]?.[date] ?? '-';
});
currentRow++;
}
console.log(
`Processed device '${device.device_name}' rows ${startRow}${endRow}`,
);
}
// ---------------- Auto-width ----------------
worksheet.columns.forEach((col) => {
let max = 10;
col.eachCell!({ includeEmpty: true }, (cell) => {
const len = String(cell.value ?? '').length;
max = Math.max(max, len);
});
col.width = max + 2;
});
worksheet.autoFilter = {
from: { row: headerRowIndex, column: 1 },
to: { row: currentRow - 1, column: headers.length },
};
// ---------------- Save file ----------------
const outputPath = path.resolve('event_report.xlsx');
await workbook.xlsx.writeFile(outputPath);
console.log(`Excel report generated: ${outputPath}`);
const readStream = fs.createReadStream(outputPath);
return createFileOutput(readStream, 'event_report.xlsx');
},
},
}),
);
}
}