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