185 lines
6.0 KiB
TypeScript
185 lines
6.0 KiB
TypeScript
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');
|
||
},
|
||
},
|
||
}),
|
||
);
|
||
}
|
||
}
|