From fce8e2c5a43d3c15c0125af0d24335aa7e9cc9cd Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 29 Aug 2024 22:39:51 +0200 Subject: [PATCH] fix: use standard ISO 8601 format for exported data --- .../server/src/lib/Transformer/Transformer.ts | 29 +++++++---- .../lib/Transformer/TransformerInjectable.ts | 7 +++ .../server/src/services/Export/ExportAls.ts | 48 +++++++++++++++++++ .../src/services/Export/ExportService.ts | 23 ++++++++- .../server/src/services/Export/constants.ts | 3 +- .../server/src/services/Import/ImportALS.ts | 7 +-- .../src/subscribers/Inventory/Inventory.ts | 2 +- 7 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 packages/server/src/services/Export/ExportAls.ts diff --git a/packages/server/src/lib/Transformer/Transformer.ts b/packages/server/src/lib/Transformer/Transformer.ts index 23613f7484..0514cd6577 100644 --- a/packages/server/src/lib/Transformer/Transformer.ts +++ b/packages/server/src/lib/Transformer/Transformer.ts @@ -2,6 +2,7 @@ import moment from 'moment'; import * as R from 'ramda'; import { includes, isFunction, isObject, isUndefined, omit } from 'lodash'; import { formatNumber, sortObjectKeysAlphabetically } from 'utils'; +import { EXPORT_DTE_FORMAT } from '@/services/Export/constants'; export class Transformer { public context: any; @@ -156,22 +157,34 @@ export class Transformer { } /** - * - * @param date - * @returns + * Format date. + * @param {} date + * @param {string} format - + * @returns {} */ - protected formatDate(date) { - return date ? moment(date).format(this.dateFormat) : ''; + protected formatDate(date, format?: string) { + // Use the export date format if the async operation is in exporting, + // otherwise use the given or default format. + const _format = this.context.exportAls.isExport + ? EXPORT_DTE_FORMAT + : format || this.dateFormat; + + return date ? moment(date).format(_format) : ''; } - protected formatDateFromNow(date){ + /** + * + * @param date + * @returns {} + */ + protected formatDateFromNow(date) { return date ? moment(date).fromNow(true) : ''; } /** * * @param number - * @returns + * @returns {} */ protected formatNumber(number, props?) { return formatNumber(number, { money: false, ...props }); @@ -181,7 +194,7 @@ export class Transformer { * * @param money * @param options - * @returns + * @returns {} */ protected formatMoney(money, options?) { return formatNumber(money, { diff --git a/packages/server/src/lib/Transformer/TransformerInjectable.ts b/packages/server/src/lib/Transformer/TransformerInjectable.ts index 128e3f9bce..5f16433121 100644 --- a/packages/server/src/lib/Transformer/TransformerInjectable.ts +++ b/packages/server/src/lib/Transformer/TransformerInjectable.ts @@ -3,12 +3,17 @@ import { isNull } from 'lodash'; import HasTenancyService from '@/services/Tenancy/TenancyService'; import { TenantMetadata } from '@/system/models'; import { Transformer } from './Transformer'; +import { ImportAls } from '@/services/Import/ImportALS'; +import { ExportAls } from '@/services/Export/ExportAls'; @Service() export class TransformerInjectable { @Inject() private tenancy: HasTenancyService; + @Inject() + private exportAls: ExportAls; + /** * Retrieves the application context of all tenant transformers. * @param {number} tenantId @@ -17,10 +22,12 @@ export class TransformerInjectable { async getApplicationContext(tenantId: number) { const i18n = this.tenancy.i18n(tenantId); const organization = await TenantMetadata.query().findOne({ tenantId }); + const exportAls = this.exportAls; return { organization, i18n, + exportAls, }; } diff --git a/packages/server/src/services/Export/ExportAls.ts b/packages/server/src/services/Export/ExportAls.ts new file mode 100644 index 0000000000..b242270092 --- /dev/null +++ b/packages/server/src/services/Export/ExportAls.ts @@ -0,0 +1,48 @@ +import { Service } from 'typedi'; +import { AsyncLocalStorage } from 'async_hooks'; + +@Service() +export class ExportAls { + private als: AsyncLocalStorage>; + + constructor() { + this.als = new AsyncLocalStorage(); + } + + /** + * Runs a callback function within the context of a new AsyncLocalStorage store. + * @param callback The function to be executed within the AsyncLocalStorage context. + * @returns The result of the callback function. + */ + public run(callback: () => T): T { + return this.als.run(new Map(), () => { + this.markAsExport(); + + return callback(); + }); + } + + /** + * Retrieves the current AsyncLocalStorage store. + * @returns The current store or undefined if not in a valid context. + */ + public getStore(): Map | undefined { + return this.als.getStore(); + } + + /** + * Marks the current context as an export operation. + * @param flag Boolean flag to set or unset the export status. Defaults to true. + */ + public markAsExport(flag: boolean = true): void { + const store = this.getStore(); + store?.set('isExport', flag); + } + /** + * Checks if the current context is an export operation. + * @returns {boolean} True if the context is an export operation, false otherwise. + */ + public get isExport(): boolean { + return !!this.getStore()?.get('isExport'); + } +} diff --git a/packages/server/src/services/Export/ExportService.ts b/packages/server/src/services/Export/ExportService.ts index eec34c9596..60fa0a7042 100644 --- a/packages/server/src/services/Export/ExportService.ts +++ b/packages/server/src/services/Export/ExportService.ts @@ -10,6 +10,7 @@ import { Errors, ExportFormat } from './common'; import { IModelMeta, IModelMetaColumn } from '@/interfaces'; import { flatDataCollections, getDataAccessor } from './utils'; import { ExportPdf } from './ExportPdf'; +import { ExportAls } from './ExportAls'; @Service() export class ExportResourceService { @@ -22,13 +23,33 @@ export class ExportResourceService { @Inject() private exportPdf: ExportPdf; + @Inject() + private exportAls: ExportAls; + + /** + * + * @param {number} tenantId + * @param {string} resourceName + * @param {ExportFormat} format + * @returns + */ + public async export( + tenantId: number, + resourceName: string, + format: ExportFormat = ExportFormat.Csv + ) { + return this.exportAls.run(() => + this.exportAlsRun(tenantId, resourceName, format) + ); + } + /** * Exports the given resource data through csv, xlsx or pdf. * @param {number} tenantId - Tenant id. * @param {string} resourceName - Resource name. * @param {ExportFormat} format - File format. */ - public async export( + public async exportAlsRun( tenantId: number, resourceName: string, format: ExportFormat = ExportFormat.Csv diff --git a/packages/server/src/services/Export/constants.ts b/packages/server/src/services/Export/constants.ts index 075bd1fc4a..b7723d38cc 100644 --- a/packages/server/src/services/Export/constants.ts +++ b/packages/server/src/services/Export/constants.ts @@ -1 +1,2 @@ -export const EXPORT_SIZE_LIMIT = 9999999; \ No newline at end of file +export const EXPORT_SIZE_LIMIT = 9999999; +export const EXPORT_DTE_FORMAT = 'YYYY-MM-DD'; diff --git a/packages/server/src/services/Import/ImportALS.ts b/packages/server/src/services/Import/ImportALS.ts index bfa69eb9f1..d1298e5bd9 100644 --- a/packages/server/src/services/Import/ImportALS.ts +++ b/packages/server/src/services/Import/ImportALS.ts @@ -8,6 +8,7 @@ export class ImportAls { constructor() { this.als = new AsyncLocalStorage(); } + /** * Runs a callback function within the context of a new AsyncLocalStorage store. * @param callback The function to be executed within the AsyncLocalStorage context. @@ -82,7 +83,7 @@ export class ImportAls { * Checks if the current context is an import operation. * @returns {boolean} True if the context is an import operation, false otherwise. */ - public isImport(): boolean { + public get isImport(): boolean { return !!this.getStore()?.get('isImport'); } @@ -90,7 +91,7 @@ export class ImportAls { * Checks if the current context is an import commit operation. * @returns {boolean} True if the context is an import commit operation, false otherwise. */ - public isImportCommit(): boolean { + public get isImportCommit(): boolean { return !!this.getStore()?.get('isImportCommit'); } @@ -98,7 +99,7 @@ export class ImportAls { * Checks if the current context is an import preview operation. * @returns {boolean} True if the context is an import preview operation, false otherwise. */ - public isImportPreview(): boolean { + public get isImportPreview(): boolean { return !!this.getStore()?.get('isImportPreview'); } } diff --git a/packages/server/src/subscribers/Inventory/Inventory.ts b/packages/server/src/subscribers/Inventory/Inventory.ts index f45a1a8f74..4229506b8c 100644 --- a/packages/server/src/subscribers/Inventory/Inventory.ts +++ b/packages/server/src/subscribers/Inventory/Inventory.ts @@ -92,7 +92,7 @@ export default class InventorySubscriber { inventoryTransactions, trx, }: IInventoryTransactionsCreatedPayload) => { - const inImportPreviewScope = this.importAls.isImportPreview(); + const inImportPreviewScope = this.importAls.isImportPreview; // Avoid running the cost items job if the async process is in import preview. if (inImportPreviewScope) return;