Skip to content

Commit

Permalink
feat: add service hours exports
Browse files Browse the repository at this point in the history
  • Loading branch information
SebassNoob committed May 3, 2024
1 parent 63903f5 commit 71184ef
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 15 deletions.
2 changes: 1 addition & 1 deletion interapp-backend/api/models/exports/exports_attendance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { BaseExportsModel } from './exports_base';
import { ServiceSession, type AttendanceStatus } from '@db/entities';
import { HTTPErrors } from '@utils/errors';
import { WorkSheet } from 'node-xlsx';
import { type WorkSheet } from 'node-xlsx';
import appDataSource from '@utils/init_datasource';

@staticImplements<ExportsModelImpl>()
Expand Down
47 changes: 40 additions & 7 deletions interapp-backend/api/models/exports/exports_service_hours.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,48 @@
import {
AttendanceExportsResult,
AttendanceExportsXLSX,
AttendanceQueryExportsConditions,
type ServiceHoursExportResult,
type ServiceHoursExportsXLSX,
type ServiceHoursQueryExportsConditions,
ExportsModelImpl,
staticImplements,
} from './types';
import { BaseExportsModel } from './exports_base';
import { ServiceSession, type AttendanceStatus } from '@db/entities';
import { User } from '@db/entities';
import { HTTPErrors } from '@utils/errors';
import { WorkSheet } from 'node-xlsx';
import appDataSource from '@utils/init_datasource';

// @staticImplements<ExportsModelImpl>()
export class ServiceHoursExportsModel extends BaseExportsModel {}
@staticImplements<ExportsModelImpl>()
export class ServiceHoursExportsModel extends BaseExportsModel {
public static async queryExports({ type, order }: ServiceHoursQueryExportsConditions) {
const result: ServiceHoursExportResult[] = await appDataSource.manager
.createQueryBuilder()
.select(['user.user_id', 'user.username', 'user.service_hours'])
.from(User, 'user')
.orderBy(`user.${type}`, order)
.getMany();
return result;
}
public static async formatXLSX(conds: ServiceHoursQueryExportsConditions) {
const ret = await this.queryExports(conds);

if (ret.length === 0) throw HTTPErrors.RESOURCE_NOT_FOUND;

const data: ServiceHoursExportsXLSX = [
['user_id', 'username', 'service_hours'],
...ret.map(
(r) => [r.user_id, r.username, r.service_hours] satisfies ServiceHoursExportsXLSX[1],
),
];

const sheetOptions = this.getSheetOptions(data);

return { name: 'service hours', data, options: sheetOptions };
}
public static async packXLSX(
type: ServiceHoursQueryExportsConditions['type'],
order: ServiceHoursQueryExportsConditions['order'],
) {
const worksheet = await this.formatXLSX({ type, order });

return this.constructXLSX(worksheet);
}
}
21 changes: 19 additions & 2 deletions interapp-backend/api/models/exports/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AttendanceStatus } from '@db/entities';
import { type WorkSheet } from 'node-xlsx';

export interface ExportsModelImpl {
queryExports(conds: unknown): Promise<unknown[]>;
formatXLSX(conds: unknown): Promise<unknown>;
packXLSX(ids: number[], start_date?: string, end_date?: string): Promise<Buffer>;
formatXLSX(conds: unknown): Promise<WorkSheet>;
packXLSX(...params: unknown[]): Promise<Buffer>;
}

// class decorator that asserts that a class implements an interface statically
Expand Down Expand Up @@ -48,3 +49,19 @@ export type AttendanceQueryExportsConditions = {
end_date?: never;
}
);

export type ServiceHoursExportResult = {
username: string;
user_id: number;
service_hours: number;
};

export type ServiceHoursExportsXLSX = [
['user_id', 'username', 'service_hours'],
...[number, string, number][],
];

export type ServiceHoursQueryExportsConditions = {
type: 'user_id' | 'username' | 'service_hours';
order: 'ASC' | 'DESC';
};
24 changes: 20 additions & 4 deletions interapp-backend/api/routes/endpoints/exports/exports.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AttendanceExportsModel } from '@models/exports';
import { AttendanceExportsModel, ServiceHoursExportsModel } from '@models/exports';
import { z } from 'zod';
import { validateRequiredFields, verifyJWT, verifyRequiredPermission } from '@routes/middleware';
import { ExportsFields } from './validation';
import { AttendanceExportsFields, ServiceHoursExportsFields } from './validation';
import { Router } from 'express';
import { Permissions } from '@utils/permissions';

Expand All @@ -11,11 +11,11 @@ const xlsxMime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sh

exportsRouter.get(
'/',
validateRequiredFields(ExportsFields),
validateRequiredFields(AttendanceExportsFields),
verifyJWT,
verifyRequiredPermission(Permissions.ATTENDANCE_MANAGER),
async (req, res) => {
const query = req.query as unknown as z.infer<typeof ExportsFields>;
const query = req.query as unknown as z.infer<typeof AttendanceExportsFields>;

const exports = await AttendanceExportsModel.packXLSX(
query.id,
Expand All @@ -29,3 +29,19 @@ exportsRouter.get(
res.status(200).send(exports);
},
);
exportsRouter.get(
'/service_hours',
validateRequiredFields(ServiceHoursExportsFields),
verifyJWT,
verifyRequiredPermission(Permissions.ATTENDANCE_MANAGER),
async (req, res) => {
const query = req.query as unknown as z.infer<typeof ServiceHoursExportsFields>;

const exports = await ServiceHoursExportsModel.packXLSX(query.type, query.order);

res.setHeader('Content-Type', xlsxMime);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.type(xlsxMime);
res.status(200).send(exports);
},
);
9 changes: 8 additions & 1 deletion interapp-backend/api/routes/endpoints/exports/validation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from 'zod';

export const ExportsFields = z
export const AttendanceExportsFields = z
.object({
id: z.array(z.coerce.number()).or(z.coerce.number()),
start_date: z.string().optional(),
Expand Down Expand Up @@ -52,3 +52,10 @@ export const ExportsFields = z
...data,
id: Array.isArray(data.id) ? data.id : [data.id],
}));

export const ServiceHoursExportsFields = z
.object({
type: z.enum(['user_id', 'username', 'service_hours']),
order: z.enum(['ASC', 'DESC']),
})
.transform((data) => data);

0 comments on commit 71184ef

Please sign in to comment.