Merge pull request #638 from bigcapitalhq/use-standard-date-format-export

fix: use standard ISO 8601 format for exported data
This commit is contained in:
Ahmed Bouhuolia
2024-08-29 22:40:59 +02:00
committed by GitHub
7 changed files with 105 additions and 14 deletions

View File

@@ -2,6 +2,7 @@ import moment from 'moment';
import * as R from 'ramda'; import * as R from 'ramda';
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash'; import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
import { formatNumber, sortObjectKeysAlphabetically } from 'utils'; import { formatNumber, sortObjectKeysAlphabetically } from 'utils';
import { EXPORT_DTE_FORMAT } from '@/services/Export/constants';
export class Transformer { export class Transformer {
public context: any; public context: any;
@@ -156,22 +157,34 @@ export class Transformer {
} }
/** /**
* * Format date.
* @param date * @param {} date
* @returns * @param {string} format -
* @returns {}
*/ */
protected formatDate(date) { protected formatDate(date, format?: string) {
return date ? moment(date).format(this.dateFormat) : ''; // 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) : ''; return date ? moment(date).fromNow(true) : '';
} }
/** /**
* *
* @param number * @param number
* @returns * @returns {}
*/ */
protected formatNumber(number, props?) { protected formatNumber(number, props?) {
return formatNumber(number, { money: false, ...props }); return formatNumber(number, { money: false, ...props });
@@ -181,7 +194,7 @@ export class Transformer {
* *
* @param money * @param money
* @param options * @param options
* @returns * @returns {}
*/ */
protected formatMoney(money, options?) { protected formatMoney(money, options?) {
return formatNumber(money, { return formatNumber(money, {

View File

@@ -3,12 +3,17 @@ import { isNull } from 'lodash';
import HasTenancyService from '@/services/Tenancy/TenancyService'; import HasTenancyService from '@/services/Tenancy/TenancyService';
import { TenantMetadata } from '@/system/models'; import { TenantMetadata } from '@/system/models';
import { Transformer } from './Transformer'; import { Transformer } from './Transformer';
import { ImportAls } from '@/services/Import/ImportALS';
import { ExportAls } from '@/services/Export/ExportAls';
@Service() @Service()
export class TransformerInjectable { export class TransformerInjectable {
@Inject() @Inject()
private tenancy: HasTenancyService; private tenancy: HasTenancyService;
@Inject()
private exportAls: ExportAls;
/** /**
* Retrieves the application context of all tenant transformers. * Retrieves the application context of all tenant transformers.
* @param {number} tenantId * @param {number} tenantId
@@ -17,10 +22,12 @@ export class TransformerInjectable {
async getApplicationContext(tenantId: number) { async getApplicationContext(tenantId: number) {
const i18n = this.tenancy.i18n(tenantId); const i18n = this.tenancy.i18n(tenantId);
const organization = await TenantMetadata.query().findOne({ tenantId }); const organization = await TenantMetadata.query().findOne({ tenantId });
const exportAls = this.exportAls;
return { return {
organization, organization,
i18n, i18n,
exportAls,
}; };
} }

View File

@@ -0,0 +1,48 @@
import { Service } from 'typedi';
import { AsyncLocalStorage } from 'async_hooks';
@Service()
export class ExportAls {
private als: AsyncLocalStorage<Map<string, any>>;
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<T>(callback: () => T): T {
return this.als.run<T>(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<string, any> | 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');
}
}

View File

@@ -10,6 +10,7 @@ import { Errors, ExportFormat } from './common';
import { IModelMeta, IModelMetaColumn } from '@/interfaces'; import { IModelMeta, IModelMetaColumn } from '@/interfaces';
import { flatDataCollections, getDataAccessor } from './utils'; import { flatDataCollections, getDataAccessor } from './utils';
import { ExportPdf } from './ExportPdf'; import { ExportPdf } from './ExportPdf';
import { ExportAls } from './ExportAls';
@Service() @Service()
export class ExportResourceService { export class ExportResourceService {
@@ -22,13 +23,33 @@ export class ExportResourceService {
@Inject() @Inject()
private exportPdf: ExportPdf; 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. * Exports the given resource data through csv, xlsx or pdf.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @param {string} resourceName - Resource name. * @param {string} resourceName - Resource name.
* @param {ExportFormat} format - File format. * @param {ExportFormat} format - File format.
*/ */
public async export( public async exportAlsRun(
tenantId: number, tenantId: number,
resourceName: string, resourceName: string,
format: ExportFormat = ExportFormat.Csv format: ExportFormat = ExportFormat.Csv

View File

@@ -1 +1,2 @@
export const EXPORT_SIZE_LIMIT = 9999999; export const EXPORT_SIZE_LIMIT = 9999999;
export const EXPORT_DTE_FORMAT = 'YYYY-MM-DD';

View File

@@ -8,6 +8,7 @@ export class ImportAls {
constructor() { constructor() {
this.als = new AsyncLocalStorage(); this.als = new AsyncLocalStorage();
} }
/** /**
* Runs a callback function within the context of a new AsyncLocalStorage store. * Runs a callback function within the context of a new AsyncLocalStorage store.
* @param callback The function to be executed within the AsyncLocalStorage context. * @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. * Checks if the current context is an import operation.
* @returns {boolean} True if the context is an import operation, false otherwise. * @returns {boolean} True if the context is an import operation, false otherwise.
*/ */
public isImport(): boolean { public get isImport(): boolean {
return !!this.getStore()?.get('isImport'); return !!this.getStore()?.get('isImport');
} }
@@ -90,7 +91,7 @@ export class ImportAls {
* Checks if the current context is an import commit operation. * Checks if the current context is an import commit operation.
* @returns {boolean} True if the context is an import commit operation, false otherwise. * @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'); return !!this.getStore()?.get('isImportCommit');
} }
@@ -98,7 +99,7 @@ export class ImportAls {
* Checks if the current context is an import preview operation. * Checks if the current context is an import preview operation.
* @returns {boolean} True if the context is an import preview operation, false otherwise. * @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'); return !!this.getStore()?.get('isImportPreview');
} }
} }

View File

@@ -92,7 +92,7 @@ export default class InventorySubscriber {
inventoryTransactions, inventoryTransactions,
trx, trx,
}: IInventoryTransactionsCreatedPayload) => { }: 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. // Avoid running the cost items job if the async process is in import preview.
if (inImportPreviewScope) return; if (inImportPreviewScope) return;