mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
feat: one-click demo account
This commit is contained in:
@@ -1,11 +1,21 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
import SettingsStore from '@/services/Settings/SettingsStore';
|
||||
|
||||
|
||||
export default async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { tenantId } = req.user;
|
||||
|
||||
const Logger = Container.get('logger');
|
||||
const settings = await initializeTenantSettings(tenantId);
|
||||
req.settings = settings;
|
||||
|
||||
res.on('finish', async () => {
|
||||
await settings.save();
|
||||
});
|
||||
next();
|
||||
}
|
||||
|
||||
|
||||
export const initializeTenantSettings = async (tenantId: number) => {
|
||||
const tenantContainer = Container.of(`tenant-${tenantId}`);
|
||||
|
||||
if (tenantContainer && !tenantContainer.has('settings')) {
|
||||
@@ -18,10 +28,5 @@ export default async (req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
await settings.load();
|
||||
|
||||
req.settings = settings;
|
||||
|
||||
res.on('finish', async () => {
|
||||
await settings.save();
|
||||
});
|
||||
next();
|
||||
return settings;
|
||||
}
|
||||
@@ -33,3 +33,7 @@ export interface IOrganizationBuildEventPayload {
|
||||
buildDTO: IOrganizationBuildDTO;
|
||||
systemUser: ISystemUser;
|
||||
}
|
||||
|
||||
export interface IOrganizationBuiltEventPayload {
|
||||
tenantId: number;
|
||||
}
|
||||
@@ -106,6 +106,9 @@ export class TriggerRecognizedTransactions {
|
||||
const batch = importFile.paramsParsed.batch;
|
||||
const payload = { tenantId, transactionsCriteria: { batch } };
|
||||
|
||||
// Cannot continue if the imported resource is not bank account transactions.
|
||||
if (importFile.resource !== 'UncategorizedCashflowTransaction') return;
|
||||
|
||||
await this.agenda.now('recognize-uncategorized-transactions-job', payload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ export class UncategorizedTransactionsImportable extends Importable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes the import params before storing them.
|
||||
* Transforms the import params before storing them.
|
||||
* @param {Record<string, any>} parmas
|
||||
*/
|
||||
public transformParams(parmas: Record<string, any>) {
|
||||
|
||||
@@ -19,7 +19,7 @@ export class ImportFilePreview {
|
||||
*/
|
||||
public async preview(
|
||||
tenantId: number,
|
||||
importId: number
|
||||
importId: string
|
||||
): Promise<ImportFilePreviewPOJO> {
|
||||
const knex = this.tenancy.knex(tenantId);
|
||||
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' });
|
||||
|
||||
@@ -37,7 +37,7 @@ export class ImportFileProcess {
|
||||
*/
|
||||
public async import(
|
||||
tenantId: number,
|
||||
importId: number,
|
||||
importId: string,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<ImportFilePreviewPOJO> {
|
||||
const importFile = await Import.query()
|
||||
|
||||
@@ -67,7 +67,7 @@ export class ImportResourceApplication {
|
||||
* @param {number} importId - Import id.
|
||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
||||
*/
|
||||
public async preview(tenantId: number, importId: number) {
|
||||
public async preview(tenantId: number, importId: string) {
|
||||
return this.ImportFilePreviewService.preview(tenantId, importId);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ServiceError } from '@/exceptions';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ItemEntry } from '@/models';
|
||||
import { entriesAmountDiff } from 'utils';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
const ERRORS = {
|
||||
ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND',
|
||||
@@ -58,13 +59,14 @@ export default class ItemsEntriesService {
|
||||
*/
|
||||
public async filterInventoryEntries(
|
||||
tenantId: number,
|
||||
entries: IItemEntry[]
|
||||
entries: IItemEntry[],
|
||||
trx?: Knex.Transaction
|
||||
): Promise<IItemEntry[]> {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
const entriesItemsIds = entries.map((e) => e.itemId);
|
||||
|
||||
// Retrieve entries inventory items.
|
||||
const inventoryItems = await Item.query()
|
||||
const inventoryItems = await Item.query(trx)
|
||||
.whereIn('id', entriesItemsIds)
|
||||
.where('type', 'inventory');
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ 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';
|
||||
import events from '@/subscribers/events';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { defaultDemoOrganizationDTO } from './_constants';
|
||||
|
||||
@Service()
|
||||
export class CreateOneClickDemo {
|
||||
@@ -17,6 +19,10 @@ export class CreateOneClickDemo {
|
||||
@Inject()
|
||||
private organizationService: OrganizationService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
|
||||
/**
|
||||
* Creates one-click demo account.
|
||||
* @returns {Promise<ICreateOneClickDemoPOJO>}
|
||||
@@ -34,22 +40,12 @@ 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 });
|
||||
|
||||
const buildJob = await this.organizationService.buildRunJob(
|
||||
tenantId,
|
||||
{
|
||||
name: 'BIGCAPITAL, INC',
|
||||
base_currency: 'USD',
|
||||
location: 'US',
|
||||
language: 'en',
|
||||
fiscal_year: 'march',
|
||||
timezone: 'US/Central',
|
||||
},
|
||||
defaultDemoOrganizationDTO,
|
||||
signedIn.user
|
||||
);
|
||||
return { email, demoId, signedIn, buildJob };
|
||||
|
||||
12
packages/server/src/services/OneClickDemo/_constants.ts
Normal file
12
packages/server/src/services/OneClickDemo/_constants.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
export const defaultDemoOrganizationDTO = {
|
||||
name: 'BIGCAPITAL, INC',
|
||||
baseCurrency: 'USD',
|
||||
location: 'US',
|
||||
language: 'en',
|
||||
industry: 'Technology',
|
||||
fiscalYear: 'march',
|
||||
timezone: 'US/Central',
|
||||
dateFormat: 'MM/DD/yyyy',
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Inject } from 'typedi';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import uniqid from 'uniqid';
|
||||
import { isEmpty } from 'lodash';
|
||||
import events from '@/subscribers/events';
|
||||
import { PromisePool } from '@supercharge/promise-pool';
|
||||
import { IOrganizationBuildEventPayload } from '@/interfaces';
|
||||
import { IOrganizationBuiltEventPayload } from '@/interfaces';
|
||||
import { SeedDemoAccountItems } from '../DemoSeeders/SeedDemoItems';
|
||||
import { ImportResourceApplication } from '@/services/Import/ImportResourceApplication';
|
||||
import { getImportsStoragePath } from '@/services/Import/_utils';
|
||||
@@ -24,7 +26,7 @@ export class SeedInitialDemoAccountDataOnOrgBuild {
|
||||
*/
|
||||
public attach = (bus) => {
|
||||
bus.subscribe(
|
||||
events.organization.build,
|
||||
events.organization.built,
|
||||
this.seedInitialDemoAccountDataOnOrgBuild.bind(this)
|
||||
);
|
||||
};
|
||||
@@ -35,12 +37,12 @@ export class SeedInitialDemoAccountDataOnOrgBuild {
|
||||
get seedDemoAccountSeeders() {
|
||||
return [
|
||||
SeedDemoAccountItems,
|
||||
SeedDemoBankTransactions,
|
||||
SeedDemoAccountCustomers,
|
||||
SeedDemoAccountVendors,
|
||||
SeedDemoAccountManualJournals,
|
||||
SeedDemoBankTransactions,
|
||||
SeedDemoAccountExpenses,
|
||||
SeedDemoSaleInvoices,
|
||||
SeedDemoAccountExpenses,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -50,11 +52,14 @@ export class SeedInitialDemoAccountDataOnOrgBuild {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initiateSeederFile(fileName: string) {
|
||||
const destination = path.join(getImportsStoragePath(), fileName);
|
||||
const destFileName = uniqid();
|
||||
const source = path.join(global.__views_dir, `/demo-sheets`, fileName);
|
||||
const destination = path.join(getImportsStoragePath(), destFileName);
|
||||
|
||||
// Use the fs.promises.copyFile method to copy the file
|
||||
await fs.copyFile(source, destination);
|
||||
|
||||
return destFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +68,7 @@ export class SeedInitialDemoAccountDataOnOrgBuild {
|
||||
*/
|
||||
async seedInitialDemoAccountDataOnOrgBuild({
|
||||
tenantId,
|
||||
}: IOrganizationBuildEventPayload) {
|
||||
}: IOrganizationBuiltEventPayload) {
|
||||
const foundDemo = await OneClickDemo.query().findOne('tenantId', tenantId);
|
||||
|
||||
// Can't continue if the found demo is not exists.
|
||||
@@ -77,14 +82,14 @@ export class SeedInitialDemoAccountDataOnOrgBuild {
|
||||
const seederInstance = new SeedDemoAccountSeeder();
|
||||
|
||||
// Initialize the seeder sheet file before importing.
|
||||
await this.initiateSeederFile(seederInstance.importFileName);
|
||||
const importFileName = await this.initiateSeederFile(seederInstance.importFileName);
|
||||
|
||||
// Import the given seeder file.
|
||||
const importedFile = await this.importApp.import(
|
||||
tenantId,
|
||||
seederInstance.resource,
|
||||
seederInstance.importFileName,
|
||||
seederInstance.importParams || {}
|
||||
importFileName,
|
||||
seederInstance.importParams
|
||||
);
|
||||
// Mapping the columns with resource fields.
|
||||
await this.importApp.mapping(
|
||||
@@ -92,18 +97,16 @@ export class SeedInitialDemoAccountDataOnOrgBuild {
|
||||
importedFile.import.importId,
|
||||
seederInstance.mapping
|
||||
);
|
||||
await this.importApp.preview(tenantId, importedFile.import.importId);
|
||||
|
||||
// Commit the imported file.
|
||||
const re = await this.importApp.process(
|
||||
await this.importApp.process(
|
||||
tenantId,
|
||||
importedFile.import.importId
|
||||
);
|
||||
console.log(re);
|
||||
});
|
||||
|
||||
console.error(results.errors);
|
||||
console.log(results.results);
|
||||
|
||||
if (results.errors) {
|
||||
if (!isEmpty(results.errors)) {
|
||||
throw results.errors;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
IOrganizationBuildDTO,
|
||||
IOrganizationBuildEventPayload,
|
||||
IOrganizationBuiltEventPayload,
|
||||
IOrganizationUpdateDTO,
|
||||
ISystemUser,
|
||||
ITenant,
|
||||
@@ -17,6 +18,8 @@ import { Tenant } from '@/system/models';
|
||||
import OrganizationBaseCurrencyLocking from './OrganizationBaseCurrencyLocking';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ERRORS } from './constants';
|
||||
import { initializeTenantSettings } from '@/api/middleware/SettingsMiddleware';
|
||||
import { initalizeTenantServices } from '@/api/middleware/TenantDependencyInjection';
|
||||
|
||||
@Service()
|
||||
export default class OrganizationService {
|
||||
@@ -62,6 +65,10 @@ export default class OrganizationService {
|
||||
// Migrated tenant.
|
||||
const migratedTenant = await tenant.$query().withGraphFetched('metadata');
|
||||
|
||||
// Injects the given tenant IoC services.
|
||||
await initalizeTenantServices(tenantId);
|
||||
await initializeTenantSettings(tenantId);
|
||||
|
||||
// Creates a tenancy object from given tenant model.
|
||||
const tenancyContext =
|
||||
this.tenantsManager.getSeedMigrationContext(migratedTenant);
|
||||
@@ -82,6 +89,11 @@ export default class OrganizationService {
|
||||
|
||||
//
|
||||
await this.flagTenantDBBatch(tenantId);
|
||||
|
||||
// Triggers the organization built event.
|
||||
await this.eventPublisher.emitAsync(events.organization.built, {
|
||||
tenantId: tenant.id,
|
||||
} as IOrganizationBuiltEventPayload)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,7 +44,7 @@ export class SaleInvoiceGLEntries {
|
||||
|
||||
// Find or create the A/R account.
|
||||
const ARAccount = await accountRepository.findOrCreateAccountReceivable(
|
||||
saleInvoice.currencyCode
|
||||
saleInvoice.currencyCode, {}, trx
|
||||
);
|
||||
// Find or create tax payable account.
|
||||
const taxPayableAccount = await accountRepository.findOrCreateTaxPayable(
|
||||
|
||||
@@ -32,7 +32,8 @@ export class InvoiceInventoryTransactions {
|
||||
const inventoryEntries =
|
||||
await this.itemsEntriesService.filterInventoryEntries(
|
||||
tenantId,
|
||||
saleInvoice.entries
|
||||
saleInvoice.entries,
|
||||
trx
|
||||
);
|
||||
const transaction = {
|
||||
transactionId: saleInvoice.id,
|
||||
|
||||
@@ -35,6 +35,8 @@ export default {
|
||||
*/
|
||||
organization: {
|
||||
build: 'onOrganizationBuild',
|
||||
built: 'onOrganizationBuilt',
|
||||
|
||||
seeded: 'onOrganizationSeeded',
|
||||
|
||||
baseCurrencyUpdated: 'onOrganizationBaseCurrencyUpdated',
|
||||
|
||||
Reference in New Issue
Block a user