mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
Compare commits
2 Commits
fix-gettin
...
all-contri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55ba66205c | ||
|
|
291bf80135 |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -2,27 +2,6 @@
|
|||||||
|
|
||||||
All notable changes to Bigcapital server-side will be in this file.
|
All notable changes to Bigcapital server-side will be in this file.
|
||||||
|
|
||||||
## [0.19.4] - 18-08-2024
|
|
||||||
|
|
||||||
* fix: Allow multi-lines to statements transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/594
|
|
||||||
* feat: Add amount comparators to amount bank rule field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/595
|
|
||||||
* fix: Transaction type and description do not show in general ledger. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/596
|
|
||||||
* fix: Refresh accounts and account transactions. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/597
|
|
||||||
* fix: Typo payments made by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/598
|
|
||||||
* fix: Typo categories list by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/600
|
|
||||||
* fix: Autofill the quick created customer/vendor by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/601
|
|
||||||
* fix: Remove views tabs from receipts list by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/602
|
|
||||||
* fix: Typo payment receive messages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/599
|
|
||||||
* fix: Enhance Dropzone visual of accept and reject modes by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/603
|
|
||||||
* fix: Matching bank transactions should create associate payment transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/606
|
|
||||||
* fix: Change Dropzone title and subtitle by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/607
|
|
||||||
* fix: Inconsistance page size of paginated data tables by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/604
|
|
||||||
* fix: Database connection lost error by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/611
|
|
||||||
* fix: Language typos by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/613
|
|
||||||
* Fix: Correctly display Date, Published At, and Created At in ExpenseDrawerHeader by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/612
|
|
||||||
* fix: Delete bank account with uncategorized transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/614
|
|
||||||
* feat: activate/inactivate account from drawer details by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/615
|
|
||||||
|
|
||||||
## [v0.18.0] - 10-08-2024
|
## [v0.18.0] - 10-08-2024
|
||||||
|
|
||||||
* feat: Bank rules for automated categorization by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
|
* feat: Bank rules for automated categorization by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
|
||||||
|
|||||||
@@ -37,7 +37,6 @@
|
|||||||
"agendash": "^3.1.0",
|
"agendash": "^3.1.0",
|
||||||
"app-root-path": "^3.0.0",
|
"app-root-path": "^3.0.0",
|
||||||
"async": "^3.2.0",
|
"async": "^3.2.0",
|
||||||
"async-mutex": "^0.5.0",
|
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"babel-loader": "^9.1.2",
|
"babel-loader": "^9.1.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
@@ -70,8 +69,9 @@
|
|||||||
"is-my-json-valid": "^2.20.5",
|
"is-my-json-valid": "^2.20.5",
|
||||||
"js-money": "^0.6.3",
|
"js-money": "^0.6.3",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"knex": "^3.1.0",
|
"knex": "^0.95.15",
|
||||||
"knex-cleaner": "^1.3.0",
|
"knex-cleaner": "^1.3.0",
|
||||||
|
"knex-db-manager": "^0.6.1",
|
||||||
"libphonenumber-js": "^1.9.6",
|
"libphonenumber-js": "^1.9.6",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"lru-cache": "^6.0.0",
|
"lru-cache": "^6.0.0",
|
||||||
|
|||||||
@@ -46,10 +46,6 @@ export class ExcludeBankTransactionsController extends BaseController {
|
|||||||
query('account_id').optional().isNumeric().toInt(),
|
query('account_id').optional().isNumeric().toInt(),
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
|
||||||
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
|
||||||
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
|
||||||
query('max_amount').optional({ nullable: true }).isFloat().toFloat(),
|
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.getExcludedBankTransactions.bind(this)
|
this.getExcludedBankTransactions.bind(this)
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ export class RecognizedTransactionsController extends BaseController {
|
|||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
query('account_id').optional().isNumeric().toInt(),
|
query('account_id').optional().isNumeric().toInt(),
|
||||||
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
|
||||||
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
|
||||||
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
|
||||||
query('max_amount').optional({ nullable: true }).isFloat().isFloat(),
|
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.getRecognizedTransactions.bind(this)
|
this.getRecognizedTransactions.bind(this)
|
||||||
|
|||||||
@@ -84,10 +84,6 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
param('id').exists().isNumeric().toInt(),
|
param('id').exists().isNumeric().toInt(),
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
query('min_date').optional({ nullable: true }).isISO8601().toDate(),
|
|
||||||
query('max_date').optional({ nullable: true }).isISO8601().toDate(),
|
|
||||||
query('min_amount').optional({ nullable: true }).isFloat().toFloat(),
|
|
||||||
query('max_amount').optional({ nullable: true }).isFloat().toFloat(),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
import { Router, Request, Response, NextFunction } from 'express';
|
|
||||||
import { Service, Inject } from 'typedi';
|
|
||||||
import { body } from 'express-validator';
|
|
||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
|
||||||
import BaseController from '@/api/controllers/BaseController';
|
|
||||||
import { OneClickDemoApplication } from '@/services/OneClickDemo/OneClickDemoApplication';
|
|
||||||
import config from '@/config';
|
|
||||||
@Service()
|
|
||||||
export class OneClickDemoController extends BaseController {
|
|
||||||
@Inject()
|
|
||||||
private oneClickDemoApp: OneClickDemoApplication;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Router constructor method.
|
|
||||||
*/
|
|
||||||
router() {
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
// Protects the endpoints if the feature is not enabled.
|
|
||||||
const protectMiddleware = (
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) => {
|
|
||||||
// Add your protection logic here
|
|
||||||
if (config.oneClickDemoAccounts) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
res.status(403).send({ message: 'Forbidden' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
router.post(
|
|
||||||
'/one_click',
|
|
||||||
protectMiddleware,
|
|
||||||
asyncMiddleware(this.oneClickDemo.bind(this))
|
|
||||||
);
|
|
||||||
router.post(
|
|
||||||
'/one_click_signin',
|
|
||||||
[body('demo_id').exists()],
|
|
||||||
this.validationResult,
|
|
||||||
protectMiddleware,
|
|
||||||
asyncMiddleware(this.oneClickSignIn.bind(this))
|
|
||||||
);
|
|
||||||
return router;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* One-click demo application.
|
|
||||||
* @param {Request} req -
|
|
||||||
* @param {Response} res -
|
|
||||||
* @param {NextFunction} next -
|
|
||||||
*/
|
|
||||||
private async oneClickDemo(req: Request, res: Response, next: NextFunction) {
|
|
||||||
try {
|
|
||||||
const data = await this.oneClickDemoApp.createOneClick();
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
data,
|
|
||||||
message: 'The one-click demo has been created successfully.',
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sign-in to one-click demo account.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
private async oneClickSignIn(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
const { demoId } = this.matchedBodyData(req);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await this.oneClickDemoApp.autoSignIn(demoId);
|
|
||||||
|
|
||||||
return res.status(200).send(data);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -113,7 +113,7 @@ export class SubscriptionController extends BaseController {
|
|||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.subscriptionApp.cancelSubscription(tenantId);
|
await this.subscriptionApp.cancelSubscription(tenantId, '455610');
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
status: 200,
|
status: 200,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class Webhooks extends BaseController {
|
|||||||
*/
|
*/
|
||||||
public async lemonWebhooks(req: Request, res: Response, next: NextFunction) {
|
public async lemonWebhooks(req: Request, res: Response, next: NextFunction) {
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
const signature = req.headers['x-signature'] as string ?? '';
|
const signature = req.headers['x-signature'] ?? '';
|
||||||
const rawBody = req.rawBody;
|
const rawBody = req.rawBody;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ import { BankingController } from './controllers/Banking/BankingController';
|
|||||||
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
import { Webhooks } from './controllers/Webhooks/Webhooks';
|
||||||
import { ExportController } from './controllers/Export/ExportController';
|
import { ExportController } from './controllers/Export/ExportController';
|
||||||
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
|
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
|
||||||
import { OneClickDemoController } from './controllers/OneClickDemo/OneClickDemoController';
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const app = Router();
|
const app = Router();
|
||||||
@@ -81,7 +80,6 @@ export default () => {
|
|||||||
app.use('/jobs', Container.get(Jobs).router());
|
app.use('/jobs', Container.get(Jobs).router());
|
||||||
app.use('/account', Container.get(Account).router());
|
app.use('/account', Container.get(Account).router());
|
||||||
app.use('/webhooks', Container.get(Webhooks).router());
|
app.use('/webhooks', Container.get(Webhooks).router());
|
||||||
app.use('/demo', Container.get(OneClickDemoController).router())
|
|
||||||
|
|
||||||
// - Dashboard routes.
|
// - Dashboard routes.
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
import { Container } from 'typedi';
|
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Container } from 'typedi';
|
||||||
import SettingsStore from '@/services/Settings/SettingsStore';
|
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 settings = await initializeTenantSettings(tenantId);
|
const Logger = Container.get('logger');
|
||||||
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')) {
|
||||||
@@ -28,5 +18,10 @@ export const initializeTenantSettings = async (tenantId: number) => {
|
|||||||
|
|
||||||
await settings.load();
|
await settings.load();
|
||||||
|
|
||||||
return settings;
|
req.settings = settings;
|
||||||
|
|
||||||
|
res.on('finish', async () => {
|
||||||
|
await settings.save();
|
||||||
|
});
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ import { Request } from 'express';
|
|||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import TenantsManagerService from '@/services/Tenancy/TenantsManager';
|
import TenantsManagerService from '@/services/Tenancy/TenantsManager';
|
||||||
import rtlDetect from 'rtl-detect';
|
import rtlDetect from 'rtl-detect';
|
||||||
import { Tenant } from '@/system/models';
|
|
||||||
|
|
||||||
export default (req: Request, tenant: ITenant) => {
|
export default (req: Request, tenant: ITenant) => {
|
||||||
const { id: tenantId, organizationId } = tenant;
|
const { id: tenantId, organizationId } = tenant;
|
||||||
@@ -17,7 +16,7 @@ export default (req: Request, tenant: ITenant) => {
|
|||||||
|
|
||||||
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
const tenantContainer = tenantServices.tenantContainer(tenantId);
|
||||||
|
|
||||||
tenantContainer.set('i18n', injectI18nUtils());
|
tenantContainer.set('i18n', injectI18nUtils(req));
|
||||||
|
|
||||||
const knexInstance = tenantServices.knex(tenantId);
|
const knexInstance = tenantServices.knex(tenantId);
|
||||||
const models = tenantServices.models(tenantId);
|
const models = tenantServices.models(tenantId);
|
||||||
@@ -34,35 +33,14 @@ export default (req: Request, tenant: ITenant) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const injectI18nUtils = (req) => {
|
export const injectI18nUtils = (req) => {
|
||||||
const globalI18n = Container.get('i18n');
|
const locale = req.getLocale();
|
||||||
const locale = globalI18n.getLocale();
|
|
||||||
const direction = rtlDetect.getLangDir(locale);
|
const direction = rtlDetect.getLangDir(locale);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
locale,
|
locale,
|
||||||
__: globalI18n.__,
|
__: req.__,
|
||||||
direction,
|
direction,
|
||||||
isRtl: direction === 'rtl',
|
isRtl: direction === 'rtl',
|
||||||
isLtr: direction === 'ltr',
|
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);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
tenant: {
|
tenant: {
|
||||||
db_client: process.env.TENANT_DB_CLIENT || process.env.DB_CLIENT || 'mysql',
|
db_client: process.env.TENANT_DB_CLIENT || process.env.DB_CLIENT || 'mysql',
|
||||||
db_name_prefix: process.env.TENANT_DB_NAME_PERFIX || 'bigcapital_tenant_',
|
db_name_prefix: process.env.TENANT_DB_NAME_PERFIX,
|
||||||
db_host: process.env.TENANT_DB_HOST || process.env.DB_HOST,
|
db_host: process.env.TENANT_DB_HOST || process.env.DB_HOST,
|
||||||
db_user: process.env.TENANT_DB_USER || process.env.DB_USER,
|
db_user: process.env.TENANT_DB_USER || process.env.DB_USER,
|
||||||
db_password: process.env.TENANT_DB_PASSWORD || process.env.DB_PASSWORD,
|
db_password: process.env.TENANT_DB_PASSWORD || process.env.DB_PASSWORD,
|
||||||
@@ -245,12 +245,4 @@ module.exports = {
|
|||||||
loops: {
|
loops: {
|
||||||
apiKey: process.env.LOOPS_API_KEY,
|
apiKey: process.env.LOOPS_API_KEY,
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* One-click demo accounts.
|
|
||||||
*/
|
|
||||||
oneClickDemoAccounts: {
|
|
||||||
enable: parseBoolean(process.env.ONE_CLICK_DEMO_ACCOUNTS, false),
|
|
||||||
demoUrl: process.env.ONE_CLICK_DEMO_ACCOUNTS_URL || '',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export default class SeedSettings extends TenantSeeder {
|
|||||||
|
|
||||||
// Manual journals settings.
|
// Manual journals settings.
|
||||||
{ group: 'manual_journals', key: 'next_number', value: '00001' },
|
{ group: 'manual_journals', key: 'next_number', value: '00001' },
|
||||||
{ group: 'manual_journals', key: 'number_prefix', value: 'J-' },
|
|
||||||
{ group: 'manual_journals', key: 'auto_increment', value: true },
|
{ group: 'manual_journals', key: 'auto_increment', value: true },
|
||||||
|
|
||||||
// Sale invoices settings.
|
// Sale invoices settings.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface IRegisterDTO {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
organizationName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILoginDTO {
|
export interface ILoginDTO {
|
||||||
@@ -76,10 +77,6 @@ export interface IAuthSendedResetPassword {
|
|||||||
|
|
||||||
export interface IAuthGetMetaPOJO {
|
export interface IAuthGetMetaPOJO {
|
||||||
signupDisabled: boolean;
|
signupDisabled: boolean;
|
||||||
oneClickDemo: {
|
|
||||||
enable: boolean;
|
|
||||||
demoUrl: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAuthSignUpVerifingEventPayload {
|
export interface IAuthSignUpVerifingEventPayload {
|
||||||
|
|||||||
@@ -167,18 +167,11 @@ export interface CategorizeTransactionAsExpenseDTO {
|
|||||||
export interface IGetUncategorizedTransactionsQuery {
|
export interface IGetUncategorizedTransactionsQuery {
|
||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
minDate?: Date;
|
|
||||||
maxDate?: Date;
|
|
||||||
minAmount?: number;
|
|
||||||
maxAmount?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IGetRecognizedTransactionsQuery {
|
export interface IGetRecognizedTransactionsQuery {
|
||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
accountId?: number;
|
accountId?: number;
|
||||||
minDate?: Date;
|
|
||||||
maxDate?: Date;
|
|
||||||
minAmount?: number;
|
|
||||||
maxAmount?: number;
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,6 @@ import { ImportFilePreviewPOJO } from "@/services/Import/interfaces";
|
|||||||
|
|
||||||
export interface IImportFileCommitedEventPayload {
|
export interface IImportFileCommitedEventPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
importId: string;
|
importId: number;
|
||||||
meta: ImportFilePreviewPOJO;
|
meta: ImportFilePreviewPOJO;
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ export interface IPaymentReceivedEntry {
|
|||||||
export interface IPaymentReceivedEntryDTO {
|
export interface IPaymentReceivedEntryDTO {
|
||||||
id?: number;
|
id?: number;
|
||||||
index: number;
|
index: number;
|
||||||
paymentReceiveId?: number;
|
paymentReceiveId: number;
|
||||||
invoiceId: number;
|
invoiceId: number;
|
||||||
paymentAmount: number;
|
paymentAmount: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,3 @@ export interface IOrganizationBuildEventPayload {
|
|||||||
buildDTO: IOrganizationBuildDTO;
|
buildDTO: IOrganizationBuildDTO;
|
||||||
systemUser: ISystemUser;
|
systemUser: ISystemUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOrganizationBuiltEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export interface SubscriptionPayload {
|
|
||||||
lemonSqueezyId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SubscriptionPaymentStatus {
|
|
||||||
Succeed = 'succeed',
|
|
||||||
Failed = 'failed',
|
|
||||||
}
|
|
||||||
@@ -51,4 +51,5 @@ export interface ISystemService {
|
|||||||
cache();
|
cache();
|
||||||
repositories();
|
repositories();
|
||||||
knex();
|
knex();
|
||||||
|
dbManager();
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,6 @@ export * from './Times';
|
|||||||
export * from './ProjectProfitabilitySummary';
|
export * from './ProjectProfitabilitySummary';
|
||||||
export * from './TaxRate';
|
export * from './TaxRate';
|
||||||
export * from './Plaid';
|
export * from './Plaid';
|
||||||
export * from './Subscription';
|
|
||||||
|
|
||||||
export interface I18nService {
|
export interface I18nService {
|
||||||
__: (input: string) => string;
|
__: (input: string) => string;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default class ComputeItemCostJob {
|
|||||||
|
|
||||||
agenda.define(
|
agenda.define(
|
||||||
'compute-item-cost',
|
'compute-item-cost',
|
||||||
{ priority: 'high', concurrency: 20 },
|
{ priority: 'high', concurrency: 1 },
|
||||||
this.handler.bind(this)
|
this.handler.bind(this)
|
||||||
);
|
);
|
||||||
this.agenda.on('start:compute-item-cost', this.onJobStart.bind(this));
|
this.agenda.on('start:compute-item-cost', this.onJobStart.bind(this));
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default class OrganizationSetupJob {
|
|||||||
constructor(agenda) {
|
constructor(agenda) {
|
||||||
agenda.define(
|
agenda.define(
|
||||||
'organization-setup',
|
'organization-setup',
|
||||||
{ priority: 'high', concurrency: 20 },
|
{ priority: 'high', concurrency: 1 },
|
||||||
this.handler
|
this.handler
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default class WriteInvoicesJournalEntries {
|
|||||||
|
|
||||||
agenda.define(
|
agenda.define(
|
||||||
eventName,
|
eventName,
|
||||||
{ priority: 'normal', concurrency: 20 },
|
{ priority: 'normal', concurrency: 1 },
|
||||||
this.handler.bind(this)
|
this.handler.bind(this)
|
||||||
);
|
);
|
||||||
agenda.on(`complete:${eventName}`, this.onJobCompleted.bind(this));
|
agenda.on(`complete:${eventName}`, this.onJobCompleted.bind(this));
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ 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,35 +155,19 @@ export class Transformer {
|
|||||||
this.dateFormat = format;
|
this.dateFormat = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Format date.
|
|
||||||
* @param {} date
|
|
||||||
* @param {string} format -
|
|
||||||
* @returns {}
|
|
||||||
*/
|
|
||||||
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) : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param date
|
* @param date
|
||||||
* @returns {}
|
* @returns
|
||||||
*/
|
*/
|
||||||
protected formatDateFromNow(date) {
|
protected formatDate(date) {
|
||||||
return date ? moment(date).fromNow(true) : '';
|
return date ? moment(date).format(this.dateFormat) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @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 });
|
||||||
@@ -194,7 +177,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, {
|
||||||
|
|||||||
@@ -3,17 +3,12 @@ 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
|
||||||
@@ -22,12 +17,10 @@ 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,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
packages/server/src/loaders/dbManager.ts
Normal file
7
packages/server/src/loaders/dbManager.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import knexManager from 'knex-db-manager';
|
||||||
|
import { systemKnexConfig, systemDbManager } from 'config/knexConfig';
|
||||||
|
|
||||||
|
export default () => knexManager.databaseManagerFactory({
|
||||||
|
knex: systemKnexConfig,
|
||||||
|
dbManager: systemDbManager,
|
||||||
|
});
|
||||||
@@ -3,6 +3,7 @@ import LoggerInstance from '@/loaders/logger';
|
|||||||
import agendaFactory from '@/loaders/agenda';
|
import agendaFactory from '@/loaders/agenda';
|
||||||
import SmsClientLoader from '@/loaders/smsClient';
|
import SmsClientLoader from '@/loaders/smsClient';
|
||||||
import mailInstance from '@/loaders/mail';
|
import mailInstance from '@/loaders/mail';
|
||||||
|
import dbManagerFactory from '@/loaders/dbManager';
|
||||||
import i18n from '@/loaders/i18n';
|
import i18n from '@/loaders/i18n';
|
||||||
import repositoriesLoader from '@/loaders/systemRepositories';
|
import repositoriesLoader from '@/loaders/systemRepositories';
|
||||||
import Cache from '@/services/Cache';
|
import Cache from '@/services/Cache';
|
||||||
@@ -15,6 +16,7 @@ export default ({ mongoConnection, knex }) => {
|
|||||||
try {
|
try {
|
||||||
const agendaInstance = agendaFactory({ mongoConnection });
|
const agendaInstance = agendaFactory({ mongoConnection });
|
||||||
const smsClientInstance = SmsClientLoader(config.easySMSGateway.api_key);
|
const smsClientInstance = SmsClientLoader(config.easySMSGateway.api_key);
|
||||||
|
const dbManager = dbManagerFactory(knex);
|
||||||
const cacheInstance = new Cache();
|
const cacheInstance = new Cache();
|
||||||
|
|
||||||
Container.set('logger', LoggerInstance);
|
Container.set('logger', LoggerInstance);
|
||||||
@@ -22,6 +24,7 @@ export default ({ mongoConnection, knex }) => {
|
|||||||
Container.set('SMSClient', smsClientInstance);
|
Container.set('SMSClient', smsClientInstance);
|
||||||
Container.set('mail', mailInstance);
|
Container.set('mail', mailInstance);
|
||||||
|
|
||||||
|
Container.set('dbManager', dbManager);
|
||||||
LoggerInstance.info(
|
LoggerInstance.info(
|
||||||
'[DI] Database manager has been injected into container.'
|
'[DI] Database manager has been injected into container.'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -115,9 +115,6 @@ import { DecrementUncategorizedTransactionOnExclude } from '@/services/Banking/E
|
|||||||
import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize';
|
import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize';
|
||||||
import { DisconnectPlaidItemOnAccountDeleted } from '@/services/Banking/BankAccounts/events/DisconnectPlaidItemOnAccountDeleted';
|
import { DisconnectPlaidItemOnAccountDeleted } from '@/services/Banking/BankAccounts/events/DisconnectPlaidItemOnAccountDeleted';
|
||||||
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
|
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
|
||||||
import { DeleteUncategorizedTransactionsOnAccountDeleting } from '@/services/Banking/BankAccounts/events/DeleteUncategorizedTransactionsOnAccountDeleting';
|
|
||||||
import { SeedInitialDemoAccountDataOnOrgBuild } from '@/services/OneClickDemo/events/SeedInitialDemoAccountData';
|
|
||||||
import { TriggerInvalidateCacheOnSubscriptionChange } from '@/services/Subscription/events/TriggerInvalidateCacheOnSubscriptionChange';
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return new EventPublisher();
|
return new EventPublisher();
|
||||||
@@ -248,10 +245,8 @@ export const susbcribers = () => {
|
|||||||
DeleteCashflowTransactionOnUncategorize,
|
DeleteCashflowTransactionOnUncategorize,
|
||||||
PreventDeleteTransactionOnDelete,
|
PreventDeleteTransactionOnDelete,
|
||||||
|
|
||||||
// Subscription
|
|
||||||
SubscribeFreeOnSignupCommunity,
|
SubscribeFreeOnSignupCommunity,
|
||||||
SendVerfiyMailOnSignUp,
|
SendVerfiyMailOnSignUp,
|
||||||
TriggerInvalidateCacheOnSubscriptionChange,
|
|
||||||
|
|
||||||
// Attachments
|
// Attachments
|
||||||
AttachmentsOnSaleInvoiceCreated,
|
AttachmentsOnSaleInvoiceCreated,
|
||||||
@@ -282,12 +277,8 @@ export const susbcribers = () => {
|
|||||||
// Plaid
|
// Plaid
|
||||||
RecognizeSyncedBankTranasctions,
|
RecognizeSyncedBankTranasctions,
|
||||||
DisconnectPlaidItemOnAccountDeleted,
|
DisconnectPlaidItemOnAccountDeleted,
|
||||||
DeleteUncategorizedTransactionsOnAccountDeleting,
|
|
||||||
|
|
||||||
// Loops
|
// Loops
|
||||||
LoopsEventsSubscriber,
|
LoopsEventsSubscriber
|
||||||
|
|
||||||
// Demo Account
|
|
||||||
SeedInitialDemoAccountDataOnOrgBuild,
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { I18n } from 'i18n';
|
|||||||
|
|
||||||
export default () => new I18n({
|
export default () => new I18n({
|
||||||
locales: ['en', 'ar'],
|
locales: ['en', 'ar'],
|
||||||
defaultLocale: 'en',
|
|
||||||
register: global,
|
register: global,
|
||||||
directory: global.__locales_dir,
|
directory: global.__locales_dir,
|
||||||
updateFiles: false,
|
updateFiles: false,
|
||||||
|
|||||||
@@ -95,11 +95,6 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
customerType: {
|
|
||||||
name: 'Customer Type',
|
|
||||||
type: 'text',
|
|
||||||
accessor: 'formattedCustomerType',
|
|
||||||
},
|
|
||||||
firstName: {
|
firstName: {
|
||||||
name: 'vendor.field.first_name',
|
name: 'vendor.field.first_name',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@@ -140,117 +135,116 @@ export default {
|
|||||||
openingBalance: {
|
openingBalance: {
|
||||||
name: 'vendor.field.opening_balance',
|
name: 'vendor.field.opening_balance',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
openingBalanceAt: {
|
openingBalanceAt: {
|
||||||
name: 'vendor.field.opening_balance_at',
|
name: 'vendor.field.opening_balance_at',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
printable: false,
|
printable: false
|
||||||
accessor: 'formattedOpeningBalanceAt'
|
|
||||||
},
|
},
|
||||||
currencyCode: {
|
currencyCode: {
|
||||||
name: 'vendor.field.currency',
|
name: 'vendor.field.currency',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
name: 'vendor.field.status',
|
name: 'vendor.field.status',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
name: 'vendor.field.note',
|
name: 'vendor.field.note',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
// Billing Address
|
// Billing Address
|
||||||
billingAddress1: {
|
billingAddress1: {
|
||||||
name: 'Billing Address 1',
|
name: 'Billing Address 1',
|
||||||
column: 'billing_address1',
|
column: 'billing_address1',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
billingAddress2: {
|
billingAddress2: {
|
||||||
name: 'Billing Address 2',
|
name: 'Billing Address 2',
|
||||||
column: 'billing_address2',
|
column: 'billing_address2',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
billingAddressCity: {
|
billingAddressCity: {
|
||||||
name: 'Billing Address City',
|
name: 'Billing Address City',
|
||||||
column: 'billing_address_city',
|
column: 'billing_address_city',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
billingAddressCountry: {
|
billingAddressCountry: {
|
||||||
name: 'Billing Address Country',
|
name: 'Billing Address Country',
|
||||||
column: 'billing_address_country',
|
column: 'billing_address_country',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
billingAddressPostcode: {
|
billingAddressPostcode: {
|
||||||
name: 'Billing Address Postcode',
|
name: 'Billing Address Postcode',
|
||||||
column: 'billing_address_postcode',
|
column: 'billing_address_postcode',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
billingAddressState: {
|
billingAddressState: {
|
||||||
name: 'Billing Address State',
|
name: 'Billing Address State',
|
||||||
column: 'billing_address_state',
|
column: 'billing_address_state',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
billingAddressPhone: {
|
billingAddressPhone: {
|
||||||
name: 'Billing Address Phone',
|
name: 'Billing Address Phone',
|
||||||
column: 'billing_address_phone',
|
column: 'billing_address_phone',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
// Shipping Address
|
// Shipping Address
|
||||||
shippingAddress1: {
|
shippingAddress1: {
|
||||||
name: 'Shipping Address 1',
|
name: 'Shipping Address 1',
|
||||||
column: 'shipping_address1',
|
column: 'shipping_address1',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
shippingAddress2: {
|
shippingAddress2: {
|
||||||
name: 'Shipping Address 2',
|
name: 'Shipping Address 2',
|
||||||
column: 'shipping_address2',
|
column: 'shipping_address2',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
shippingAddressCity: {
|
shippingAddressCity: {
|
||||||
name: 'Shipping Address City',
|
name: 'Shipping Address City',
|
||||||
column: 'shipping_address_city',
|
column: 'shipping_address_city',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
shippingAddressCountry: {
|
shippingAddressCountry: {
|
||||||
name: 'Shipping Address Country',
|
name: 'Shipping Address Country',
|
||||||
column: 'shipping_address_country',
|
column: 'shipping_address_country',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
shippingAddressPostcode: {
|
shippingAddressPostcode: {
|
||||||
name: 'Shipping Address Postcode',
|
name: 'Shipping Address Postcode',
|
||||||
column: 'shipping_address_postcode',
|
column: 'shipping_address_postcode',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
shippingAddressPhone: {
|
shippingAddressPhone: {
|
||||||
name: 'Shipping Address Phone',
|
name: 'Shipping Address Phone',
|
||||||
column: 'shipping_address_phone',
|
column: 'shipping_address_phone',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
shippingAddressState: {
|
shippingAddressState: {
|
||||||
name: 'Shipping Address State',
|
name: 'Shipping Address State',
|
||||||
column: 'shipping_address_state',
|
column: 'shipping_address_state',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
name: 'vendor.field.created_at',
|
name: 'vendor.field.created_at',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
printable: false,
|
printable: false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fields2: {
|
fields2: {
|
||||||
|
|||||||
@@ -257,25 +257,25 @@ export default {
|
|||||||
name: 'item.field.sell_price',
|
name: 'item.field.sell_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
costPrice: {
|
cost_price: {
|
||||||
name: 'item.field.cost_price',
|
name: 'item.field.cost_price',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
costAccountId: {
|
costAccount: {
|
||||||
name: 'item.field.cost_account',
|
name: 'item.field.cost_account',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Account',
|
relationModel: 'Account',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
importHint: 'Matches the account name or code.',
|
importHint: 'Matches the account name or code.',
|
||||||
},
|
},
|
||||||
sellAccountId: {
|
sellAccount: {
|
||||||
name: 'item.field.sell_account',
|
name: 'item.field.sell_account',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Account',
|
relationModel: 'Account',
|
||||||
relationImportMatch: ['name', 'code'],
|
relationImportMatch: ['name', 'code'],
|
||||||
importHint: 'Matches the account name or code.',
|
importHint: 'Matches the account name or code.',
|
||||||
},
|
},
|
||||||
inventoryAccountId: {
|
inventoryAccount: {
|
||||||
name: 'item.field.inventory_account',
|
name: 'item.field.inventory_account',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
relationModel: 'Account',
|
relationModel: 'Account',
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Model } from 'objection';
|
import { Model } from 'objection';
|
||||||
import { castArray, omit, pick } from 'lodash';
|
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
|
|
||||||
@@ -17,15 +16,7 @@ export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
queryAndThrowIfHasRelations = ({
|
queryAndThrowIfHasRelations = ({ type, message }) => {
|
||||||
type,
|
|
||||||
message,
|
|
||||||
excludeRelations = [],
|
|
||||||
includedRelations = [],
|
|
||||||
}) => {
|
|
||||||
const _excludeRelations = castArray(excludeRelations);
|
|
||||||
const _includedRelations = castArray(includedRelations);
|
|
||||||
|
|
||||||
const model = this.modelClass();
|
const model = this.modelClass();
|
||||||
const modelRelations = Object.keys(model.relationMappings).filter(
|
const modelRelations = Object.keys(model.relationMappings).filter(
|
||||||
(relation) =>
|
(relation) =>
|
||||||
@@ -34,20 +25,9 @@ export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
|||||||
) !== -1
|
) !== -1
|
||||||
);
|
);
|
||||||
const relations = model.secureDeleteRelations || modelRelations;
|
const relations = model.secureDeleteRelations || modelRelations;
|
||||||
const filteredByIncluded = relations.filter((r) =>
|
|
||||||
_includedRelations.includes(r)
|
|
||||||
);
|
|
||||||
const filteredByExcluded = relations.filter(
|
|
||||||
(r) => !excludeRelations.includes(r)
|
|
||||||
);
|
|
||||||
const filteredRelations = !isEmpty(_includedRelations)
|
|
||||||
? filteredByIncluded
|
|
||||||
: !isEmpty(_excludeRelations)
|
|
||||||
? filteredByExcluded
|
|
||||||
: relations;
|
|
||||||
|
|
||||||
this.runAfter((model, query) => {
|
this.runAfter((model, query) => {
|
||||||
const nonEmptyRelations = filteredRelations.filter(
|
const nonEmptyRelations = relations.filter(
|
||||||
(relation) => !isEmpty(model[relation])
|
(relation) => !isEmpty(model[relation])
|
||||||
);
|
);
|
||||||
if (nonEmptyRelations.length > 0) {
|
if (nonEmptyRelations.length > 0) {
|
||||||
@@ -56,7 +36,7 @@ export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
|||||||
return model;
|
return model;
|
||||||
});
|
});
|
||||||
return this.onBuild((query) => {
|
return this.onBuild((query) => {
|
||||||
filteredRelations.forEach((relation) => {
|
relations.forEach((relation) => {
|
||||||
query.withGraphFetched(`${relation}(selectId)`).modifiers({
|
query.withGraphFetched(`${relation}(selectId)`).modifiers({
|
||||||
selectId(builder) {
|
selectId(builder) {
|
||||||
builder.select('id');
|
builder.select('id');
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
/* eslint-disable global-require */
|
/* eslint-disable global-require */
|
||||||
import moment from 'moment';
|
import * as R from 'ramda';
|
||||||
import { Model, mixin } from 'objection';
|
import { Model, ModelOptions, QueryContext, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import ModelSettings from './ModelSetting';
|
import ModelSettings from './ModelSetting';
|
||||||
|
import Account from './Account';
|
||||||
import UncategorizedCashflowTransactionMeta from './UncategorizedCashflowTransaction.meta';
|
import UncategorizedCashflowTransactionMeta from './UncategorizedCashflowTransaction.meta';
|
||||||
|
|
||||||
export default class UncategorizedCashflowTransaction extends mixin(
|
export default class UncategorizedCashflowTransaction extends mixin(
|
||||||
@@ -166,28 +167,6 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
pending(query) {
|
pending(query) {
|
||||||
query.where('pending', true);
|
query.where('pending', true);
|
||||||
},
|
},
|
||||||
|
|
||||||
minAmount(query, minAmount) {
|
|
||||||
query.where('amount', '>=', minAmount);
|
|
||||||
},
|
|
||||||
|
|
||||||
maxAmount(query, maxAmount) {
|
|
||||||
query.where('amount', '<=', maxAmount);
|
|
||||||
},
|
|
||||||
|
|
||||||
toDate(query, toDate) {
|
|
||||||
const dateFormat = 'YYYY-MM-DD';
|
|
||||||
const _toDate = moment(toDate).endOf('day').format(dateFormat);
|
|
||||||
|
|
||||||
query.where('date', '<=', _toDate);
|
|
||||||
},
|
|
||||||
|
|
||||||
fromDate(query, fromDate) {
|
|
||||||
const dateFormat = 'YYYY-MM-DD';
|
|
||||||
const _fromDate = moment(fromDate).startOf('day').format(dateFormat);
|
|
||||||
|
|
||||||
query.where('date', '>=', _fromDate);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ export class AccountsApplication {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new account.
|
* Creates a new account.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IAccountCreateDTO} accountDTO
|
* @param {IAccountCreateDTO} accountDTO
|
||||||
* @returns {Promise<IAccount>}
|
* @returns {Promise<IAccount>}
|
||||||
*/
|
*/
|
||||||
public createAccount = (
|
public createAccount = (
|
||||||
@@ -108,8 +108,8 @@ export class AccountsApplication {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the account details.
|
* Retrieves the account details.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} accountId
|
* @param {number} accountId
|
||||||
* @returns {Promise<IAccount>}
|
* @returns {Promise<IAccount>}
|
||||||
*/
|
*/
|
||||||
public getAccount = (tenantId: number, accountId: number) => {
|
public getAccount = (tenantId: number, accountId: number) => {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { AccountsApplication } from './AccountsApplication';
|
import { AccountsApplication } from './AccountsApplication';
|
||||||
import { Exportable } from '../Export/Exportable';
|
import { Exportable } from '../Export/Exportable';
|
||||||
import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
|
||||||
import { EXPORT_SIZE_LIMIT } from '../Export/constants';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class AccountsExportable extends Exportable {
|
export class AccountsExportable extends Exportable {
|
||||||
@@ -21,7 +20,7 @@ export class AccountsExportable extends Exportable {
|
|||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
...query,
|
...query,
|
||||||
structure: IAccountsStructureType.Flat,
|
structure: IAccountsStructureType.Flat,
|
||||||
pageSize: EXPORT_SIZE_LIMIT,
|
pageSize: 12000,
|
||||||
page: 1,
|
page: 1,
|
||||||
} as IAccountsFilter;
|
} as IAccountsFilter;
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ export class DeleteAccount {
|
|||||||
.throwIfNotFound()
|
.throwIfNotFound()
|
||||||
.queryAndThrowIfHasRelations({
|
.queryAndThrowIfHasRelations({
|
||||||
type: ERRORS.ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS,
|
type: ERRORS.ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS,
|
||||||
excludeRelations: ['uncategorizedTransactions', 'plaidItem']
|
|
||||||
});
|
});
|
||||||
// Authorize before delete account.
|
// Authorize before delete account.
|
||||||
await this.authorize(tenantId, accountId, oldAccount);
|
await this.authorize(tenantId, accountId, oldAccount);
|
||||||
|
|||||||
@@ -11,10 +11,6 @@ export class GetAuthMeta {
|
|||||||
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
|
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
|
||||||
return {
|
return {
|
||||||
signupDisabled: config.signupRestrictions.disabled,
|
signupDisabled: config.signupRestrictions.disabled,
|
||||||
oneClickDemo: {
|
|
||||||
enable: config.oneClickDemoAccounts.enable,
|
|
||||||
demoUrl: config.oneClickDemoAccounts.demoUrl,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { initialize } from 'objection';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import { IAccountEventDeletePayload } from '@/interfaces';
|
|
||||||
import { DeleteBankRulesService } from '../../Rules/DeleteBankRules';
|
|
||||||
import { RevertRecognizedTransactions } from '../../RegonizeTranasctions/RevertRecognizedTransactions';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class DeleteUncategorizedTransactionsOnAccountDeleting {
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private deleteBankRules: DeleteBankRulesService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private revertRecognizedTransactins: RevertRecognizedTransactions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
*/
|
|
||||||
public attach(bus) {
|
|
||||||
bus.subscribe(
|
|
||||||
events.accounts.onDelete,
|
|
||||||
this.handleDeleteBankRulesOnAccountDeleting.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles revert the recognized transactions and delete all the bank rules
|
|
||||||
* associated to the deleted bank account.
|
|
||||||
* @param {IAccountEventDeletePayload}
|
|
||||||
*/
|
|
||||||
private async handleDeleteBankRulesOnAccountDeleting({
|
|
||||||
tenantId,
|
|
||||||
oldAccount,
|
|
||||||
trx,
|
|
||||||
}: IAccountEventDeletePayload) {
|
|
||||||
const knex = this.tenancy.knex(tenantId);
|
|
||||||
const {
|
|
||||||
BankRule,
|
|
||||||
UncategorizedCashflowTransaction,
|
|
||||||
MatchedBankTransaction,
|
|
||||||
RecognizedBankTransaction,
|
|
||||||
} = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const foundAssociatedRules = await BankRule.query(trx).where(
|
|
||||||
'applyIfAccountId',
|
|
||||||
oldAccount.id
|
|
||||||
);
|
|
||||||
const foundAssociatedRulesIds = foundAssociatedRules.map((rule) => rule.id);
|
|
||||||
|
|
||||||
await initialize(knex, [
|
|
||||||
UncategorizedCashflowTransaction,
|
|
||||||
RecognizedBankTransaction,
|
|
||||||
MatchedBankTransaction,
|
|
||||||
]);
|
|
||||||
// Revert the recognized transactions of the given bank rules.
|
|
||||||
await this.revertRecognizedTransactins.revertRecognizedTransactions(
|
|
||||||
tenantId,
|
|
||||||
foundAssociatedRulesIds,
|
|
||||||
null,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
// Delete the associated uncategorized transactions.
|
|
||||||
await UncategorizedCashflowTransaction.query(trx)
|
|
||||||
.where('accountId', oldAccount.id)
|
|
||||||
.delete();
|
|
||||||
|
|
||||||
// Delete the given bank rules.
|
|
||||||
await this.deleteBankRules.deleteBankRules(
|
|
||||||
tenantId,
|
|
||||||
foundAssociatedRulesIds,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@ export class DisconnectPlaidItemOnAccountDeleted {
|
|||||||
.findOne('plaidItemId', oldAccount.plaidItemId)
|
.findOne('plaidItemId', oldAccount.plaidItemId)
|
||||||
.delete();
|
.delete();
|
||||||
|
|
||||||
// Remove Plaid item once the transaction resolve.
|
|
||||||
if (oldPlaidItem) {
|
if (oldPlaidItem) {
|
||||||
const plaidInstance = PlaidClientWrapper.getClient();
|
const plaidInstance = PlaidClientWrapper.getClient();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import moment from 'moment';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
import { ExcludedBankTransactionsQuery } from './_types';
|
import { ExcludedBankTransactionsQuery } from './_types';
|
||||||
import { UncategorizedTransactionTransformer } from '@/services/Cashflow/UncategorizedTransactionTransformer';
|
import { UncategorizedTransactionTransformer } from '@/services/Cashflow/UncategorizedTransactionTransformer';
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
@@ -40,18 +39,6 @@ export class GetExcludedBankTransactionsService {
|
|||||||
if (_query.accountId) {
|
if (_query.accountId) {
|
||||||
q.where('account_id', _query.accountId);
|
q.where('account_id', _query.accountId);
|
||||||
}
|
}
|
||||||
if (_query.minDate) {
|
|
||||||
q.modify('fromDate', _query.minDate);
|
|
||||||
}
|
|
||||||
if (_query.maxDate) {
|
|
||||||
q.modify('toDate', _query.maxDate);
|
|
||||||
}
|
|
||||||
if (_query.minAmount) {
|
|
||||||
q.modify('minAmount', _query.minAmount);
|
|
||||||
}
|
|
||||||
if (_query.maxAmount) {
|
|
||||||
q.modify('maxAmount', _query.maxAmount);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.pagination(_query.page - 1, _query.pageSize);
|
.pagination(_query.page - 1, _query.pageSize);
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ export interface ExcludedBankTransactionsQuery {
|
|||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
accountId?: number;
|
accountId?: number;
|
||||||
minDate?: Date;
|
|
||||||
maxDate?: Date;
|
|
||||||
minAmount?: number;
|
|
||||||
maxAmount?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBankTransactionUnexcludingEventPayload {
|
export interface IBankTransactionUnexcludingEventPayload {
|
||||||
|
|||||||
@@ -1,25 +1,18 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { initialize } from 'objection';
|
import { initialize } from 'objection';
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { first } from 'lodash';
|
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
import { GetMatchedTransactionBillsTransformer } from './GetMatchedTransactionBillsTransformer';
|
import { GetMatchedTransactionBillsTransformer } from './GetMatchedTransactionBillsTransformer';
|
||||||
import {
|
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from './types';
|
||||||
GetMatchedTransactionsFilter,
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
IMatchTransactionDTO,
|
|
||||||
MatchedTransactionPOJO,
|
|
||||||
} from './types';
|
|
||||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
||||||
import { CreateBillPayment } from '@/services/Purchases/BillPayments/CreateBillPayment';
|
|
||||||
import { IBillPaymentDTO } from '@/interfaces';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType {
|
export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType {
|
||||||
@Inject()
|
@Inject()
|
||||||
private transformer: TransformerInjectable;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private createPaymentMadeService: CreateBillPayment;
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the matched transactions.
|
* Retrieves the matched transactions.
|
||||||
@@ -78,62 +71,4 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
|
|||||||
new GetMatchedTransactionBillsTransformer()
|
new GetMatchedTransactionBillsTransformer()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the common matched transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {Array<number>} uncategorizedTransactionIds
|
|
||||||
* @param {IMatchTransactionDTO} matchTransactionDTO
|
|
||||||
* @param {Knex.Transaction} trx
|
|
||||||
*/
|
|
||||||
public async createMatchedTransaction(
|
|
||||||
tenantId: number,
|
|
||||||
uncategorizedTransactionIds: Array<number>,
|
|
||||||
matchTransactionDTO: IMatchTransactionDTO,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<void> {
|
|
||||||
await super.createMatchedTransaction(
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransactionIds,
|
|
||||||
matchTransactionDTO,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
const { Bill, UncategorizedCashflowTransaction, MatchedBankTransaction } =
|
|
||||||
this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const uncategorizedTransactionId = first(uncategorizedTransactionIds);
|
|
||||||
const uncategorizedTransaction =
|
|
||||||
await UncategorizedCashflowTransaction.query(trx)
|
|
||||||
.findById(uncategorizedTransactionId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const bill = await Bill.query(trx)
|
|
||||||
.findById(matchTransactionDTO.referenceId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const createPaymentMadeDTO: IBillPaymentDTO = {
|
|
||||||
vendorId: bill.vendorId,
|
|
||||||
paymentAccountId: uncategorizedTransaction.accountId,
|
|
||||||
paymentDate: uncategorizedTransaction.date,
|
|
||||||
exchangeRate: 1,
|
|
||||||
entries: [
|
|
||||||
{
|
|
||||||
paymentAmount: bill.dueAmount,
|
|
||||||
billId: bill.id,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
branchId: bill.branchId,
|
|
||||||
};
|
|
||||||
// Create a new bill payment associated to the matched bill.
|
|
||||||
const billPayment = await this.createPaymentMadeService.createBillPayment(
|
|
||||||
tenantId,
|
|
||||||
createPaymentMadeDTO,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
// Link the create bill payment with matched transaction.
|
|
||||||
await super.createMatchedTransaction(tenantId, uncategorizedTransactionIds, {
|
|
||||||
referenceType: 'BillPayment',
|
|
||||||
referenceId: billPayment.id,
|
|
||||||
}, trx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,22 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { initialize } from 'objection';
|
import { initialize } from 'objection';
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { first } from 'lodash';
|
|
||||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer';
|
import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer';
|
||||||
import {
|
import {
|
||||||
GetMatchedTransactionsFilter,
|
GetMatchedTransactionsFilter,
|
||||||
IMatchTransactionDTO,
|
|
||||||
MatchedTransactionPOJO,
|
MatchedTransactionPOJO,
|
||||||
MatchedTransactionsPOJO,
|
MatchedTransactionsPOJO,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
|
||||||
import { CreatePaymentReceived } from '@/services/Sales/PaymentReceived/CreatePaymentReceived';
|
|
||||||
import { IPaymentReceivedCreateDTO } from '@/interfaces';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByType {
|
export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByType {
|
||||||
@Inject()
|
@Inject()
|
||||||
protected transformer: TransformerInjectable;
|
protected tenancy: HasTenancyService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
protected createPaymentReceivedService: CreatePaymentReceived;
|
protected transformer: TransformerInjectable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the matched transactions.
|
* Retrieves the matched transactions.
|
||||||
@@ -82,64 +78,4 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
|
|||||||
new GetMatchedTransactionInvoicesTransformer()
|
new GetMatchedTransactionInvoicesTransformer()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the common matched transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {Array<number>} uncategorizedTransactionIds
|
|
||||||
* @param {IMatchTransactionDTO} matchTransactionDTO
|
|
||||||
* @param {Knex.Transaction} trx
|
|
||||||
*/
|
|
||||||
public async createMatchedTransaction(
|
|
||||||
tenantId: number,
|
|
||||||
uncategorizedTransactionIds: Array<number>,
|
|
||||||
matchTransactionDTO: IMatchTransactionDTO,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
|
||||||
await super.createMatchedTransaction(
|
|
||||||
tenantId,
|
|
||||||
uncategorizedTransactionIds,
|
|
||||||
matchTransactionDTO,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
const { SaleInvoice, UncategorizedCashflowTransaction, MatchedBankTransaction } =
|
|
||||||
this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const uncategorizedTransactionId = first(uncategorizedTransactionIds);
|
|
||||||
const uncategorizedTransaction =
|
|
||||||
await UncategorizedCashflowTransaction.query(trx)
|
|
||||||
.findById(uncategorizedTransactionId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const invoice = await SaleInvoice.query(trx)
|
|
||||||
.findById(matchTransactionDTO.referenceId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const createPaymentReceivedDTO: IPaymentReceivedCreateDTO = {
|
|
||||||
customerId: invoice.customerId,
|
|
||||||
paymentDate: uncategorizedTransaction.date,
|
|
||||||
amount: invoice.dueAmount,
|
|
||||||
depositAccountId: uncategorizedTransaction.accountId,
|
|
||||||
entries: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
invoiceId: invoice.id,
|
|
||||||
paymentAmount: invoice.dueAmount,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
branchId: invoice.branchId,
|
|
||||||
};
|
|
||||||
// Create a payment received associated to the matched invoice.
|
|
||||||
const paymentReceived = await this.createPaymentReceivedService.createPaymentReceived(
|
|
||||||
tenantId,
|
|
||||||
createPaymentReceivedDTO,
|
|
||||||
{},
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
// Link the create payment received with matched invoice transaction.
|
|
||||||
await super.createMatchedTransaction(tenantId, uncategorizedTransactionIds, {
|
|
||||||
referenceType: 'PaymentReceive',
|
|
||||||
referenceId: paymentReceived.id,
|
|
||||||
}, trx)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
import { PlaidClientWrapper } from '@/lib/Plaid/Plaid';
|
import { PlaidClientWrapper } from '@/lib/Plaid/Plaid';
|
||||||
import { PlaidSyncDb } from './PlaidSyncDB';
|
import { PlaidSyncDb } from './PlaidSyncDB';
|
||||||
import { PlaidFetchedTransactionsUpdates } from '@/interfaces';
|
import { PlaidFetchedTransactionsUpdates } from '@/interfaces';
|
||||||
import UnitOfWork from '@/services/UnitOfWork';
|
import UnitOfWork from '@/services/UnitOfWork';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PlaidUpdateTransactions {
|
export class PlaidUpdateTransactions {
|
||||||
@@ -19,9 +19,9 @@ export class PlaidUpdateTransactions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles sync the Plaid item to Bigcaptial under UOW.
|
* Handles sync the Plaid item to Bigcaptial under UOW.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId
|
||||||
* @param {number} plaidItemId - Plaid item id.
|
* @param {number} plaidItemId
|
||||||
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
|
||||||
*/
|
*/
|
||||||
public async updateTransactions(tenantId: number, plaidItemId: string) {
|
public async updateTransactions(tenantId: number, plaidItemId: string) {
|
||||||
return this.uow.withTransaction(tenantId, (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(tenantId, (trx: Knex.Transaction) => {
|
||||||
|
|||||||
@@ -100,14 +100,12 @@ export class TriggerRecognizedTransactions {
|
|||||||
private async triggerRecognizeTransactionsOnImportCommitted({
|
private async triggerRecognizeTransactionsOnImportCommitted({
|
||||||
tenantId,
|
tenantId,
|
||||||
importId,
|
importId,
|
||||||
|
meta,
|
||||||
}: IImportFileCommitedEventPayload) {
|
}: IImportFileCommitedEventPayload) {
|
||||||
const importFile = await Import.query().findOne({ importId });
|
const importFile = await Import.query().findOne({ importId });
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,39 +26,31 @@ export class DeleteBankRuleSerivce {
|
|||||||
* @param {number} ruleId
|
* @param {number} ruleId
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async deleteBankRule(
|
public async deleteBankRule(tenantId: number, ruleId: number): Promise<void> {
|
||||||
tenantId: number,
|
|
||||||
ruleId: number,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
): Promise<void> {
|
|
||||||
const { BankRule, BankRuleCondition } = this.tenancy.models(tenantId);
|
const { BankRule, BankRuleCondition } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const oldBankRule = await BankRule.query()
|
const oldBankRule = await BankRule.query()
|
||||||
.findById(ruleId)
|
.findById(ruleId)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
return this.uow.withTransaction(
|
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||||
tenantId,
|
// Triggers `onBankRuleDeleting` event.
|
||||||
async (trx: Knex.Transaction) => {
|
await this.eventPublisher.emitAsync(events.bankRules.onDeleting, {
|
||||||
// Triggers `onBankRuleDeleting` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.bankRules.onDeleting, {
|
oldBankRule,
|
||||||
tenantId,
|
ruleId,
|
||||||
oldBankRule,
|
trx,
|
||||||
ruleId,
|
} as IBankRuleEventDeletingPayload);
|
||||||
trx,
|
|
||||||
} as IBankRuleEventDeletingPayload);
|
|
||||||
|
|
||||||
await BankRuleCondition.query(trx).where('ruleId', ruleId).delete()
|
await BankRuleCondition.query(trx).where('ruleId', ruleId).delete();
|
||||||
await BankRule.query(trx).findById(ruleId).delete();
|
await BankRule.query(trx).findById(ruleId).delete();
|
||||||
|
|
||||||
// Triggers `onBankRuleDeleted` event.
|
// Triggers `onBankRuleDeleted` event.
|
||||||
await await this.eventPublisher.emitAsync(events.bankRules.onDeleted, {
|
await await this.eventPublisher.emitAsync(events.bankRules.onDeleted, {
|
||||||
tenantId,
|
tenantId,
|
||||||
ruleId,
|
ruleId,
|
||||||
trx,
|
trx,
|
||||||
} as IBankRuleEventDeletedPayload);
|
} as IBankRuleEventDeletedPayload);
|
||||||
},
|
});
|
||||||
trx
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import PromisePool from '@supercharge/promise-pool';
|
|
||||||
import { castArray, uniq } from 'lodash';
|
|
||||||
import { DeleteBankRuleSerivce } from './DeleteBankRule';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class DeleteBankRulesService {
|
|
||||||
@Inject()
|
|
||||||
private deleteBankRuleService: DeleteBankRuleSerivce;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete bank rules.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number | Array<number>} bankRuleId
|
|
||||||
*/
|
|
||||||
async deleteBankRules(
|
|
||||||
tenantId: number,
|
|
||||||
bankRuleId: number | Array<number>,
|
|
||||||
trx?: Knex.Transaction
|
|
||||||
) {
|
|
||||||
const bankRulesIds = uniq(castArray(bankRuleId));
|
|
||||||
|
|
||||||
const results = await PromisePool.withConcurrency(1)
|
|
||||||
.for(bankRulesIds)
|
|
||||||
.process(async (bankRuleId: number) => {
|
|
||||||
await this.deleteBankRuleService.deleteBankRule(
|
|
||||||
tenantId,
|
|
||||||
bankRuleId,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,12 +8,7 @@ export class CashflowAccountTransformer extends Transformer {
|
|||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
public includeAttributes = (): string[] => {
|
public includeAttributes = (): string[] => {
|
||||||
return [
|
return ['formattedAmount'];
|
||||||
'formattedAmount',
|
|
||||||
'lastFeedsUpdatedAt',
|
|
||||||
'lastFeedsUpdatedAtFormatted',
|
|
||||||
'lastFeedsUpdatedFromNow',
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +29,7 @@ export class CashflowAccountTransformer extends Transformer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve formatted account amount.
|
* Retrieve formatted account amount.
|
||||||
* @param {IAccount} invoice
|
* @param {IAccount} invoice
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected formattedAmount = (account: IAccount): string => {
|
protected formattedAmount = (account: IAccount): string => {
|
||||||
@@ -42,22 +37,4 @@ export class CashflowAccountTransformer extends Transformer {
|
|||||||
currencyCode: account.currencyCode,
|
currencyCode: account.currencyCode,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the last feeds update at formatted date.
|
|
||||||
* @param {IAccount} account
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected lastFeedsUpdatedAtFormatted(account: IAccount): string {
|
|
||||||
return this.formatDate(account.lastFeedsUpdatedAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the last feeds updated from now.
|
|
||||||
* @param {IAccount} account
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected lastFeedsUpdatedFromNow(account: IAccount): string {
|
|
||||||
return this.formatDateFromNow(account.lastFeedsUpdatedAt);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class GetRecognizedTransactionsService {
|
|||||||
) {
|
) {
|
||||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const _query = {
|
const _filter = {
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
...filter,
|
...filter,
|
||||||
@@ -41,26 +41,11 @@ export class GetRecognizedTransactionsService {
|
|||||||
// Exclude the pending transactions.
|
// Exclude the pending transactions.
|
||||||
q.modify('notPending');
|
q.modify('notPending');
|
||||||
|
|
||||||
if (_query.accountId) {
|
if (_filter.accountId) {
|
||||||
q.where('accountId', _query.accountId);
|
q.where('accountId', _filter.accountId);
|
||||||
}
|
|
||||||
if (_query.minDate) {
|
|
||||||
q.modify('fromDate', _query.minDate);
|
|
||||||
}
|
|
||||||
if (_query.maxDate) {
|
|
||||||
q.modify('toDate', _query.maxDate);
|
|
||||||
}
|
|
||||||
if (_query.minAmount) {
|
|
||||||
q.modify('minAmount', _query.minAmount);
|
|
||||||
}
|
|
||||||
if (_query.maxAmount) {
|
|
||||||
q.modify('maxAmount', _query.maxAmount);
|
|
||||||
}
|
|
||||||
if (_query.accountId) {
|
|
||||||
q.where('accountId', _query.accountId);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.pagination(_query.page - 1, _query.pageSize);
|
.pagination(_filter.page - 1, _filter.pageSize);
|
||||||
|
|
||||||
const data = await this.transformer.transform(
|
const data = await this.transformer.transform(
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
@@ -62,19 +62,6 @@ export class GetUncategorizedTransactions {
|
|||||||
|
|
||||||
q.whereNull('matchedBankTransactions.id');
|
q.whereNull('matchedBankTransactions.id');
|
||||||
q.orderBy('date', 'DESC');
|
q.orderBy('date', 'DESC');
|
||||||
|
|
||||||
if (_query.minDate) {
|
|
||||||
q.modify('fromDate', _query.minDate);
|
|
||||||
}
|
|
||||||
if (_query.maxDate) {
|
|
||||||
q.modify('toDate', _query.maxDate);
|
|
||||||
}
|
|
||||||
if (_query.minAmount) {
|
|
||||||
q.modify('minAmount', _query.minAmount);
|
|
||||||
}
|
|
||||||
if (_query.maxAmount) {
|
|
||||||
q.modify('maxAmount', _query.maxAmount);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.pagination(_query.page - 1, _query.pageSize);
|
.pagination(_query.page - 1, _query.pageSize);
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export class UncategorizedTransactionsImportable extends Importable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms the import params before storing them.
|
* Transformes 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>) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { IItemsFilter } from '@/interfaces';
|
import { IItemsFilter } from '@/interfaces';
|
||||||
import { CustomersApplication } from './CustomersApplication';
|
import { CustomersApplication } from './CustomersApplication';
|
||||||
import { Exportable } from '@/services/Export/Exportable';
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CustomersExportable extends Exportable {
|
export class CustomersExportable extends Exportable {
|
||||||
@@ -18,9 +17,9 @@ export class CustomersExportable extends Exportable {
|
|||||||
const parsedQuery = {
|
const parsedQuery = {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...query,
|
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: EXPORT_SIZE_LIMIT,
|
...query,
|
||||||
|
pageSize: 12,
|
||||||
} as IItemsFilter;
|
} as IItemsFilter;
|
||||||
|
|
||||||
return this.customersApplication
|
return this.customersApplication
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { IItemsFilter } from '@/interfaces';
|
import { IItemsFilter } from '@/interfaces';
|
||||||
import { Exportable } from '@/services/Export/Exportable';
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
import { VendorsApplication } from './VendorsApplication';
|
import { VendorsApplication } from './VendorsApplication';
|
||||||
import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class VendorsExportable extends Exportable {
|
export class VendorsExportable extends Exportable {
|
||||||
@@ -18,9 +17,9 @@ export class VendorsExportable extends Exportable {
|
|||||||
const parsedQuery = {
|
const parsedQuery = {
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'DESC',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...query,
|
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: EXPORT_SIZE_LIMIT,
|
...query,
|
||||||
|
pageSize: 12,
|
||||||
} as IItemsFilter;
|
} as IItemsFilter;
|
||||||
|
|
||||||
return this.vendorsApplication
|
return this.vendorsApplication
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export class ExpenseTransfromer extends Transformer {
|
|||||||
'formattedAllocatedCostAmount',
|
'formattedAllocatedCostAmount',
|
||||||
'formattedDate',
|
'formattedDate',
|
||||||
'formattedCreatedAt',
|
'formattedCreatedAt',
|
||||||
'formattedPublishedAt',
|
|
||||||
'categories',
|
'categories',
|
||||||
'attachments',
|
'attachments',
|
||||||
];
|
];
|
||||||
@@ -92,13 +91,4 @@ export class ExpenseTransfromer extends Transformer {
|
|||||||
protected attachments = (expense: IExpense) => {
|
protected attachments = (expense: IExpense) => {
|
||||||
return this.item(expense.attachments, new AttachmentTransformer());
|
return this.item(expense.attachments, new AttachmentTransformer());
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve formatted published at date.
|
|
||||||
* @param {IExpense} expense
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected formattedPublishedAt = (expense: IExpense): string => {
|
|
||||||
return this.formatDate(expense.publishedAt);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { Exportable } from '../Export/Exportable';
|
import { Exportable } from '../Export/Exportable';
|
||||||
import { IExpensesFilter } from '@/interfaces';
|
import { IExpensesFilter } from '@/interfaces';
|
||||||
import { ExpensesApplication } from './ExpensesApplication';
|
import { ExpensesApplication } from './ExpensesApplication';
|
||||||
import { EXPORT_SIZE_LIMIT } from '../Export/constants';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ExpensesExportable extends Exportable {
|
export class ExpensesExportable extends Exportable {
|
||||||
@@ -20,7 +19,7 @@ export class ExpensesExportable extends Exportable {
|
|||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...query,
|
...query,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: EXPORT_SIZE_LIMIT,
|
pageSize: 12000,
|
||||||
} as IExpensesFilter;
|
} as IExpensesFilter;
|
||||||
|
|
||||||
return this.expensesApplication
|
return this.expensesApplication
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ 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 {
|
||||||
@@ -23,33 +22,13 @@ 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 exportAlsRun(
|
public async export(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
resourceName: string,
|
resourceName: string,
|
||||||
format: ExportFormat = ExportFormat.Csv
|
format: ExportFormat = ExportFormat.Csv
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
export const EXPORT_SIZE_LIMIT = 9999999;
|
|
||||||
export const EXPORT_DTE_FORMAT = 'YYYY-MM-DD';
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import { Service } from 'typedi';
|
|
||||||
import { AsyncLocalStorage } from 'async_hooks';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class ImportAls {
|
|
||||||
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(), callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs a callback function in preview mode within the AsyncLocalStorage context.
|
|
||||||
* @param callback The function to be executed in preview mode.
|
|
||||||
* @returns The result of the callback function.
|
|
||||||
*/
|
|
||||||
public runPreview<T>(callback: () => T): T {
|
|
||||||
return this.run(() => {
|
|
||||||
this.markAsImport();
|
|
||||||
this.markAsImportPreview();
|
|
||||||
return callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs a callback function in commit mode within the AsyncLocalStorage context.
|
|
||||||
* @param {() => T} callback - The function to be executed in commit mode.
|
|
||||||
* @returns {T} The result of the callback function.
|
|
||||||
*/
|
|
||||||
public runCommit<T>(callback: () => T): T {
|
|
||||||
return this.run(() => {
|
|
||||||
this.markAsImport();
|
|
||||||
this.markAsImportCommit();
|
|
||||||
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 import operation.
|
|
||||||
* @param flag Boolean flag to set or unset the import status. Defaults to true.
|
|
||||||
*/
|
|
||||||
public markAsImport(flag: boolean = true): void {
|
|
||||||
const store = this.getStore();
|
|
||||||
store?.set('isImport', flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks the current context as an import commit operation.
|
|
||||||
* @param flag Boolean flag to set or unset the import commit status. Defaults to true.
|
|
||||||
*/
|
|
||||||
public markAsImportCommit(flag: boolean = true): void {
|
|
||||||
const store = this.getStore();
|
|
||||||
store?.set('isImportCommit', flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks the current context as an import preview operation.
|
|
||||||
* @param {boolean} flag - Boolean flag to set or unset the import preview status. Defaults to true.
|
|
||||||
*/
|
|
||||||
public markAsImportPreview(flag: boolean = true): void {
|
|
||||||
const store = this.getStore();
|
|
||||||
store?.set('isImportPreview', flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current context is an import operation.
|
|
||||||
* @returns {boolean} True if the context is an import operation, false otherwise.
|
|
||||||
*/
|
|
||||||
public get isImport(): boolean {
|
|
||||||
return !!this.getStore()?.get('isImport');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current context is an import commit operation.
|
|
||||||
* @returns {boolean} True if the context is an import commit operation, false otherwise.
|
|
||||||
*/
|
|
||||||
public get isImportCommit(): boolean {
|
|
||||||
return !!this.getStore()?.get('isImportCommit');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current context is an import preview operation.
|
|
||||||
* @returns {boolean} True if the context is an import preview operation, false otherwise.
|
|
||||||
*/
|
|
||||||
public get isImportPreview(): boolean {
|
|
||||||
return !!this.getStore()?.get('isImportPreview');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import XLSX from 'xlsx';
|
||||||
import bluebird from 'bluebird';
|
import bluebird from 'bluebird';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
@@ -27,6 +28,22 @@ export class ImportFileCommon {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private resource: ResourceService;
|
private resource: ResourceService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the columns of the imported data based on the provided mapping attributes.
|
||||||
|
* @param {Record<string, any>[]} body - The array of data objects to map.
|
||||||
|
* @param {ImportMappingAttr[]} map - The mapping attributes.
|
||||||
|
* @returns {Record<string, any>[]} - The mapped data objects.
|
||||||
|
*/
|
||||||
|
public parseXlsxSheet(buffer: Buffer): Record<string, unknown>[] {
|
||||||
|
const workbook = XLSX.read(buffer, { type: 'buffer', raw: true });
|
||||||
|
|
||||||
|
const firstSheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[firstSheetName];
|
||||||
|
|
||||||
|
return XLSX.utils.sheet_to_json(worksheet, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports the given parsed data to the resource storage through registered importable service.
|
* Imports the given parsed data to the resource storage through registered importable service.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class ImportFileMapping {
|
|||||||
*/
|
*/
|
||||||
public async mapping(
|
public async mapping(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
importId: string,
|
importId: number,
|
||||||
maps: ImportMappingAttr[]
|
maps: ImportMappingAttr[]
|
||||||
): Promise<ImportFileMapPOJO> {
|
): Promise<ImportFileMapPOJO> {
|
||||||
const importFile = await Import.query()
|
const importFile = await Import.query()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
import { ImportFilePreviewPOJO } from './interfaces';
|
import { ImportFilePreviewPOJO } from './interfaces';
|
||||||
import { ImportFileProcess } from './ImportFileProcess';
|
import { ImportFileProcess } from './ImportFileProcess';
|
||||||
import { ImportAls } from './ImportALS';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportFilePreview {
|
export class ImportFilePreview {
|
||||||
@@ -12,33 +11,15 @@ export class ImportFilePreview {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private importFile: ImportFileProcess;
|
private importFile: ImportFileProcess;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private importAls: ImportAls;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preview the imported file results before commiting the transactions.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {string} importId -
|
|
||||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
|
||||||
*/
|
|
||||||
public async preview(
|
|
||||||
tenantId: number,
|
|
||||||
importId: string
|
|
||||||
): Promise<ImportFilePreviewPOJO> {
|
|
||||||
return this.importAls.runPreview<Promise<ImportFilePreviewPOJO>>(() =>
|
|
||||||
this.previewAlsRun(tenantId, importId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preview the imported file results before commiting the transactions.
|
* Preview the imported file results before commiting the transactions.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} importId
|
* @param {number} importId
|
||||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
* @returns {Promise<ImportFilePreviewPOJO>}
|
||||||
*/
|
*/
|
||||||
public async previewAlsRun(
|
public async preview(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
importId: string
|
importId: number
|
||||||
): 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' });
|
||||||
|
|||||||
@@ -2,14 +2,18 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { chain } from 'lodash';
|
import { chain } from 'lodash';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import { ERRORS, getUnmappedSheetColumns, readImportFile } from './_utils';
|
import {
|
||||||
|
ERRORS,
|
||||||
|
getSheetColumns,
|
||||||
|
getUnmappedSheetColumns,
|
||||||
|
readImportFile,
|
||||||
|
} from './_utils';
|
||||||
import { ImportFileCommon } from './ImportFileCommon';
|
import { ImportFileCommon } from './ImportFileCommon';
|
||||||
import { ImportFileDataTransformer } from './ImportFileDataTransformer';
|
import { ImportFileDataTransformer } from './ImportFileDataTransformer';
|
||||||
import ResourceService from '../Resource/ResourceService';
|
import ResourceService from '../Resource/ResourceService';
|
||||||
import UnitOfWork from '../UnitOfWork';
|
import UnitOfWork from '../UnitOfWork';
|
||||||
import { ImportFilePreviewPOJO } from './interfaces';
|
import { ImportFilePreviewPOJO } from './interfaces';
|
||||||
import { Import } from '@/system/models';
|
import { Import } from '@/system/models';
|
||||||
import { parseSheetData } from './sheet_utils';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportFileProcess {
|
export class ImportFileProcess {
|
||||||
@@ -33,7 +37,7 @@ export class ImportFileProcess {
|
|||||||
*/
|
*/
|
||||||
public async import(
|
public async import(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
importId: string,
|
importId: number,
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction
|
||||||
): Promise<ImportFilePreviewPOJO> {
|
): Promise<ImportFilePreviewPOJO> {
|
||||||
const importFile = await Import.query()
|
const importFile = await Import.query()
|
||||||
@@ -45,10 +49,10 @@ export class ImportFileProcess {
|
|||||||
if (!importFile.isMapped) {
|
if (!importFile.isMapped) {
|
||||||
throw new ServiceError(ERRORS.IMPORT_FILE_NOT_MAPPED);
|
throw new ServiceError(ERRORS.IMPORT_FILE_NOT_MAPPED);
|
||||||
}
|
}
|
||||||
// Read the imported file and parse the given buffer to get columns
|
// Read the imported file.
|
||||||
// and sheet data in json format.
|
|
||||||
const buffer = await readImportFile(importFile.filename);
|
const buffer = await readImportFile(importFile.filename);
|
||||||
const [sheetData, sheetColumns] = parseSheetData(buffer);
|
const sheetData = this.importCommon.parseXlsxSheet(buffer);
|
||||||
|
const header = getSheetColumns(sheetData);
|
||||||
|
|
||||||
const resource = importFile.resource;
|
const resource = importFile.resource;
|
||||||
const resourceFields = this.resource.getResourceFields2(tenantId, resource);
|
const resourceFields = this.resource.getResourceFields2(tenantId, resource);
|
||||||
@@ -83,7 +87,7 @@ export class ImportFileProcess {
|
|||||||
.flatten()
|
.flatten()
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
const unmappedColumns = getUnmappedSheetColumns(sheetColumns, mapping);
|
const unmappedColumns = getUnmappedSheetColumns(header, mapping);
|
||||||
const totalCount = allData.length;
|
const totalCount = allData.length;
|
||||||
|
|
||||||
const createdCount = successedImport.length;
|
const createdCount = successedImport.length;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { ImportFileProcess } from './ImportFileProcess';
|
|||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import { IImportFileCommitedEventPayload } from '@/interfaces/Import';
|
import { IImportFileCommitedEventPayload } from '@/interfaces/Import';
|
||||||
import { ImportAls } from './ImportALS';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportFileProcessCommit {
|
export class ImportFileProcessCommit {
|
||||||
@@ -15,36 +14,18 @@ export class ImportFileProcessCommit {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private importFile: ImportFileProcess;
|
private importFile: ImportFileProcess;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private importAls: ImportAls;
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private eventPublisher: EventPublisher;
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
/**
|
|
||||||
* Commits the imported file under ALS.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {string} importId
|
|
||||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
|
||||||
*/
|
|
||||||
public commit(
|
|
||||||
tenantId: number,
|
|
||||||
importId: string
|
|
||||||
): Promise<ImportFilePreviewPOJO> {
|
|
||||||
return this.importAls.runCommit<Promise<ImportFilePreviewPOJO>>(() =>
|
|
||||||
this.commitAlsRun(tenantId, importId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commits the imported file.
|
* Commits the imported file.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} importId
|
* @param {number} importId
|
||||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
* @returns {Promise<ImportFilePreviewPOJO>}
|
||||||
*/
|
*/
|
||||||
public async commitAlsRun(
|
public async commit(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
importId: string
|
importId: number
|
||||||
): 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' });
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { ImportFileCommon } from './ImportFileCommon';
|
|||||||
import { ImportFileDataValidator } from './ImportFileDataValidator';
|
import { ImportFileDataValidator } from './ImportFileDataValidator';
|
||||||
import { ImportFileUploadPOJO } from './interfaces';
|
import { ImportFileUploadPOJO } from './interfaces';
|
||||||
import { Import } from '@/system/models';
|
import { Import } from '@/system/models';
|
||||||
import { parseSheetData } from './sheet_utils';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportFileUploadService {
|
export class ImportFileUploadService {
|
||||||
@@ -78,12 +77,14 @@ export class ImportFileUploadService {
|
|||||||
const buffer = await readImportFile(filename);
|
const buffer = await readImportFile(filename);
|
||||||
|
|
||||||
// Parse the buffer file to array data.
|
// Parse the buffer file to array data.
|
||||||
const [sheetData, sheetColumns] = parseSheetData(buffer);
|
const sheetData = this.importFileCommon.parseXlsxSheet(buffer);
|
||||||
const coumnsStringified = JSON.stringify(sheetColumns);
|
|
||||||
|
|
||||||
// Throws service error if the sheet data is empty.
|
// Throws service error if the sheet data is empty.
|
||||||
validateSheetEmpty(sheetData);
|
validateSheetEmpty(sheetData);
|
||||||
|
|
||||||
|
const sheetColumns = this.importFileCommon.parseSheetColumns(sheetData);
|
||||||
|
const coumnsStringified = JSON.stringify(sheetColumns);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Validates the params Yup schema.
|
// Validates the params Yup schema.
|
||||||
await this.importFileCommon.validateParamsSchema(resource, params);
|
await this.importFileCommon.validateParamsSchema(resource, params);
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export class ImportResourceApplication {
|
|||||||
*/
|
*/
|
||||||
public async mapping(
|
public async mapping(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
importId: string,
|
importId: number,
|
||||||
maps: ImportMappingAttr[]
|
maps: ImportMappingAttr[]
|
||||||
) {
|
) {
|
||||||
return this.importMappingService.mapping(tenantId, importId, maps);
|
return this.importMappingService.mapping(tenantId, importId, maps);
|
||||||
@@ -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: string) {
|
public async preview(tenantId: number, importId: number) {
|
||||||
return this.ImportFilePreviewService.preview(tenantId, importId);
|
return this.ImportFilePreviewService.preview(tenantId, importId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ export class ImportResourceApplication {
|
|||||||
* @param {number} importId
|
* @param {number} importId
|
||||||
* @returns {Promise<ImportFilePreviewPOJO>}
|
* @returns {Promise<ImportFilePreviewPOJO>}
|
||||||
*/
|
*/
|
||||||
public async process(tenantId: number, importId: string) {
|
public async process(tenantId: number, importId: number) {
|
||||||
return this.importProcessCommit.commit(tenantId, importId);
|
return this.importProcessCommit.commit(tenantId, importId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
import XLSX from 'xlsx';
|
|
||||||
import { first } from 'lodash';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the given sheet buffer to worksheet.
|
|
||||||
* @param {Buffer} buffer
|
|
||||||
* @returns {XLSX.WorkSheet}
|
|
||||||
*/
|
|
||||||
export function parseFirstSheet(buffer: Buffer): XLSX.WorkSheet {
|
|
||||||
const workbook = XLSX.read(buffer, { type: 'buffer', raw: true });
|
|
||||||
|
|
||||||
const firstSheetName = workbook.SheetNames[0];
|
|
||||||
const worksheet = workbook.Sheets[firstSheetName];
|
|
||||||
|
|
||||||
return worksheet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the given worksheet to columns.
|
|
||||||
* @param {XLSX.WorkSheet} worksheet
|
|
||||||
* @returns {Array<string>}
|
|
||||||
*/
|
|
||||||
export function extractSheetColumns(worksheet: XLSX.WorkSheet): Array<string> {
|
|
||||||
// By default, sheet_to_json scans the first row and uses the values as headers.
|
|
||||||
// With the header: 1 option, the function exports an array of arrays of values.
|
|
||||||
const sheetCells = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
|
||||||
const sheetCols = first(sheetCells) as Array<string>;
|
|
||||||
|
|
||||||
return sheetCols.filter((col) => col);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the given worksheet to json values. the keys are columns labels.
|
|
||||||
* @param {XLSX.WorkSheet} worksheet
|
|
||||||
* @returns {Array<Record<string, string>>}
|
|
||||||
*/
|
|
||||||
export function parseSheetToJson(
|
|
||||||
worksheet: XLSX.WorkSheet
|
|
||||||
): Array<Record<string, string>> {
|
|
||||||
return XLSX.utils.sheet_to_json(worksheet, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the given sheet buffer then retrieves the sheet data and columns.
|
|
||||||
* @param {Buffer} buffer
|
|
||||||
*/
|
|
||||||
export function parseSheetData(
|
|
||||||
buffer: Buffer
|
|
||||||
): [Array<Record<string, string>>, string[]] {
|
|
||||||
const worksheet = parseFirstSheet(buffer);
|
|
||||||
|
|
||||||
const columns = extractSheetColumns(worksheet);
|
|
||||||
const data = parseSheetToJson(worksheet);
|
|
||||||
|
|
||||||
return [data, columns];
|
|
||||||
}
|
|
||||||
@@ -139,25 +139,24 @@ export default class InventoryService {
|
|||||||
) {
|
) {
|
||||||
const agenda = Container.get('agenda');
|
const agenda = Container.get('agenda');
|
||||||
|
|
||||||
const commonJobsQuery = {
|
|
||||||
name: 'compute-item-cost',
|
|
||||||
lastRunAt: { $exists: false },
|
|
||||||
'data.tenantId': tenantId,
|
|
||||||
'data.itemId': itemId,
|
|
||||||
};
|
|
||||||
// Cancel any `compute-item-cost` in the queue has upper starting date
|
// Cancel any `compute-item-cost` in the queue has upper starting date
|
||||||
// with the same given item.
|
// with the same given item.
|
||||||
await agenda.cancel({
|
await agenda.cancel({
|
||||||
...commonJobsQuery,
|
name: 'compute-item-cost',
|
||||||
'data.startingDate': { $lte: startingDate },
|
nextRunAt: { $ne: null },
|
||||||
|
'data.tenantId': tenantId,
|
||||||
|
'data.itemId': itemId,
|
||||||
|
'data.startingDate': { $gt: startingDate },
|
||||||
});
|
});
|
||||||
// Retrieve any `compute-item-cost` in the queue has lower starting date
|
// Retrieve any `compute-item-cost` in the queue has lower starting date
|
||||||
// with the same given item.
|
// with the same given item.
|
||||||
const dependsJobs = await agenda.jobs({
|
const dependsJobs = await agenda.jobs({
|
||||||
...commonJobsQuery,
|
name: 'compute-item-cost',
|
||||||
'data.startingDate': { $gte: startingDate },
|
nextRunAt: { $ne: null },
|
||||||
|
'data.tenantId': tenantId,
|
||||||
|
'data.itemId': itemId,
|
||||||
|
'data.startingDate': { $lte: startingDate },
|
||||||
});
|
});
|
||||||
// If the depends jobs cleared.
|
|
||||||
if (dependsJobs.length === 0) {
|
if (dependsJobs.length === 0) {
|
||||||
await agenda.schedule(
|
await agenda.schedule(
|
||||||
config.scheduleComputeItemCost,
|
config.scheduleComputeItemCost,
|
||||||
@@ -173,13 +172,6 @@ export default class InventoryService {
|
|||||||
events.inventory.onComputeItemCostJobScheduled,
|
events.inventory.onComputeItemCostJobScheduled,
|
||||||
{ startingDate, itemId, tenantId } as IInventoryItemCostScheduledPayload
|
{ startingDate, itemId, tenantId } as IInventoryItemCostScheduledPayload
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// Re-schedule the jobs that have higher date from current moment.
|
|
||||||
await Promise.all(
|
|
||||||
dependsJobs.map((job) =>
|
|
||||||
job.schedule(config.scheduleComputeItemCost).save()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,22 +43,12 @@ export class CreateItem {
|
|||||||
itemDTO.sellAccountId
|
itemDTO.sellAccountId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Validate the income account id existance if the item is sellable.
|
|
||||||
this.validators.validateIncomeAccountExistance(
|
|
||||||
itemDTO.sellable,
|
|
||||||
itemDTO.sellAccountId
|
|
||||||
);
|
|
||||||
if (itemDTO.costAccountId) {
|
if (itemDTO.costAccountId) {
|
||||||
await this.validators.validateItemCostAccountExistance(
|
await this.validators.validateItemCostAccountExistance(
|
||||||
tenantId,
|
tenantId,
|
||||||
itemDTO.costAccountId
|
itemDTO.costAccountId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Validate the cost account id existance if the item is purchasable.
|
|
||||||
this.validators.validateCostAccountExistance(
|
|
||||||
itemDTO.purchasable,
|
|
||||||
itemDTO.costAccountId
|
|
||||||
);
|
|
||||||
if (itemDTO.inventoryAccountId) {
|
if (itemDTO.inventoryAccountId) {
|
||||||
await this.validators.validateItemInventoryAccountExistance(
|
await this.validators.validateItemInventoryAccountExistance(
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
@@ -55,11 +55,6 @@ export class EditItem {
|
|||||||
itemDTO.categoryId
|
itemDTO.categoryId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Validate the income account id existance if the item is sellable.
|
|
||||||
this.validators.validateIncomeAccountExistance(
|
|
||||||
itemDTO.sellable,
|
|
||||||
itemDTO.sellAccountId
|
|
||||||
);
|
|
||||||
// Validate the sell account existance on the storage.
|
// Validate the sell account existance on the storage.
|
||||||
if (itemDTO.sellAccountId) {
|
if (itemDTO.sellAccountId) {
|
||||||
await this.validators.validateItemSellAccountExistance(
|
await this.validators.validateItemSellAccountExistance(
|
||||||
@@ -67,11 +62,6 @@ export class EditItem {
|
|||||||
itemDTO.sellAccountId
|
itemDTO.sellAccountId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Validate the cost account id existance if the item is purchasable.
|
|
||||||
this.validators.validateCostAccountExistance(
|
|
||||||
itemDTO.purchasable,
|
|
||||||
itemDTO.costAccountId
|
|
||||||
);
|
|
||||||
// Validate the cost account existance on the storage.
|
// Validate the cost account existance on the storage.
|
||||||
if (itemDTO.costAccountId) {
|
if (itemDTO.costAccountId) {
|
||||||
await this.validators.validateItemCostAccountExistance(
|
await this.validators.validateItemCostAccountExistance(
|
||||||
|
|||||||
@@ -85,42 +85,6 @@ export class ItemsValidators {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates income account existance.
|
|
||||||
* @param {number|null} sellable - Detarmines if the item sellable.
|
|
||||||
* @param {number|null} incomeAccountId - Income account id.
|
|
||||||
* @throws {ServiceError(ERRORS.INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM)}
|
|
||||||
*/
|
|
||||||
public validateIncomeAccountExistance(
|
|
||||||
sellable?: boolean,
|
|
||||||
incomeAccountId?: number
|
|
||||||
) {
|
|
||||||
if (sellable && !incomeAccountId) {
|
|
||||||
throw new ServiceError(
|
|
||||||
ERRORS.INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM,
|
|
||||||
'Income account is require with sellable item.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the cost account existance.
|
|
||||||
* @param {boolean|null} purchasable - Detarmines if the item purchasble.
|
|
||||||
* @param {number|null} costAccountId - Cost account id.
|
|
||||||
* @throws {ServiceError(ERRORS.COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM)}
|
|
||||||
*/
|
|
||||||
public validateCostAccountExistance(
|
|
||||||
purchasable: boolean,
|
|
||||||
costAccountId?: number
|
|
||||||
) {
|
|
||||||
if (purchasable && !costAccountId) {
|
|
||||||
throw new ServiceError(
|
|
||||||
ERRORS.COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM,
|
|
||||||
'The cost account is required with purchasable item.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate item inventory account existance and type.
|
* Validate item inventory account existance and type.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ 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',
|
||||||
@@ -59,14 +58,13 @@ 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(trx)
|
const inventoryItems = await Item.query()
|
||||||
.whereIn('id', entriesItemsIds)
|
.whereIn('id', entriesItemsIds)
|
||||||
.where('type', 'inventory');
|
.where('type', 'inventory');
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { Exportable } from '../Export/Exportable';
|
import { Exportable } from '../Export/Exportable';
|
||||||
import { IItemsFilter } from '@/interfaces';
|
import { IItemsFilter } from '@/interfaces';
|
||||||
import { ItemsApplication } from './ItemsApplication';
|
import { ItemsApplication } from './ItemsApplication';
|
||||||
import { EXPORT_SIZE_LIMIT } from '../Export/constants';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ItemsExportable extends Exportable {
|
export class ItemsExportable extends Exportable {
|
||||||
@@ -20,7 +19,7 @@ export class ItemsExportable extends Exportable {
|
|||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
...query,
|
...query,
|
||||||
pageSize: EXPORT_SIZE_LIMIT,
|
pageSize: 12,
|
||||||
} as IItemsFilter;
|
} as IItemsFilter;
|
||||||
|
|
||||||
return this.itemsApplication
|
return this.itemsApplication
|
||||||
|
|||||||
@@ -26,11 +26,6 @@ export const ERRORS = {
|
|||||||
|
|
||||||
PURCHASE_TAX_RATE_NOT_FOUND: 'PURCHASE_TAX_RATE_NOT_FOUND',
|
PURCHASE_TAX_RATE_NOT_FOUND: 'PURCHASE_TAX_RATE_NOT_FOUND',
|
||||||
SELL_TAX_RATE_NOT_FOUND: 'SELL_TAX_RATE_NOT_FOUND',
|
SELL_TAX_RATE_NOT_FOUND: 'SELL_TAX_RATE_NOT_FOUND',
|
||||||
|
|
||||||
INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM:
|
|
||||||
'INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM',
|
|
||||||
COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM:
|
|
||||||
'COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_VIEW_COLUMNS = [];
|
export const DEFAULT_VIEW_COLUMNS = [];
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { IManualJournalsFilter } from '@/interfaces';
|
import { IManualJournalsFilter } from '@/interfaces';
|
||||||
import { Exportable } from '../Export/Exportable';
|
import { Exportable } from '../Export/Exportable';
|
||||||
import { ManualJournalsApplication } from './ManualJournalsApplication';
|
import { ManualJournalsApplication } from './ManualJournalsApplication';
|
||||||
import { EXPORT_SIZE_LIMIT } from '../Export/constants';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ManualJournalsExportable extends Exportable {
|
export class ManualJournalsExportable extends Exportable {
|
||||||
@@ -20,7 +19,7 @@ export class ManualJournalsExportable extends Exportable {
|
|||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...query,
|
...query,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: EXPORT_SIZE_LIMIT,
|
pageSize: 12000,
|
||||||
} as IManualJournalsFilter;
|
} as IManualJournalsFilter;
|
||||||
|
|
||||||
return this.manualJournalsApplication
|
return this.manualJournalsApplication
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { faker } from '@faker-js/faker';
|
|
||||||
import uniqid from 'uniqid';
|
|
||||||
import AuthenticationApplication from '../Authentication/AuthApplication';
|
|
||||||
import OrganizationService from '../Organization/OrganizationService';
|
|
||||||
import { OneClickDemo } from '@/system/models/OneclickDemo';
|
|
||||||
import { SystemUser } from '@/system/models';
|
|
||||||
import { IAuthSignInPOJO } from '@/interfaces';
|
|
||||||
import { ICreateOneClickDemoPOJO } from './interfaces';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
||||||
import { defaultDemoOrganizationDTO } from './_constants';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class CreateOneClickDemo {
|
|
||||||
@Inject()
|
|
||||||
private authApp: AuthenticationApplication;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private organizationService: OrganizationService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private eventPublisher: EventPublisher;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates one-click demo account.
|
|
||||||
* @returns {Promise<ICreateOneClickDemoPOJO>}
|
|
||||||
*/
|
|
||||||
public async createOneClickDemo(): Promise<ICreateOneClickDemoPOJO> {
|
|
||||||
const firstName = faker.person.firstName();
|
|
||||||
const lastName = faker.person.lastName();
|
|
||||||
const email = faker.internet.email();
|
|
||||||
const password = '123123123';
|
|
||||||
const demoId = uniqid();
|
|
||||||
|
|
||||||
await this.authApp.signUp({ firstName, lastName, email, password });
|
|
||||||
|
|
||||||
const signedIn = await this.authApp.signIn(email, password);
|
|
||||||
const tenantId = signedIn.tenant.id;
|
|
||||||
const userId = signedIn.user.id;
|
|
||||||
|
|
||||||
// Creates a new one-click demo.
|
|
||||||
await OneClickDemo.query().insert({ key: demoId, tenantId, userId });
|
|
||||||
|
|
||||||
const buildJob = await this.organizationService.buildRunJob(
|
|
||||||
tenantId,
|
|
||||||
defaultDemoOrganizationDTO,
|
|
||||||
signedIn.user
|
|
||||||
);
|
|
||||||
return { email, demoId, signedIn, buildJob };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sign-in automicatlly using the demo id one creating an account finish.
|
|
||||||
* @param {string} oneClickDemoId -
|
|
||||||
* @returns {Promise<IAuthSignInPOJO>}
|
|
||||||
*/
|
|
||||||
async autoSignIn(oneClickDemoId: string): Promise<IAuthSignInPOJO> {
|
|
||||||
const foundOneclickDemo = await OneClickDemo.query()
|
|
||||||
.findOne('key', oneClickDemoId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const userId = foundOneclickDemo.userId;
|
|
||||||
const user = await SystemUser.query().findById(userId);
|
|
||||||
|
|
||||||
const email = user.email;
|
|
||||||
const password = '123123123';
|
|
||||||
|
|
||||||
const signedIn = await this.authApp.signIn(email, password);
|
|
||||||
|
|
||||||
return signedIn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
export class SeedDemoAbstract {
|
|
||||||
/**
|
|
||||||
* Retrieves the seeder file mapping.
|
|
||||||
* @returns {Array<>}
|
|
||||||
*/
|
|
||||||
get mapping() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retireves the seeder file import params.
|
|
||||||
* @returns {Record<string, any>}
|
|
||||||
*/
|
|
||||||
get importParams() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
|
||||||
|
|
||||||
export class SeedDemoBankTransactions extends SeedDemoAbstract {
|
|
||||||
get mapping() {
|
|
||||||
return [
|
|
||||||
{ from: 'Date', to: 'date' },
|
|
||||||
{ from: 'Payee', to: 'payee' },
|
|
||||||
{ from: 'Description', to: 'description' },
|
|
||||||
{ from: 'Reference No.', to: 'referenceNo' },
|
|
||||||
{ from: 'Amount', to: 'amount' },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the seeder file name.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get importFileName() {
|
|
||||||
return `bank-transactions.csv`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the resource name of the seeder.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get resource() {
|
|
||||||
return 'UncategorizedCashflowTransaction';
|
|
||||||
}
|
|
||||||
|
|
||||||
get importParams() {
|
|
||||||
return {
|
|
||||||
accountId: 1001,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
|
||||||
|
|
||||||
export class SeedDemoAccountCustomers extends SeedDemoAbstract {
|
|
||||||
/**
|
|
||||||
* Retrieves the seeder file mapping.
|
|
||||||
*/
|
|
||||||
get mapping() {
|
|
||||||
return [
|
|
||||||
{ from: 'Customer Type', to: 'customerType' },
|
|
||||||
{ from: 'First Name', to: 'firstName' },
|
|
||||||
{ from: 'Last Name', to: 'lastName' },
|
|
||||||
{ from: 'Display Name', to: 'displayName' },
|
|
||||||
{ from: 'Email', to: 'email' },
|
|
||||||
{ from: 'Work Phone Number', to: 'workPhone' },
|
|
||||||
{ from: 'Personal Phone Number', to: 'personalPhone' },
|
|
||||||
{ from: 'Company Name', to: 'companyName' },
|
|
||||||
{ from: 'Website', to: 'website' },
|
|
||||||
{ from: 'Active', to: 'active' },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the seeder file name.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get importFileName() {
|
|
||||||
return `customers.csv`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the resource name of the seeder.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get resource() {
|
|
||||||
return 'Customer';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
|
||||||
|
|
||||||
export class SeedDemoAccountExpenses extends SeedDemoAbstract {
|
|
||||||
/**
|
|
||||||
* Retrieves the seeder file mapping.
|
|
||||||
*/
|
|
||||||
get mapping() {
|
|
||||||
return [
|
|
||||||
{ from: 'Payment Account', to: 'paymentAccountId' },
|
|
||||||
{ from: 'Reference No.', to: 'referenceNo' },
|
|
||||||
{ from: 'Payment Date', to: 'paymentDate' },
|
|
||||||
{ from: 'Description', to: 'description' },
|
|
||||||
{ from: 'Publish', to: 'publish' },
|
|
||||||
{
|
|
||||||
from: 'Expense Account',
|
|
||||||
to: 'expenseAccountId',
|
|
||||||
group: 'categories',
|
|
||||||
},
|
|
||||||
{ from: 'Amount', to: 'amount', group: 'categories' },
|
|
||||||
{ from: 'Line Description', to: 'description', group: 'categories' },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the seeder file name.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get importFileName() {
|
|
||||||
return `Expenses.csv`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the resource name of the seeder.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get resource() {
|
|
||||||
return 'Expense';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
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: 'costPrice' },
|
|
||||||
{ from: 'Cost Account', to: 'costAccountId' },
|
|
||||||
{ from: 'Sell Account', to: 'sellAccountId' },
|
|
||||||
{ from: 'Inventory Account', to: 'inventoryAccountId' },
|
|
||||||
{ 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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
|
||||||
|
|
||||||
export class SeedDemoAccountManualJournals extends SeedDemoAbstract {
|
|
||||||
/**
|
|
||||||
* Retrieves the seeder file mapping.
|
|
||||||
*/
|
|
||||||
get mapping() {
|
|
||||||
return [
|
|
||||||
{ from: 'Date', to: 'date' },
|
|
||||||
{ from: 'Journal No', to: 'journalNumber' },
|
|
||||||
{ from: 'Reference No.', to: 'reference' },
|
|
||||||
{ from: 'Description', to: 'description' },
|
|
||||||
{ from: 'Publish', to: 'publish' },
|
|
||||||
{ from: 'Credit', to: 'credit', group: 'entries' },
|
|
||||||
{ from: 'Debit', to: 'debit', group: 'entries' },
|
|
||||||
{ from: 'Account', to: 'accountId', group: 'entries' },
|
|
||||||
{ from: 'Note', to: 'note', group: 'entries' },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the seeder file name.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get importFileName() {
|
|
||||||
return `manual-journals.csv`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the resource name of the seeder.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get resource() {
|
|
||||||
return 'ManualJournal';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
|
||||||
|
|
||||||
export class SeedDemoSaleInvoices extends SeedDemoAbstract {
|
|
||||||
get mapping() {
|
|
||||||
return [
|
|
||||||
{ from: 'Invoice Date', to: 'invoiceDate' },
|
|
||||||
{ from: 'Due Date', to: 'dueDate' },
|
|
||||||
{ from: 'Reference No.', to: 'referenceNo' },
|
|
||||||
{ from: 'Invoice No.', to: 'invoiceNo' },
|
|
||||||
{ from: 'Customer', to: 'customerId' },
|
|
||||||
{ from: 'Exchange Rate', to: 'exchangeRate' },
|
|
||||||
{ from: 'Invoice Message', to: 'invoiceMessage' },
|
|
||||||
{ from: 'Terms & Conditions', to: 'termsConditions' },
|
|
||||||
{ from: 'Delivered', to: 'delivered' },
|
|
||||||
{ from: 'Item', to: 'itemId', group: 'entries' },
|
|
||||||
{ from: 'Rate', to: 'rate', group: 'entries' },
|
|
||||||
{ from: 'Quantity', to: 'quantity', group: 'entries' },
|
|
||||||
{ from: 'Description', to: 'description', group: 'entries' },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the seeder file name.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get importFileName() {
|
|
||||||
return `sale-invoices.csv`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the resource name of the seeder.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get resource() {
|
|
||||||
return 'SaleInvoice';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { SeedDemoAbstract } from './SeedDemoAbstract';
|
|
||||||
|
|
||||||
export class SeedDemoAccountVendors extends SeedDemoAbstract {
|
|
||||||
/**
|
|
||||||
* Retrieves the seeder file mapping.
|
|
||||||
*/
|
|
||||||
get mapping() {
|
|
||||||
return [
|
|
||||||
{ from: 'First Name', to: 'firstName' },
|
|
||||||
{ from: 'Last Name', to: 'lastName' },
|
|
||||||
{ from: 'Display Name', to: 'displayName' },
|
|
||||||
{ from: 'Email', to: 'email' },
|
|
||||||
{ from: 'Work Phone Number', to: 'workPhone' },
|
|
||||||
{ from: 'Personal Phone Number', to: 'personalPhone' },
|
|
||||||
{ from: 'Company Name', to: 'companyName' },
|
|
||||||
{ from: 'Website', to: 'website' },
|
|
||||||
{ from: 'Active', to: 'active' },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the seeder file name.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get importFileName() {
|
|
||||||
return `vendors.csv`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the resource name of the seeder.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get resource() {
|
|
||||||
return 'Vendor';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import { CreateOneClickDemo } from './CreateOneClickDemo';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class OneClickDemoApplication {
|
|
||||||
@Inject()
|
|
||||||
private createOneClickDemoService: CreateOneClickDemo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates one-click demo account.
|
|
||||||
* @returns {Promise<ICreateOneClickDemoPOJO>}
|
|
||||||
*/
|
|
||||||
public createOneClick() {
|
|
||||||
return this.createOneClickDemoService.createOneClickDemo();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-sign-in to created demo account.
|
|
||||||
* @param {string} demoId -
|
|
||||||
* @returns {Promise<IAuthSignInPOJO>}
|
|
||||||
*/
|
|
||||||
public autoSignIn(demoId: string) {
|
|
||||||
return this.createOneClickDemoService.autoSignIn(demoId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
export const defaultDemoOrganizationDTO = {
|
|
||||||
name: 'BIGCAPITAL, INC',
|
|
||||||
baseCurrency: 'USD',
|
|
||||||
location: 'US',
|
|
||||||
language: 'en',
|
|
||||||
industry: 'Technology',
|
|
||||||
fiscalYear: 'march',
|
|
||||||
timezone: 'US/Central',
|
|
||||||
dateFormat: 'MM/DD/yyyy',
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
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 { IOrganizationBuiltEventPayload } 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';
|
|
||||||
import { SeedDemoAccountCustomers } from '../DemoSeeders/SeedDemoCustomers';
|
|
||||||
import { SeedDemoAccountVendors } from '../DemoSeeders/SeedDemoVendors';
|
|
||||||
import { SeedDemoAccountManualJournals } from '../DemoSeeders/SeedDemoManualJournals';
|
|
||||||
import { SeedDemoAccountExpenses } from '../DemoSeeders/SeedDemoExpenses';
|
|
||||||
import { SeedDemoBankTransactions } from '../DemoSeeders/SeedDemoBankTransactions';
|
|
||||||
import { SeedDemoSaleInvoices } from '../DemoSeeders/SeedDemoSaleInvoices';
|
|
||||||
|
|
||||||
export class SeedInitialDemoAccountDataOnOrgBuild {
|
|
||||||
@Inject()
|
|
||||||
private importApp: ImportResourceApplication;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches events with handlers.
|
|
||||||
*/
|
|
||||||
public attach = (bus) => {
|
|
||||||
bus.subscribe(
|
|
||||||
events.organization.built,
|
|
||||||
this.seedInitialDemoAccountDataOnOrgBuild.bind(this)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Demo account seeder.
|
|
||||||
*/
|
|
||||||
get seedDemoAccountSeeders() {
|
|
||||||
return [
|
|
||||||
SeedDemoAccountItems,
|
|
||||||
SeedDemoBankTransactions,
|
|
||||||
SeedDemoAccountCustomers,
|
|
||||||
SeedDemoAccountVendors,
|
|
||||||
SeedDemoAccountManualJournals,
|
|
||||||
SeedDemoSaleInvoices,
|
|
||||||
SeedDemoAccountExpenses,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the seeder sheet file to the import storage first.
|
|
||||||
* @param {string} fileName -
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async initiateSeederFile(fileName: string) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Seeds initial demo account data on organization build
|
|
||||||
* @param {IOrganizationBuildEventPayload}
|
|
||||||
*/
|
|
||||||
async seedInitialDemoAccountDataOnOrgBuild({
|
|
||||||
tenantId,
|
|
||||||
}: IOrganizationBuiltEventPayload) {
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Initialize the seeder sheet file before importing.
|
|
||||||
const importFileName = await this.initiateSeederFile(seederInstance.importFileName);
|
|
||||||
|
|
||||||
// Import the given seeder file.
|
|
||||||
const importedFile = await this.importApp.import(
|
|
||||||
tenantId,
|
|
||||||
seederInstance.resource,
|
|
||||||
importFileName,
|
|
||||||
seederInstance.importParams
|
|
||||||
);
|
|
||||||
// Mapping the columns with resource fields.
|
|
||||||
await this.importApp.mapping(
|
|
||||||
tenantId,
|
|
||||||
importedFile.import.importId,
|
|
||||||
seederInstance.mapping
|
|
||||||
);
|
|
||||||
await this.importApp.preview(tenantId, importedFile.import.importId);
|
|
||||||
|
|
||||||
// Commit the imported file.
|
|
||||||
await this.importApp.process(
|
|
||||||
tenantId,
|
|
||||||
importedFile.import.importId
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isEmpty(results.errors)) {
|
|
||||||
throw results.errors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { IAuthSignInPOJO } from '@/interfaces';
|
|
||||||
|
|
||||||
export interface ICreateOneClickDemoPOJO {
|
|
||||||
email: string;
|
|
||||||
demoId: string;
|
|
||||||
signedIn: IAuthSignInPOJO;
|
|
||||||
buildJob: any;
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ import { ServiceError } from '@/exceptions';
|
|||||||
import {
|
import {
|
||||||
IOrganizationBuildDTO,
|
IOrganizationBuildDTO,
|
||||||
IOrganizationBuildEventPayload,
|
IOrganizationBuildEventPayload,
|
||||||
IOrganizationBuiltEventPayload,
|
|
||||||
IOrganizationUpdateDTO,
|
IOrganizationUpdateDTO,
|
||||||
ISystemUser,
|
ISystemUser,
|
||||||
ITenant,
|
ITenant,
|
||||||
@@ -18,8 +17,6 @@ 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 {
|
||||||
@@ -65,10 +62,6 @@ 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);
|
||||||
@@ -89,11 +82,6 @@ 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { Exportable } from '@/services/Export/Exportable';
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
import { BillPaymentsApplication } from './BillPaymentsApplication';
|
import { BillPaymentsApplication } from './BillPaymentsApplication';
|
||||||
import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class BillPaymentExportable extends Exportable {
|
export class BillPaymentExportable extends Exportable {
|
||||||
@@ -15,11 +14,11 @@ export class BillPaymentExportable extends Exportable {
|
|||||||
*/
|
*/
|
||||||
public exportable(tenantId: number, query: any) {
|
public exportable(tenantId: number, query: any) {
|
||||||
const parsedQuery = {
|
const parsedQuery = {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 12,
|
||||||
|
...query,
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...query,
|
|
||||||
page: 1,
|
|
||||||
pageSize: EXPORT_SIZE_LIMIT,
|
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
return this.billPaymentsApplication
|
return this.billPaymentsApplication
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { IBillsFilter } from '@/interfaces';
|
import { IBillsFilter } from '@/interfaces';
|
||||||
import { Exportable } from '@/services/Export/Exportable';
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
import { BillsApplication } from './BillsApplication';
|
import { BillsApplication } from './BillsApplication';
|
||||||
import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class BillsExportable extends Exportable {
|
export class BillsExportable extends Exportable {
|
||||||
@@ -20,7 +19,7 @@ export class BillsExportable extends Exportable {
|
|||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...query,
|
...query,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: EXPORT_SIZE_LIMIT,
|
pageSize: 12000,
|
||||||
} as IBillsFilter;
|
} as IBillsFilter;
|
||||||
|
|
||||||
return this.billsApplication
|
return this.billsApplication
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { ISalesInvoicesFilter } from '@/interfaces';
|
import { ISalesInvoicesFilter } from '@/interfaces';
|
||||||
import { Exportable } from '@/services/Export/Exportable';
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
import { SaleEstimatesApplication } from './SaleEstimatesApplication';
|
import { SaleEstimatesApplication } from './SaleEstimatesApplication';
|
||||||
import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleEstimatesExportable extends Exportable {
|
export class SaleEstimatesExportable extends Exportable {
|
||||||
@@ -20,7 +19,7 @@ export class SaleEstimatesExportable extends Exportable {
|
|||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...query,
|
...query,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: EXPORT_SIZE_LIMIT,
|
pageSize: 12000,
|
||||||
} as ISalesInvoicesFilter;
|
} as ISalesInvoicesFilter;
|
||||||
|
|
||||||
return this.saleEstimatesApplication
|
return this.saleEstimatesApplication
|
||||||
|
|||||||
@@ -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, {}, trx
|
saleInvoice.currencyCode
|
||||||
);
|
);
|
||||||
// Find or create tax payable account.
|
// Find or create tax payable account.
|
||||||
const taxPayableAccount = await accountRepository.findOrCreateTaxPayable(
|
const taxPayableAccount = await accountRepository.findOrCreateTaxPayable(
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ 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,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { ISalesInvoicesFilter } from '@/interfaces';
|
import { ISalesInvoicesFilter } from '@/interfaces';
|
||||||
import { SaleInvoiceApplication } from './SaleInvoicesApplication';
|
import { SaleInvoiceApplication } from './SaleInvoicesApplication';
|
||||||
import { Exportable } from '@/services/Export/Exportable';
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleInvoicesExportable extends Exportable {
|
export class SaleInvoicesExportable extends Exportable {
|
||||||
@@ -20,7 +19,7 @@ export class SaleInvoicesExportable extends Exportable {
|
|||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...query,
|
...query,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: EXPORT_SIZE_LIMIT,
|
pageSize: 120000,
|
||||||
} as ISalesInvoicesFilter;
|
} as ISalesInvoicesFilter;
|
||||||
|
|
||||||
return this.saleInvoicesApplication
|
return this.saleInvoicesApplication
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Mutex } from 'async-mutex';
|
|
||||||
import { Container, Service, Inject } from 'typedi';
|
import { Container, Service, Inject } from 'typedi';
|
||||||
import { chain } from 'lodash';
|
import { chain } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@@ -35,26 +34,17 @@ export class SaleInvoicesCost {
|
|||||||
inventoryItemsIds: number[],
|
inventoryItemsIds: number[],
|
||||||
startingDate: Date
|
startingDate: Date
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const mutex = new Mutex();
|
const asyncOpers: Promise<[]>[] = [];
|
||||||
|
|
||||||
const asyncOpers = inventoryItemsIds.map(
|
inventoryItemsIds.forEach((inventoryItemId: number) => {
|
||||||
async (inventoryItemId: number) => {
|
const oper: Promise<[]> = this.inventoryService.scheduleComputeItemCost(
|
||||||
// @todo refactor the lock acquire to be distrbuted using Redis
|
tenantId,
|
||||||
// and run the cost schedule job after running invoice transaction.
|
inventoryItemId,
|
||||||
const release = await mutex.acquire();
|
startingDate
|
||||||
|
);
|
||||||
try {
|
asyncOpers.push(oper);
|
||||||
await this.inventoryService.scheduleComputeItemCost(
|
});
|
||||||
tenantId,
|
await Promise.all([...asyncOpers]);
|
||||||
inventoryItemId,
|
|
||||||
startingDate
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await Promise.all(asyncOpers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,22 +86,17 @@ export class SaleInvoicesCost {
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
inventoryTransactions: IInventoryTransaction[]
|
inventoryTransactions: IInventoryTransaction[]
|
||||||
) {
|
) {
|
||||||
const mutex = new Mutex();
|
const asyncOpers: Promise<[]>[] = [];
|
||||||
const reducedTransactions = this.getMaxDateInventoryTransactions(
|
const reducedTransactions = this.getMaxDateInventoryTransactions(
|
||||||
inventoryTransactions
|
inventoryTransactions
|
||||||
);
|
);
|
||||||
const asyncOpers = reducedTransactions.map(async (transaction) => {
|
reducedTransactions.forEach((transaction) => {
|
||||||
const release = await mutex.acquire();
|
const oper: Promise<[]> = this.inventoryService.scheduleComputeItemCost(
|
||||||
|
tenantId,
|
||||||
try {
|
transaction.itemId,
|
||||||
await this.inventoryService.scheduleComputeItemCost(
|
transaction.date
|
||||||
tenantId,
|
);
|
||||||
transaction.itemId,
|
asyncOpers.push(oper);
|
||||||
transaction.date
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
release();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
await Promise.all([...asyncOpers]);
|
await Promise.all([...asyncOpers]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { IAccountsStructureType, IPaymentsReceivedFilter } from '@/interfaces';
|
import { IAccountsStructureType, IPaymentsReceivedFilter } from '@/interfaces';
|
||||||
import { Exportable } from '@/services/Export/Exportable';
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
import { PaymentReceivesApplication } from './PaymentReceivedApplication';
|
import { PaymentReceivesApplication } from './PaymentReceivedApplication';
|
||||||
import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PaymentsReceivedExportable extends Exportable {
|
export class PaymentsReceivedExportable extends Exportable {
|
||||||
@@ -22,8 +21,6 @@ export class PaymentsReceivedExportable extends Exportable {
|
|||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
...query,
|
...query,
|
||||||
structure: IAccountsStructureType.Flat,
|
structure: IAccountsStructureType.Flat,
|
||||||
page: 1,
|
|
||||||
pageSize: EXPORT_SIZE_LIMIT,
|
|
||||||
} as IPaymentsReceivedFilter;
|
} as IPaymentsReceivedFilter;
|
||||||
|
|
||||||
return this.paymentReceivedApp
|
return this.paymentReceivedApp
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { ISalesReceiptsFilter } from '@/interfaces';
|
import { ISalesReceiptsFilter } from '@/interfaces';
|
||||||
import { Exportable } from '@/services/Export/Exportable';
|
import { Exportable } from '@/services/Export/Exportable';
|
||||||
import { SaleReceiptApplication } from './SaleReceiptApplication';
|
import { SaleReceiptApplication } from './SaleReceiptApplication';
|
||||||
import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleReceiptsExportable extends Exportable {
|
export class SaleReceiptsExportable extends Exportable {
|
||||||
@@ -20,7 +19,7 @@ export class SaleReceiptsExportable extends Exportable {
|
|||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...query,
|
...query,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: EXPORT_SIZE_LIMIT,
|
pageSize: 12,
|
||||||
} as ISalesReceiptsFilter;
|
} as ISalesReceiptsFilter;
|
||||||
|
|
||||||
return this.saleReceiptsApp
|
return this.saleReceiptsApp
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { configureLemonSqueezy } from './utils';
|
|||||||
import { PlanSubscription } from '@/system/models';
|
import { PlanSubscription } from '@/system/models';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
import { ERRORS, IOrganizationSubscriptionCancel } from './types';
|
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
|
import { ERRORS, IOrganizationSubscriptionCanceled } from './types';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class LemonCancelSubscription {
|
export class LemonCancelSubscription {
|
||||||
@@ -18,15 +18,12 @@ export class LemonCancelSubscription {
|
|||||||
* @param {number} subscriptionId
|
* @param {number} subscriptionId
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async cancelSubscription(
|
public async cancelSubscription(tenantId: number) {
|
||||||
tenantId: number,
|
|
||||||
subscriptionSlug: string = 'main'
|
|
||||||
) {
|
|
||||||
configureLemonSqueezy();
|
configureLemonSqueezy();
|
||||||
|
|
||||||
const subscription = await PlanSubscription.query().findOne({
|
const subscription = await PlanSubscription.query().findOne({
|
||||||
tenantId,
|
tenantId,
|
||||||
slug: subscriptionSlug,
|
slug: 'main',
|
||||||
});
|
});
|
||||||
if (!subscription) {
|
if (!subscription) {
|
||||||
throw new ServiceError(ERRORS.SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT);
|
throw new ServiceError(ERRORS.SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT);
|
||||||
@@ -38,10 +35,13 @@ export class LemonCancelSubscription {
|
|||||||
if (cancelledSub.error) {
|
if (cancelledSub.error) {
|
||||||
throw new Error(cancelledSub.error.message);
|
throw new Error(cancelledSub.error.message);
|
||||||
}
|
}
|
||||||
// Triggers `onSubscriptionCancelled` event.
|
await PlanSubscription.query().findById(subscriptionId).patch({
|
||||||
|
canceledAt: new Date(),
|
||||||
|
});
|
||||||
|
// Triggers `onSubscriptionCanceled` event.
|
||||||
await this.eventPublisher.emitAsync(
|
await this.eventPublisher.emitAsync(
|
||||||
events.subscription.onSubscriptionCancel,
|
events.subscription.onSubscriptionCanceled,
|
||||||
{ tenantId, subscriptionId } as IOrganizationSubscriptionCancel
|
{ tenantId, subscriptionId } as IOrganizationSubscriptionCanceled
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user