From 6fbccb0c9dba0033c2d03f92912f73d3a0ebb01b Mon Sep 17 00:00:00 2001 From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com> Date: Thu, 4 Jul 2024 15:55:14 +0700 Subject: [PATCH 1/3] feat: integration feature report bookmark --- .../report-bookmark.controller.ts | 5 + .../report-bookmark.service.ts | 121 ++++++++++++++++-- .../report-export/report-export.service.ts | 4 +- src/modules/reports/report/report.service.ts | 4 +- .../shared/dto/report-bookmark.get.dto.ts | 4 +- .../shared/services/base-report.service.ts | 26 +++- 6 files changed, 147 insertions(+), 17 deletions(-) diff --git a/src/modules/reports/report-bookmark/report-bookmark.controller.ts b/src/modules/reports/report-bookmark/report-bookmark.controller.ts index 08df759..dfb067b 100644 --- a/src/modules/reports/report-bookmark/report-bookmark.controller.ts +++ b/src/modules/reports/report-bookmark/report-bookmark.controller.ts @@ -35,6 +35,11 @@ export class ReportBookmarkController { return await this.service.getAll(query); } + @Get('id') + async get(@Param('id') id: string) { + return await this.service.getOne(id); + } + @Get('label-history') async getAllLabelHistory(@Query() query: GetLabelReportBookmarkDto) { return await this.service.getAllLabelHistory(query); diff --git a/src/modules/reports/report-bookmark/report-bookmark.service.ts b/src/modules/reports/report-bookmark/report-bookmark.service.ts index 7cbe05f..ccf8cb6 100644 --- a/src/modules/reports/report-bookmark/report-bookmark.service.ts +++ b/src/modules/reports/report-bookmark/report-bookmark.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { BaseReportService } from '../shared/services/base-report.service'; import { CreateReportBookmarkDto } from '../shared/dto/report-bookmark.create.dto'; import { @@ -9,9 +9,13 @@ import { Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; import { ReportBookmarkModel } from '../shared/models/report-bookmark.model'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { UserProvider } from 'src/core/sessions'; -@Injectable() +@Injectable({ scope: Scope.REQUEST }) export class ReportBookmarkService extends BaseReportService { + @Inject() + protected userProvider: UserProvider; + constructor( @InjectRepository(ReportBookmarkModel, CONNECTION_NAME.DEFAULT) private repo: Repository, @@ -20,15 +24,17 @@ export class ReportBookmarkService extends BaseReportService { } async create(body: CreateReportBookmarkDto) { - return 'you hit API for create report bookmark'; + const newPayload = this.injectDefaultColumnCreate(body); + return await this.repo.save(newPayload); } async getAll(query: GetReportBookmarkDto) { const modelName = ReportBookmarkModel.name; - const requestor_id = this.userProvider.user.id; + const creator_id = this.getUser().id; const unique_names = query.unique_names; const group_names = query.group_names; + const types = query.types; const qb = this.repo .createQueryBuilder(modelName) @@ -37,28 +43,123 @@ export class ReportBookmarkService extends BaseReportService { query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); } if (group_names) { - query.andWhere(`group_name =IN (:...group_names)`, { group_names }); + query.andWhere(`group_name IN (:...group_names)`, { group_names }); } - query.andWhere(`requestor_id = :requestor_id`, { requestor_id }); + + if (types) { + query.andWhere(`type IN (:...types)`, { types }); + } + + query.andWhere(`creator_id = :creator_id`, { creator_id }); }) .orderBy(`${modelName}.created_at`, 'DESC'); return await qb.getMany(); } + async getOne(id: string) { + return await this.repo.findOneBy({ id }); + } + async getAllLabelHistory(query: GetLabelReportBookmarkDto) { - return 'you hit API for get all label history report bookmark'; + const modelName = ReportBookmarkModel.name; + + const creator_id = this.getUser().id; + const unique_names = query.unique_names; + const group_names = query.group_names; + const types = query.types; + + const qb = this.repo + .createQueryBuilder(modelName) + .select(`${modelName}.label`) + .where((query) => { + if (unique_names) { + query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); + } + if (group_names) { + query.andWhere(`group_name IN (:...group_names)`, { group_names }); + } + + if (types) { + query.andWhere(`type IN (:...types)`, { types }); + } + + query.andWhere(`creator_id = :creator_id`, { creator_id }); + }) + .distinct(true); + + const newData = await qb.getRawMany(); + return newData.map((el) => el.ReportBookmarkModel_label); } async applied(id: string) { - return 'you hit API for applied report bookmark'; + await this.repo + .createQueryBuilder() + .update(ReportBookmarkModel) + .set({ applied: true }) + .where((query) => { + query.andWhere(`id = :id`, { id }); + }) + .execute(); + + const data = await this.appliedFalseBookmark(id); + const group_name = data.group_name; + const unique_name = data.unique_name; + const type = data.type; + + return await this.getAll({ + group_names: [group_name], + unique_names: [unique_name], + types: [type], + }); } async unapplied(id: string) { - return 'you hit API for unapplied report bookmark'; + await this.repo + .createQueryBuilder() + .update(ReportBookmarkModel) + .set({ applied: false }) + .where((query) => { + query.andWhere(`id = :id`, { id }); + }) + .execute(); + + const data = await this.getOne(id); + const group_name = data.group_name; + const unique_name = data.unique_name; + const type = data.type; + + return await this.getAll({ + group_names: [group_name], + unique_names: [unique_name], + types: [type], + }); } async delete(id: string) { - return 'you hit API for delete report bookmark'; + await this.repo.delete(id); + return { + success: true, + message: `Successfully deleted bookmark with id "${id}"`, + }; + } + + async appliedFalseBookmark(id: string) { + const data = await this.getOne(id); + const creator_id = data.creator_id; + const type = data.type; + + await this.repo + .createQueryBuilder() + .update(ReportBookmarkModel) + .set({ applied: false }) + .where((query) => { + query.andWhere(`id != :id`, { id }); + query.andWhere(`creator_id = :creator_id`, { creator_id }); + query.andWhere(`type = :type`, { type }); + }) + .execute(); + + return data; } } diff --git a/src/modules/reports/report-export/report-export.service.ts b/src/modules/reports/report-export/report-export.service.ts index 1586329..b8096c4 100644 --- a/src/modules/reports/report-export/report-export.service.ts +++ b/src/modules/reports/report-export/report-export.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Scope } from '@nestjs/common'; import { BaseReportService } from '../shared/services/base-report.service'; import { CreateReportExportDto } from '../shared/dto/report-export.create.dto'; import { @@ -11,7 +11,7 @@ import { ExportReportHistoryModel } from '../shared/models/export-report-history import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; import { DataSource, Repository } from 'typeorm'; -@Injectable() +@Injectable({ scope: Scope.REQUEST }) export class ReportExportService extends BaseReportService { constructor( @InjectDataSource(CONNECTION_NAME.DEFAULT) diff --git a/src/modules/reports/report/report.service.ts b/src/modules/reports/report/report.service.ts index 9bcb8fe..ff5f488 100644 --- a/src/modules/reports/report/report.service.ts +++ b/src/modules/reports/report/report.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, Scope } from '@nestjs/common'; import { BaseReportService } from '../shared/services/base-report.service'; import { GetReportConfigDto } from '../shared/dto/report-config.get.dto'; import { GetReportDataDto } from '../shared/dto/report-data.get.dto'; @@ -11,7 +11,7 @@ import { ReportQueryBuilder } from '../shared/helpers'; import { DATA_FORMAT } from '../shared/constant'; import { roundingCurrency } from '../shared/helpers/rounding-currency'; -@Injectable() +@Injectable({ scope: Scope.REQUEST }) export class ReportService extends BaseReportService { private readonly logger = new Logger(ReportService.name); diff --git a/src/modules/reports/shared/dto/report-bookmark.get.dto.ts b/src/modules/reports/shared/dto/report-bookmark.get.dto.ts index b3d8ba2..655828c 100644 --- a/src/modules/reports/shared/dto/report-bookmark.get.dto.ts +++ b/src/modules/reports/shared/dto/report-bookmark.get.dto.ts @@ -7,7 +7,7 @@ export class GetReportBookmarkDto { @Transform((body) => { return Array.isArray(body.value) ? body.value : [body.value]; }) - group_names?: string; + group_names?: string[]; @ApiProperty({ type: ['string'], required: false }) @Transform((body) => { @@ -27,7 +27,7 @@ export class GetLabelReportBookmarkDto { @Transform((body) => { return Array.isArray(body.value) ? body.value : [body.value]; }) - group_names?: string; + group_names?: string[]; @ApiProperty({ type: ['string'], required: false }) @Transform((body) => { diff --git a/src/modules/reports/shared/services/base-report.service.ts b/src/modules/reports/shared/services/base-report.service.ts index dbaefd8..bed19b0 100644 --- a/src/modules/reports/shared/services/base-report.service.ts +++ b/src/modules/reports/shared/services/base-report.service.ts @@ -3,7 +3,7 @@ import { UserProvider } from 'src/core/sessions'; import { BLANK_USER } from 'src/core/strings/constants/base.constants'; @Injectable() -export class BaseReportService { +export class BaseReportService { @Inject() protected userProvider: UserProvider; @@ -14,4 +14,28 @@ export class BaseReportService { return BLANK_USER; } } + + injectDefaultColumnCreate(payload: PayloadEntity): any { + const currentDate = new Date().getTime(); + const user = this.getUser(); + return { + ...payload, + creator_id: user.id, + creator_name: user.name, + editor_id: user.id, + editor_name: user.name, + created_at: currentDate, + updated_at: currentDate, + }; + } + + injectDefaultColumnUpdate(payload: PayloadEntity) { + const user = this.getUser(); + return { + ...payload, + editor_id: user.id, + editor_name: user.name, + updated_at: new Date().getTime(), + }; + } } -- 2.40.1 From ed66b88dd042b702962357b9253483b39d29e97b Mon Sep 17 00:00:00 2001 From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com> Date: Thu, 4 Jul 2024 17:22:56 +0700 Subject: [PATCH 2/3] feat: integration create export report --- .gitignore | 5 +- env/env.development | 2 + package.json | 3 +- .../report-export/report-export.controller.ts | 4 +- .../report-export/report-export.service.ts | 280 +++++++++++- src/modules/reports/report/report.service.ts | 19 +- .../shared/dto/report-export.create.dto.ts | 7 +- src/modules/reports/shared/helpers/index.ts | 2 + .../shared/helpers/string-formatter.ts | 64 +++ yarn.lock | 418 ++++++++++++++++-- 10 files changed, 756 insertions(+), 48 deletions(-) create mode 100644 src/modules/reports/shared/helpers/string-formatter.ts diff --git a/.gitignore b/.gitignore index fac794b..5b81307 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,7 @@ docker-compose.yml !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file +!.vscode/extensions.json + +# IGNORE UPLOAD FOLDER +/uploads \ No newline at end of file diff --git a/env/env.development b/env/env.development index c2c1623..bd26af1 100644 --- a/env/env.development +++ b/env/env.development @@ -15,3 +15,5 @@ DEFAULT_DB_NAME="pos" ELASTIC_APM_ACTIVATE=true ELASTIC_APM_SERVICE_NAME="Skyworld POS" ELASTIC_APM_SERVER_URL="http://172.10.10.10:8200" + +EXPORT_LIMIT_PARTITION=200 \ No newline at end of file diff --git a/package.json b/package.json index 6aaad51..4728bd1 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "class-validator": "^0.14.1", "dotenv": "^16.4.5", "elastic-apm-node": "^4.5.4", + "exceljs": "^4.4.0", "googleapis": "^140.0.0", "nano": "^10.1.3", "pg": "^8.11.5", @@ -90,4 +91,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} \ No newline at end of file +} diff --git a/src/modules/reports/report-export/report-export.controller.ts b/src/modules/reports/report-export/report-export.controller.ts index c18a5a3..5ed60e8 100644 --- a/src/modules/reports/report-export/report-export.controller.ts +++ b/src/modules/reports/report-export/report-export.controller.ts @@ -5,7 +5,6 @@ import { Get, Param, Post, - Put, Query, } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; @@ -28,7 +27,8 @@ export class ReportExportController { @Post() async create(@Body() body: CreateReportExportDto) { - return await this.service.create(body); + await this.service.create(body); + return { message: 'Processing request export data.' }; } @Get() diff --git a/src/modules/reports/report-export/report-export.service.ts b/src/modules/reports/report-export/report-export.service.ts index b8096c4..492f494 100644 --- a/src/modules/reports/report-export/report-export.service.ts +++ b/src/modules/reports/report-export/report-export.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Scope } from '@nestjs/common'; +import { Injectable, Logger, Scope } from '@nestjs/common'; import { BaseReportService } from '../shared/services/base-report.service'; import { CreateReportExportDto } from '../shared/dto/report-export.create.dto'; import { @@ -10,9 +10,23 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { ExportReportHistoryModel } from '../shared/models/export-report-history.model'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; import { DataSource, Repository } from 'typeorm'; +import { ReportConfigs } from '../shared/configs'; +import { ReportConfigEntity } from '../shared/entities/report-config.entity'; +import { ReportQueryBuilder, capitalizeEachWord } from '../shared/helpers'; +import { DATA_FORMAT, REPORT_HISTORY_STATUS } from '../shared/constant'; + +import * as fs from 'fs'; +import * as path from 'path'; +import * as ExcelJS from 'exceljs'; +import { roundingCurrency } from '../shared/helpers'; @Injectable({ scope: Scope.REQUEST }) export class ReportExportService extends BaseReportService { + private readonly logger = new Logger(ReportExportService.name); + private readonly exportLimitPartition = process.env.EXPORT_LIMIT_PARTITION + ? parseInt(process.env.EXPORT_LIMIT_PARTITION) + : 100; + constructor( @InjectDataSource(CONNECTION_NAME.DEFAULT) private dataSource: DataSource, @@ -23,8 +37,270 @@ export class ReportExportService extends BaseReportService { super(); } + getReportConfigByUniqueName(group_name, unique_name): ReportConfigEntity { + return ReportConfigs.find( + (item) => + item.unique_name === unique_name && item.group_name === group_name, + ); + } + + changeTimeZone(date, timeZone) { + if (typeof date === 'string') { + return new Date( + new Date(date).toLocaleString('en-US', { + timeZone, + }), + ); + } + + return new Date( + date.toLocaleString('en-US', { + timeZone, + }), + ); + } + async create(body: CreateReportExportDto) { - return 'you hit API for create report export'; + let historyDataID = undefined; + try { + const config = this.getReportConfigByUniqueName( + body.group_name, + body.unique_name, + ); + const query_model = body.query_model; + const columnState = body.column_state; + const timeZone = body.time_zone; + const fName = body.file_name; + const defaultData = this.injectDefaultColumnCreate({}); + + query_model.groupKeys = []; // set [] on report only for get all data; + const DATA_SOURCE_NAME = 'dataSource'; + + const builder = new ReportQueryBuilder(config, query_model); + const SQL_EXPORT = builder.getSqlExport(); + const SQL_EXPORT_COUNT = builder.getSqlCountExport(); + + const count = await this[`${DATA_SOURCE_NAME}`].query(SQL_EXPORT_COUNT); + const totalRow = parseInt(count[0].count); + + const limit = this.exportLimitPartition; + + const partitionOffset = Array.from( + { length: Math.ceil(totalRow / limit) }, + (_, i) => i * limit, + ); + + const fileName = `${fName ?? config.label} (${Number(new Date())})`; + + const exportHistory = { + id: undefined, + group_name: config.group_name, + unique_name: config.unique_name, + label: config.label, + file_name: fileName, + file_url: null, + total_data: totalRow, + processing_data: 0, + status: REPORT_HISTORY_STATUS.PROCESSING, + last_process_offset: 0, + last_process_limit: limit, + query_export: '', + ...defaultData, + }; + + this.logger.verbose(`EXPORT ${fileName} VIA STREAMING`); + this.logger.verbose(`EXPORT_LIMIT_PARTITION = ${limit}`); + + const directory = './uploads/report-data/'; + const filePath = path.join(directory, fileName); + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory, { recursive: true }); + } + const workbookOptions = { + filename: filePath + '.xlsx', + useStyles: true, + }; + + exportHistory.file_url = fileName + '.xlsx'; + + //save export history + const historySave: any = await this.exportHistoryRepo.save( + exportHistory as any, + ); + exportHistory.id = historySave.id; + historyDataID = historySave.id; + + // Create a new workbook + const workbook = new ExcelJS.stream.xlsx.WorkbookWriter(workbookOptions); + // Create a new worksheet + const reportSheet = workbook.addWorksheet('Report'); + + const columnStateIds = columnState.map((i) => i.colId); + let configColumns = config.column_configs; + const rowGroups = query_model?.rowGroupCols ?? []; + + if (rowGroups.length > 0) { + const valueCol = query_model?.valueCols ?? []; + configColumns = [...rowGroups, ...valueCol].map((i) => { + return config.column_configs.find((conf) => conf.column === i.id); + }); + } + + const newConfigColumn = columnStateIds + .map((i) => { + const findData = configColumns.find((col) => col.column === i); + return findData; + }) + .filter(Boolean); + + reportSheet.columns = newConfigColumn.map((conf) => { + const isDate = + conf.format === DATA_FORMAT.DATE_EPOCH || + conf.format === DATA_FORMAT.DATE_TIMESTAMP; + const isCurrency = + conf.format === DATA_FORMAT.CURRENCY || + conf.format === DATA_FORMAT.MINUS_CURRENCY; + const isNumber = conf.format === DATA_FORMAT.NUMBER; + + const excelColumn = { + header: conf.label, + key: conf.column, + width: 20, + }; + + if (isDate) { + Object.assign(excelColumn, { style: { numFmt: 'dd/mm/yyyy' } }); + } else if (isCurrency) { + Object.assign(excelColumn, { style: { numFmt: '0.00' } }); + } else if (isNumber) { + Object.assign(excelColumn, { style: { numFmt: '' } }); + } + + return excelColumn; + }); + + // Making first line in excel bold + reportSheet.getRow(1).eachCell((cell) => { + cell.font = { bold: true }; + }); + + for (const offset of partitionOffset) { + const limitSql = ` limit ${limit} offset ${offset} `; + const newSqlExport = SQL_EXPORT + limitSql; + + const data = await this[`${DATA_SOURCE_NAME}`].query(newSqlExport); + + //update export history + exportHistory.processing_data += data.length; + exportHistory.updated_at = Number(new Date()); + exportHistory.last_process_offset = offset; + exportHistory.query_export = newSqlExport; + + await this.exportHistoryRepo.save(exportHistory as any); + + for (const item of data) { + const realItem = {}; + for (const itemKey of Object.keys(item)) { + const confCol = configColumns.find((c) => c.column === itemKey); + const isStatus = confCol.format === DATA_FORMAT.STATUS; + const isDate = confCol.format === DATA_FORMAT.DATE_EPOCH; + const isDateTimestamp = + confCol.format === DATA_FORMAT.DATE_TIMESTAMP; + const isNumber = confCol.format === DATA_FORMAT.NUMBER; + const isCurrency = confCol.format === DATA_FORMAT.CURRENCY; + const isMinusCurrency = + confCol.format === DATA_FORMAT.MINUS_CURRENCY; + const isBoolean = confCol.format === DATA_FORMAT.BOOLEAN; + const isPercentage = confCol.format === DATA_FORMAT.PERCENTAGE; + + const isSetInitNull = isNumber || isCurrency || isMinusCurrency; + + const isTextUpperCase = + confCol.format === DATA_FORMAT.TEXT_UPPERCASE; + const isTextLowerCase = + confCol.format === DATA_FORMAT.TEXT_LOWERCASE; + + if (isStatus) { + if (item[itemKey]) { + Object.assign(realItem, { + [`${itemKey}`]: capitalizeEachWord( + item[itemKey].split('_').join(' '), + ), + }); + } + } else if (isDate) { + if (item[itemKey]) { + Object.assign(realItem, { + [`${itemKey}`]: this.changeTimeZone( + new Date(Number(item[itemKey])), + timeZone, + ), + }); + } + } else if (isDateTimestamp) { + if (item[itemKey]) { + Object.assign(realItem, { + [`${itemKey}`]: this.changeTimeZone( + new Date(item[itemKey]), + timeZone, + ), + }); + } + } else if (isSetInitNull) { + const realValue = item[itemKey] ?? 0; + let valueItem = realValue; + if (isCurrency) { + valueItem = roundingCurrency(realValue); + } else if (isMinusCurrency) { + valueItem = roundingCurrency(realValue) * -1; + } + + Object.assign(realItem, { [`${itemKey}`]: Number(valueItem) }); + } else if (isPercentage) { + const realValue = item[itemKey] + ? `${item[itemKey]}%` + : item[itemKey]; + Object.assign(realItem, { [`${itemKey}`]: realValue }); + } else if (isBoolean) { + let realValue = ''; + if (item[itemKey] === true || item[itemKey] === 1) + realValue = 'Yes'; + else if (item[itemKey] === false || item[itemKey] === 0) + realValue = 'No'; + Object.assign(realItem, { [`${itemKey}`]: realValue }); + } else if (isTextUpperCase) { + Object.assign(realItem, { + [`${itemKey}`]: item[itemKey]?.toUpperCase(), + }); + } else if (isTextLowerCase) { + Object.assign(realItem, { + [`${itemKey}`]: item[itemKey]?.toLowerCase(), + }); + } else { + Object.assign(realItem, { [`${itemKey}`]: item[itemKey] }); + } + } + + reportSheet.addRow(realItem).commit(); + } + } + + reportSheet.commit(); + workbook.commit(); + + // update file_url on data export history using scrip bellow + // exportHistory.file_url = fileName + '.xlsx'; + exportHistory.total_data = exportHistory.processing_data; + exportHistory.status = 'done'; + exportHistory.updated_at = Number(new Date()); + await this.exportHistoryRepo.save(exportHistory as any); + } catch (error) { + this.exportHistoryRepo.update(historyDataID, { + status: REPORT_HISTORY_STATUS.FAILED, + }); + this.logger.error(error); + return new Error(error); + } } async getAll(query: GetReportExportDto) { diff --git a/src/modules/reports/report/report.service.ts b/src/modules/reports/report/report.service.ts index ff5f488..4fa06b7 100644 --- a/src/modules/reports/report/report.service.ts +++ b/src/modules/reports/report/report.service.ts @@ -9,7 +9,7 @@ import { DataSource } from 'typeorm'; import { ReportConfigEntity } from '../shared/entities/report-config.entity'; import { ReportQueryBuilder } from '../shared/helpers'; import { DATA_FORMAT } from '../shared/constant'; -import { roundingCurrency } from '../shared/helpers/rounding-currency'; +import { roundingCurrency } from '../shared/helpers'; @Injectable({ scope: Scope.REQUEST }) export class ReportService extends BaseReportService { @@ -39,14 +39,20 @@ export class ReportService extends BaseReportService { return configs; } - getReportConfigByUniqueName(unique_name): ReportConfigEntity { - return ReportConfigs.find((item) => item.unique_name === unique_name); + getReportConfigByUniqueName(group_name, unique_name): ReportConfigEntity { + return ReportConfigs.find( + (item) => + item.unique_name === unique_name && item.group_name === group_name, + ); } async getReportData(body: GetReportDataDto) { try { const queryModel = body.query_model; - const reportConfig = this.getReportConfigByUniqueName(body.unique_name); + const reportConfig = this.getReportConfigByUniqueName( + body.group_name, + body.unique_name, + ); const builder = new ReportQueryBuilder(reportConfig, queryModel); const SQL = builder.getSql(); const queryResult = await this.dataSource.query(SQL); @@ -125,7 +131,10 @@ export class ReportService extends BaseReportService { async getReportMeta(body: GetReportDataDto) { try { const queryModel = body.query_model; - const reportConfig = this.getReportConfigByUniqueName(body.unique_name); + const reportConfig = this.getReportConfigByUniqueName( + body.group_name, + body.unique_name, + ); const builder = new ReportQueryBuilder(reportConfig, queryModel); const SQL_COUNT = builder.getSqlCount(); const queryResult = await this.dataSource.query(SQL_COUNT); diff --git a/src/modules/reports/shared/dto/report-export.create.dto.ts b/src/modules/reports/shared/dto/report-export.create.dto.ts index fd66fa4..973dd6a 100644 --- a/src/modules/reports/shared/dto/report-export.create.dto.ts +++ b/src/modules/reports/shared/dto/report-export.create.dto.ts @@ -4,7 +4,7 @@ import { IsString, IsNumber, IsOptional, IsArray } from 'class-validator'; import { ColumnStateEntity } from '../entities/query-model.entity'; export class CreateReportExportDto extends GetReportDataDto { - @ApiProperty({ name: 'time_zone', required: true }) + @ApiProperty({ name: 'time_zone', required: true, default: 'Asia/Jakarta' }) @IsString() time_zone: string; @@ -16,6 +16,11 @@ export class CreateReportExportDto extends GetReportDataDto { name: 'column_state', type: [Object], required: true, + default: [ + { colId: 'main__created_at' }, + { colId: 'main__updated_at' }, + { colId: 'main__name' }, + ], }) @IsOptional() @IsArray() diff --git a/src/modules/reports/shared/helpers/index.ts b/src/modules/reports/shared/helpers/index.ts index 0fe5354..3f9a6a4 100644 --- a/src/modules/reports/shared/helpers/index.ts +++ b/src/modules/reports/shared/helpers/index.ts @@ -1 +1,3 @@ export * from './query-builder'; +export * from './rounding-currency'; +export * from './string-formatter'; diff --git a/src/modules/reports/shared/helpers/string-formatter.ts b/src/modules/reports/shared/helpers/string-formatter.ts new file mode 100644 index 0000000..be9f603 --- /dev/null +++ b/src/modules/reports/shared/helpers/string-formatter.ts @@ -0,0 +1,64 @@ +interface CurrencyEntity { + value: number; + currency?: ' IDR' | 'EUR' | 'JPY'; +} +export interface IStringFormatter { + capitalizeFistWord(string: string): string; + capitalizeEachWord(string: string): string; + stringLimiter(string: string, limit: number): string; + upperCase(string: string): string; + lowerCase(string: string): string; + currency(payload: CurrencyEntity): string; +} + +export class BaseStringFormatter implements IStringFormatter { + capitalizeFistWord(string: string): string { + if (typeof string !== 'string') return ''; + return string.charAt(0).toUpperCase() + string.slice(1); + } + + capitalizeEachWord(string: string): string { + if (typeof string !== 'string') return ''; + const newStringSplit = string.split(' '); + const newString = newStringSplit.map((item) => { + return item.charAt(0).toUpperCase() + item.slice(1); + }); + return newString.join(' '); + } + + stringLimiter(string: string, limit: number): string { + if (!limit || !string) return string; + return string.length > limit ? `${string.substring(0, limit)} ...` : string; + } + + upperCase(string: string): string { + if (!string) return ''; + return string.toUpperCase(); + } + lowerCase(string: string): string { + if (!string) return ''; + return string.toLowerCase(); + } + currency(payload: CurrencyEntity): any { + const { value, currency = 'IDR' } = payload; + if (!value) return ''; + return new Intl.NumberFormat('en-IN', { + style: 'currency', + currency, + }).format(value); + } +} + +const stringFormatter = new BaseStringFormatter(); +export const { + capitalizeFistWord, + capitalizeEachWord, + stringLimiter, + upperCase, + lowerCase, + currency, +} = stringFormatter; + +export function transformProductCode(code: string) { + return code?.split('-').join(''); +} diff --git a/yarn.lock b/yarn.lock index 5de5fa7..557fec5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -393,6 +393,31 @@ resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.4.1.tgz#5d5e8aee8fce48f5e189bf730ebd1f758f491451" integrity sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg== +"@fast-csv/format@4.3.5": + version "4.3.5" + resolved "https://registry.yarnpkg.com/@fast-csv/format/-/format-4.3.5.tgz#90d83d1b47b6aaf67be70d6118f84f3e12ee1ff3" + integrity sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A== + dependencies: + "@types/node" "^14.0.1" + lodash.escaperegexp "^4.1.2" + lodash.isboolean "^3.0.3" + lodash.isequal "^4.5.0" + lodash.isfunction "^3.0.9" + lodash.isnil "^4.0.0" + +"@fast-csv/parse@4.3.6": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@fast-csv/parse/-/parse-4.3.6.tgz#ee47d0640ca0291034c7aa94039a744cfb019264" + integrity sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA== + dependencies: + "@types/node" "^14.0.1" + lodash.escaperegexp "^4.1.2" + lodash.groupby "^4.6.0" + lodash.isfunction "^3.0.9" + lodash.isnil "^4.0.0" + lodash.isundefined "^3.0.1" + lodash.uniq "^4.5.0" + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -1140,6 +1165,11 @@ dependencies: undici-types "~5.26.4" +"@types/node@^14.0.1": + version "14.18.63" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b" + integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ== + "@types/node@^20.12.13": version "20.12.13" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.13.tgz#90ed3b8a4e52dd3c5dc5a42dde5b85b74ad8ed88" @@ -1639,6 +1669,51 @@ append-field@^1.0.0: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== +archiver-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" + integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== + dependencies: + glob "^7.1.4" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^2.0.0" + +archiver-utils@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-3.0.4.tgz#a0d201f1cf8fce7af3b5a05aea0a337329e96ec7" + integrity sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw== + dependencies: + glob "^7.2.3" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + +archiver@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.2.tgz#99991d5957e53bd0303a392979276ac4ddccf3b0" + integrity sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw== + dependencies: + archiver-utils "^2.1.0" + async "^3.2.4" + buffer-crc32 "^0.2.1" + readable-stream "^3.6.0" + readdir-glob "^1.1.2" + tar-stream "^2.2.0" + zip-stream "^4.1.0" + are-we-there-yet@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" @@ -1706,6 +1781,11 @@ async-value@^1.2.2: resolved "https://registry.yarnpkg.com/async-value/-/async-value-1.2.2.tgz#84517a1e7cb6b1a5b5e181fa31be10437b7fb125" integrity sha512-8rwtYe32OAS1W9CTwvknoyts+mc3ta8N7Pi0h7AjkMaKvsFbr39K+gEfZ7Z81aPXQ1sK5M23lgLy1QfZpcpadQ== +async@^3.2.4: + version "3.2.5" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1810,6 +1890,11 @@ bcrypt@^5.1.1: "@mapbox/node-pre-gyp" "^1.0.11" node-addon-api "^5.0.0" +big-integer@^1.6.17: + version "1.6.52" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" + integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== + bignumber.js@^9.0.0: version "9.1.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" @@ -1825,7 +1910,15 @@ binary-search@^1.3.3: resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.6.tgz#e32426016a0c5092f0f3598836a1c7da3560565c" integrity sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA== -bl@^4.1.0: +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg== + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + +bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -1834,6 +1927,11 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA== + body-parser@1.20.2: version "1.20.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" @@ -1905,6 +2003,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" @@ -1915,6 +2018,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -1931,6 +2039,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ== + busboy@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" @@ -1991,6 +2104,13 @@ capital-case@^1.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ== + dependencies: + traverse ">=0.3.0 <0.4" + chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -2259,6 +2379,16 @@ component-emitter@^1.3.0: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== +compress-commons@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.2.tgz#6542e59cb63e1f46a8b21b0e06f9a32e4c8b06df" + integrity sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg== + dependencies: + buffer-crc32 "^0.2.13" + crc32-stream "^4.0.2" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2353,6 +2483,19 @@ cosmiconfig@^8.2.0: parse-json "^5.2.0" path-type "^4.0.0" +crc-32@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + +crc32-stream@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.3.tgz#85dd677eb78fa7cad1ba17cc506a597d41fc6f33" + integrity sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw== + dependencies: + crc-32 "^1.2.0" + readable-stream "^3.4.0" + create-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" @@ -2380,7 +2523,7 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -dayjs@^1.11.9: +dayjs@^1.11.9, dayjs@^1.8.34: version "1.11.11" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== @@ -2550,6 +2693,13 @@ dotenv@16.4.5, dotenv@^16.0.3, dotenv@^16.4.5: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA== + dependencies: + readable-stream "^2.0.2" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -2863,6 +3013,21 @@ events@^3.2.0, events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +exceljs@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/exceljs/-/exceljs-4.4.0.tgz#cfb1cb8dcc82c760a9fc9faa9e52dadab66b0156" + integrity sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg== + dependencies: + archiver "^5.0.0" + dayjs "^1.8.34" + fast-csv "^4.3.1" + jszip "^3.10.1" + readable-stream "^3.6.0" + saxes "^5.0.1" + tmp "^0.2.0" + unzipper "^0.10.11" + uuid "^8.3.0" + execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -2957,6 +3122,14 @@ faker@4.1.0: resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" integrity sha512-ILKg69P6y/D8/wSmDXw35Ly0re8QzQ8pMfBCflsGiZG2ZjMUNLYNexA6lz5pkmJlepVdsiDFUxYAzPQ9/+iGLA== +fast-csv@^4.3.1: + version "4.3.6" + resolved "https://registry.yarnpkg.com/fast-csv/-/fast-csv-4.3.6.tgz#70349bdd8fe4d66b1130d8c91820b64a21bc4a63" + integrity sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw== + dependencies: + "@fast-csv/format" "4.3.5" + "@fast-csv/parse" "4.3.6" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -3194,6 +3367,11 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" @@ -3225,6 +3403,16 @@ fsevents@^2.3.2, fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" @@ -3353,7 +3541,7 @@ glob@^10.3.10: minipass "^7.1.2" path-scurry "^1.11.1" -glob@^7.0.0, glob@^7.1.3, glob@^7.1.4: +glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.2.3: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3469,7 +3657,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -3636,6 +3824,11 @@ ignore@^5.2.0, ignore@^5.2.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -3680,7 +3873,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4463,6 +4656,16 @@ jsonwebtoken@9.0.2: ms "^2.1.1" semver "^7.5.4" +jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + jwa@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -4514,6 +4717,13 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +lazystream@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" + integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== + dependencies: + readable-stream "^2.0.5" + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -4532,6 +4742,13 @@ libphonenumber-js@^1.10.53: resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.2.tgz#9ddd7d1a1e1be0e7c596c7e09487c362b4f1210c" integrity sha512-V9mGLlaXN1WETzqQvSu6qf6XVAr3nFuJvWsHcuzCCCo6xUKawwSxOPTpan5CGOSKTn5w/bQuCZcLPJkyysgC3w== +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + liftoff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-4.0.0.tgz#1a463b9073335cd425cdaa3b468996f7d66d2d81" @@ -4551,6 +4768,11 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ== + loader-runner@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" @@ -4570,11 +4792,36 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== + +lodash.escaperegexp@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" + integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw== + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== +lodash.groupby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" + integrity sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw== + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -4585,11 +4832,26 @@ lodash.isboolean@^3.0.3: resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + +lodash.isfunction@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== + lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== +lodash.isnil@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz#49e28cd559013458c814c5479d3c663a21bfaa6c" + integrity sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng== + lodash.isnumber@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" @@ -4605,6 +4867,11 @@ lodash.isstring@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== +lodash.isundefined@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" + integrity sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA== + lodash.memoize@4.x: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -4625,6 +4892,16 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== +lodash.union@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + lodash@4.17.21, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -4814,6 +5091,13 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^5.1.0: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + minimatch@^8.0.2: version "8.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229" @@ -4863,7 +5147,7 @@ minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -mkdirp@^0.5.4: +"mkdirp@>=0.5 0", mkdirp@^0.5.4: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -5266,6 +5550,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + param-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" @@ -5675,7 +5964,7 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -readable-stream@^2.2.2: +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.2.2, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -5688,7 +5977,7 @@ readable-stream@^2.2.2: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -5708,6 +5997,13 @@ readable-stream@^4.0.0: process "^0.11.10" string_decoder "^1.3.0" +readdir-glob@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" + integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== + dependencies: + minimatch "^5.1.0" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -5838,6 +6134,13 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rimraf@2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.1.tgz#bd33364f67021c5b79e93d7f4fa0568c7c21b755" @@ -5896,6 +6199,13 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + schema-utils@^3.1.1, schema-utils@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" @@ -5977,6 +6287,11 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" +setimmediate@^1.0.5, setimmediate@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -6156,16 +6471,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6206,14 +6512,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -6312,6 +6611,17 @@ tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tar-stream@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@^6.1.11: version "6.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" @@ -6399,6 +6709,11 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@^0.2.0: + version "0.2.3" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" + integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -6433,6 +6748,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ== + tree-kill@1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -6649,6 +6969,22 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== +unzipper@^0.10.11: + version "0.10.14" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.14.tgz#d2b33c977714da0fbc0f82774ad35470a7c962b1" + integrity sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + update-browserslist-db@^1.0.13: version "1.0.16" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356" @@ -6698,6 +7034,11 @@ uuid@9.0.1, uuid@^9.0.0, uuid@^9.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -6852,7 +7193,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -6870,15 +7211,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -6901,6 +7233,11 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -6996,3 +7333,12 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zip-stream@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.1.tgz#1337fe974dbaffd2fa9a1ba09662a66932bd7135" + integrity sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ== + dependencies: + archiver-utils "^3.0.4" + compress-commons "^4.1.2" + readable-stream "^3.6.0" -- 2.40.1 From f9c36582e18015a47901c96ea183864b8bdc8479 Mon Sep 17 00:00:00 2001 From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com> Date: Thu, 4 Jul 2024 18:32:03 +0700 Subject: [PATCH 3/3] feat: integration export report --- package.json | 1 + .../report-export/report-export.service.ts | 154 +++++++++++++++++- .../shared/dto/report-export.get.dto.ts | 6 - yarn.lock | 5 + 4 files changed, 154 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 4728bd1..68b66fd 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "elastic-apm-node": "^4.5.4", "exceljs": "^4.4.0", "googleapis": "^140.0.0", + "moment": "^2.30.1", "nano": "^10.1.3", "pg": "^8.11.5", "plop": "^4.0.1", diff --git a/src/modules/reports/report-export/report-export.service.ts b/src/modules/reports/report-export/report-export.service.ts index 492f494..41034ea 100644 --- a/src/modules/reports/report-export/report-export.service.ts +++ b/src/modules/reports/report-export/report-export.service.ts @@ -1,4 +1,10 @@ -import { Injectable, Logger, Scope } from '@nestjs/common'; +import { + Injectable, + Logger, + NotFoundException, + Scope, + UnprocessableEntityException, +} from '@nestjs/common'; import { BaseReportService } from '../shared/services/base-report.service'; import { CreateReportExportDto } from '../shared/dto/report-export.create.dto'; import { @@ -19,6 +25,8 @@ import * as fs from 'fs'; import * as path from 'path'; import * as ExcelJS from 'exceljs'; import { roundingCurrency } from '../shared/helpers'; +import { createPaginationMeta } from 'src/core/response/domain/utils/pagination-meta.helper'; +import * as moment from 'moment'; @Injectable({ scope: Scope.REQUEST }) export class ReportExportService extends BaseReportService { @@ -97,7 +105,7 @@ export class ReportExportService extends BaseReportService { group_name: config.group_name, unique_name: config.unique_name, label: config.label, - file_name: fileName, + file_name: fName, file_url: null, total_data: totalRow, processing_data: 0, @@ -304,18 +312,152 @@ export class ReportExportService extends BaseReportService { } async getAll(query: GetReportExportDto) { - return 'you hit API for get all report export'; + const modelName = ExportReportHistoryModel.name; + + const page = query.page; + const limit = query.limit; + + const creator_id = this.getUser().id; + const group_names = query.group_names; + const unique_names = query.unique_names; + const statuses = query.statuses; + + const qb = this.exportHistoryRepo + .createQueryBuilder(modelName) + .where((query) => { + if (unique_names) { + query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); + } + if (group_names) { + query.andWhere(`group_name IN (:...group_names)`, { group_names }); + } + + if (statuses) { + query.andWhere(`status IN (:...statuses)`, { statuses }); + } + + query.andWhere(`creator_id = :creator_id`, { creator_id }); + }) + .orderBy(`${modelName}.created_at`, 'DESC'); + + const [data, total] = await qb + .take(+limit) + .skip(+limit * +page - +limit) + .getManyAndCount(); + + const meta = createPaginationMeta(page, limit, data.length, total); + + return { data, meta }; } async delete(id: string) { - return 'you hit API for delete report export'; + const findData = await this.exportHistoryRepo.findOneBy({ id }); + if (findData && findData?.file_url) { + try { + const directory = './uploads/report-data/'; + const filePath = path.join(directory, findData.file_url); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + await this.exportHistoryRepo.delete(id); + this.logger.warn( + `Successfully deleted the ${findData.file_url} file`, + ); + return { success: true, message: 'File deleted successfully' }; + } else { + throw new NotFoundException('File not found'); + } + } catch (error) { + throw new UnprocessableEntityException('File could not be deleted'); + } + } + throw new UnprocessableEntityException(); + } + + async updateFailedData(group_names, unique_names) { + const creator_id = this.getUser().id; + const aMinutesAgo = moment().subtract(5, 'minutes').valueOf(); + + await this.exportHistoryRepo + .createQueryBuilder() + .update(ExportReportHistoryModel) + .set({ + status: REPORT_HISTORY_STATUS.FAILED, + updated_at: moment().valueOf(), + }) + .where((query) => { + if (group_names) { + query.andWhere(`group_name IN (:...group_names)`, { group_names }); + } + if (unique_names) { + query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); + } + query.andWhere(`status = :status`, { + status: REPORT_HISTORY_STATUS.PROCESSING, + }); + query.andWhere(`updated_at < :aMinutesAgo`, { aMinutesAgo }); + query.andWhere(`creator_id = :creator_id`, { creator_id }); + }) + .execute(); } async getAllProcessing(query: GetReportExportProcessingDto) { - return 'you hit API for get all processing report export'; + const creator_id = this.getUser().id; + const modelName = ExportReportHistoryModel.name; + + const group_names = query.group_names; + const unique_names = query.unique_names; + + await this.updateFailedData(group_names, unique_names); + const aMinutesAgo = moment().subtract(100, 'seconds').valueOf(); + + const qb = this.exportHistoryRepo + .createQueryBuilder(modelName) + .where((query) => { + if (group_names) { + query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); + } + if (unique_names) { + query.andWhere(`group_name IN (:...group_names)`, { group_names }); + } + + query.andWhere(`status IN (:...status)`, { + status: ['processing', 'failed', 'done'], + }); + query.andWhere(`updated_at > :aMinutesAgo`, { aMinutesAgo }); + query.andWhere(`creator_id = :creator_id`, { creator_id }); + }) + .orderBy(`${modelName}.created_at`, 'DESC'); + + const data = await qb.getMany(); + + return { + data: data, + }; } async getListHistoryFileName(query: GetReportExportFileNameDto) { - return 'you hit API for get all file name report export'; + const modelName = ExportReportHistoryModel.name; + + const creator_id = this.getUser().id; + const unique_names = query.unique_names; + const group_names = query.group_names; + + const qb = this.exportHistoryRepo + .createQueryBuilder(modelName) + .select(`${modelName}.file_name`) + .where((query) => { + if (unique_names) { + query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); + } + if (group_names) { + query.andWhere(`group_name IN (:...group_names)`, { group_names }); + } + + query.andWhere(`creator_id = :creator_id`, { creator_id }); + }) + .distinct(true); + + const newData = await qb.getRawMany(); + return newData.map((el) => el.ExportReportHistoryModel_file_name); } } diff --git a/src/modules/reports/shared/dto/report-export.get.dto.ts b/src/modules/reports/shared/dto/report-export.get.dto.ts index 264582e..718dea4 100644 --- a/src/modules/reports/shared/dto/report-export.get.dto.ts +++ b/src/modules/reports/shared/dto/report-export.get.dto.ts @@ -46,12 +46,6 @@ export class GetReportExportProcessingDto { return Array.isArray(body.value) ? body.value : [body.value]; }) unique_names?: string[]; - - @ApiProperty({ type: ['string'], required: false }) - @Transform((body) => { - return Array.isArray(body.value) ? body.value : [body.value]; - }) - statuses?: string; } export class GetReportExportFileNameDto { diff --git a/yarn.lock b/yarn.lock index 557fec5..30ebde9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5174,6 +5174,11 @@ module-details-from-path@^1.0.3: resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== +moment@^2.30.1: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + monitor-event-loop-delay@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/monitor-event-loop-delay/-/monitor-event-loop-delay-1.0.0.tgz#b5ab78165a3bb93f2b275c50d01430c7f155d1f7" -- 2.40.1