feat: import sheet files on initializing demo account

This commit is contained in:
Ahmed Bouhuolia
2024-08-20 18:30:21 +02:00
parent d29079a8c5
commit 408c807fc2
11 changed files with 176 additions and 7 deletions

View File

@@ -4,6 +4,7 @@ import { Request } from 'express';
import TenancyService from '@/services/Tenancy/TenancyService';
import TenantsManagerService from '@/services/Tenancy/TenantsManager';
import rtlDetect from 'rtl-detect';
import { Tenant } from '@/system/models';
export default (req: Request, tenant: ITenant) => {
const { id: tenantId, organizationId } = tenant;
@@ -16,7 +17,7 @@ export default (req: Request, tenant: ITenant) => {
const tenantContainer = tenantServices.tenantContainer(tenantId);
tenantContainer.set('i18n', injectI18nUtils(req));
tenantContainer.set('i18n', injectI18nUtils());
const knexInstance = tenantServices.knex(tenantId);
const models = tenantServices.models(tenantId);
@@ -33,14 +34,35 @@ export default (req: Request, tenant: ITenant) => {
};
export const injectI18nUtils = (req) => {
const locale = req.getLocale();
const globalI18n = Container.get('i18n');
const locale = globalI18n.getLocale();
const direction = rtlDetect.getLangDir(locale);
return {
locale,
__: req.__,
__: globalI18n.__,
direction,
isRtl: direction === 'rtl',
isLtr: direction === 'ltr',
};
};
export const initalizeTenantServices = async (tenantId: number) => {
const tenant = await Tenant.query()
.findById(tenantId)
.withGraphFetched('metadata');
const tenantServices = Container.get(TenancyService);
const tenantsManager = Container.get(TenantsManagerService);
// Initialize the knex instance.
tenantsManager.setupKnexInstance(tenant);
const tenantContainer = tenantServices.tenantContainer(tenantId);
tenantContainer.set('i18n', injectI18nUtils());
tenantServices.knex(tenantId);
tenantServices.models(tenantId);
tenantServices.repositories(tenantId);
tenantServices.cache(tenantId);
};

View File

@@ -116,6 +116,7 @@ import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashfl
import { DisconnectPlaidItemOnAccountDeleted } from '@/services/Banking/BankAccounts/events/DisconnectPlaidItemOnAccountDeleted';
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
import { DeleteUncategorizedTransactionsOnAccountDeleting } from '@/services/Banking/BankAccounts/events/DeleteUncategorizedTransactionsOnAccountDeleting';
import { SeedInitialDemoAccountDataOnOrgBuild } from '@/services/OneClickDemo/events/SeedInitialDemoAccountData';
export default () => {
return new EventPublisher();
@@ -282,5 +283,8 @@ export const susbcribers = () => {
// Loops
LoopsEventsSubscriber
// Demo Account
SeedInitialDemoAccountDataOnOrgBuild
];
};

View File

@@ -2,6 +2,7 @@ import { I18n } from 'i18n';
export default () => new I18n({
locales: ['en', 'ar'],
defaultLocale: 'en',
register: global,
directory: global.__locales_dir,
updateFiles: false,

View File

@@ -23,7 +23,7 @@ export class ImportFileMapping {
*/
public async mapping(
tenantId: number,
importId: number,
importId: string,
maps: ImportMappingAttr[]
): Promise<ImportFileMapPOJO> {
const importFile = await Import.query()

View File

@@ -25,7 +25,7 @@ export class ImportFileProcessCommit {
*/
public async commit(
tenantId: number,
importId: number
importId: string
): Promise<ImportFilePreviewPOJO> {
const knex = this.tenancy.knex(tenantId);
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' });

View File

@@ -55,7 +55,7 @@ export class ImportResourceApplication {
*/
public async mapping(
tenantId: number,
importId: number,
importId: string,
maps: ImportMappingAttr[]
) {
return this.importMappingService.mapping(tenantId, importId, maps);
@@ -77,7 +77,7 @@ export class ImportResourceApplication {
* @param {number} importId
* @returns {Promise<ImportFilePreviewPOJO>}
*/
public async process(tenantId: number, importId: number) {
public async process(tenantId: number, importId: string) {
return this.importProcessCommit.commit(tenantId, importId);
}

View File

@@ -7,6 +7,7 @@ import { OneClickDemo } from '@/system/models/OneclickDemo';
import { SystemUser } from '@/system/models';
import { IAuthSignInPOJO } from '@/interfaces';
import { ICreateOneClickDemoPOJO } from './interfaces';
import { initalizeTenantServices } from '@/api/middleware/TenantDependencyInjection';
@Service()
export class CreateOneClickDemo {
@@ -33,6 +34,9 @@ export class CreateOneClickDemo {
const tenantId = signedIn.tenant.id;
const userId = signedIn.user.id;
// Injects the given tenant IoC services.
await initalizeTenantServices(tenantId);
// Creates a new one-click demo.
await OneClickDemo.query().insert({ key: demoId, tenantId, userId });

View File

@@ -0,0 +1,6 @@
export class SeedDemoAbstract{
}

View File

@@ -0,0 +1,42 @@
import { SeedDemoAbstract } from './SeedDemoAbstract';
export class SeedDemoAccountItems extends SeedDemoAbstract {
/**
* Retrieves the seeder file mapping.
*/
get mapping() {
return [
{ from: 'Item Type', to: 'type' },
{ from: 'Item Name', to: 'name' },
{ from: 'Item Code', to: 'code' },
{ from: 'Sellable', to: 'sellable' },
{ from: 'Purchasable', to: 'purchasable' },
{ from: 'Sell Price', to: 'sellPrice' },
{ from: 'Cost Price', to: 'cost_price' },
{ from: 'Cost Account', to: 'costAccount' },
{ from: 'Sell Account', to: 'sellAccount' },
{ from: 'Inventory Account', to: 'inventoryAccount' },
{ from: 'Sell Description', to: 'sellDescription' },
{ from: 'Purchase Description', to: 'purchaseDescription' },
{ from: 'Note', to: 'note' },
{ from: 'Category', to: 'category' },
{ from: 'Active', to: 'active' },
];
}
/**
* Retrieves the seeder file name.
* @returns {string}
*/
get importFileName() {
return `items.csv`;
}
/**
* Retrieve the resource name of the seeder.
* @returns {string}
*/
get resource() {
return 'Item';
}
}

View File

@@ -0,0 +1,85 @@
import { Inject } from 'typedi';
import { promises as fs } from 'fs';
import path from 'path';
import events from '@/subscribers/events';
import { PromisePool } from '@supercharge/promise-pool';
import { IOrganizationBuildEventPayload } from '@/interfaces';
import { SeedDemoAccountItems } from '../DemoSeeders/SeedDemoItems';
import { ImportResourceApplication } from '@/services/Import/ImportResourceApplication';
import { getImportsStoragePath } from '@/services/Import/_utils';
import { OneClickDemo } from '@/system/models/OneclickDemo';
export class SeedInitialDemoAccountDataOnOrgBuild {
@Inject()
private importApp: ImportResourceApplication;
/**
* Attaches events with handlers.
*/
public attach = (bus) => {
bus.subscribe(
events.organization.build,
this.seedInitialDemoAccountDataOnOrgBuild.bind(this)
);
};
/**
* Demo account seeder.
*/
get seedDemoAccountSeeders() {
return [SeedDemoAccountItems];
}
/**
* Initialize the seeder sheet file to the import storage first.
* @param {string} fileName -
* @returns {Promise<void>}
*/
async initiateSeederFile(fileName: string) {
const destination = path.join(getImportsStoragePath(), fileName);
const source = path.join(global.__views_dir, `/demo-sheets`, fileName);
// Use the fs.promises.copyFile method to copy the file
await fs.copyFile(source, destination);
}
/**
* Seeds initial demo account data on organization build
* @param {IOrganizationBuildEventPayload}
*/
async seedInitialDemoAccountDataOnOrgBuild({
tenantId,
}: IOrganizationBuildEventPayload) {
const foundDemo = await OneClickDemo.query().findOne('tenantId', tenantId);
// Can't continue if the found demo is not exists.
// Means that account is not demo account.
if (!foundDemo) {
return null;
}
const results = await PromisePool.for(this.seedDemoAccountSeeders)
.withConcurrency(1)
.process(async (SeedDemoAccountSeeder) => {
const seederInstance = new SeedDemoAccountSeeder();
await this.initiateSeederFile(seederInstance.importFileName);
const importedFile = await this.importApp.import(
tenantId,
seederInstance.resource,
seederInstance.importFileName,
{}
);
await this.importApp.mapping(
tenantId,
importedFile.import.importId,
seederInstance.mapping
);
await this.importApp.process(tenantId, importedFile.import.importId);
});
if (results.errors) {
throw results.errors;
}
}
}

View File

@@ -0,0 +1,5 @@
Item Type,Item Name,Item Code,Sellable,Purchasable,Cost Price,Sell Price,Cost Account,Sell Account,Inventory Account,Sell Description,Purchase Description,Category,Note,Active
Inventory,"Hettinger, Schumm and Bartoletti",1000,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,At dolor est non tempore et quisquam.,TRUE
Inventory,Schmitt Group,1001,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Id perspiciatis at adipisci minus accusamus dolor iure dolore.,TRUE
Inventory,Marks - Carroll,1002,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Odio odio minus similique.,TRUE
Inventory,"VonRueden, Ruecker and Hettinger",1003,T,T,10000,1000,Cost of Goods Sold,Other Income,Inventory Asset,Description ....,Description ....,sdafasdfsadf,Quibusdam dolores illo.,TRUE
1 Item Type Item Name Item Code Sellable Purchasable Cost Price Sell Price Cost Account Sell Account Inventory Account Sell Description Purchase Description Category Note Active
2 Inventory Hettinger, Schumm and Bartoletti 1000 T T 10000 1000 Cost of Goods Sold Other Income Inventory Asset Description .... Description .... sdafasdfsadf At dolor est non tempore et quisquam. TRUE
3 Inventory Schmitt Group 1001 T T 10000 1000 Cost of Goods Sold Other Income Inventory Asset Description .... Description .... sdafasdfsadf Id perspiciatis at adipisci minus accusamus dolor iure dolore. TRUE
4 Inventory Marks - Carroll 1002 T T 10000 1000 Cost of Goods Sold Other Income Inventory Asset Description .... Description .... sdafasdfsadf Odio odio minus similique. TRUE
5 Inventory VonRueden, Ruecker and Hettinger 1003 T T 10000 1000 Cost of Goods Sold Other Income Inventory Asset Description .... Description .... sdafasdfsadf Quibusdam dolores illo. TRUE