mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
Merge branch 'master' of https://github.com/abouolia/Bigcapital
This commit is contained in:
@@ -45,28 +45,19 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC
|
|||||||
const { tenantId, settings } = req;
|
const { tenantId, settings } = req;
|
||||||
const filter = this.matchedQueryData(req);
|
const filter = this.matchedQueryData(req);
|
||||||
|
|
||||||
const organizationName = settings.get({
|
|
||||||
group: 'organization',
|
|
||||||
key: 'name',
|
|
||||||
});
|
|
||||||
const baseCurrency = settings.get({
|
|
||||||
group: 'organization',
|
|
||||||
key: 'base_currency',
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
query,
|
query,
|
||||||
|
meta
|
||||||
} = await this.APAgingSummaryService.APAgingSummary(tenantId, filter);
|
} = await this.APAgingSummaryService.APAgingSummary(tenantId, filter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organization_name: organizationName,
|
|
||||||
base_currency: baseCurrency,
|
|
||||||
data: this.transfromToResponse(data),
|
data: this.transfromToResponse(data),
|
||||||
columns: this.transfromToResponse(columns),
|
columns: this.transfromToResponse(columns),
|
||||||
query: this.transfromToResponse(query),
|
query: this.transfromToResponse(query),
|
||||||
|
meta: this.transfromToResponse(meta)
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receivable aging summary validation roles.
|
* AR aging summary validation roles.
|
||||||
*/
|
*/
|
||||||
get validationSchema() {
|
get validationSchema() {
|
||||||
return [
|
return [
|
||||||
@@ -41,34 +41,25 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve receivable aging summary report.
|
* Retrieve AR aging summary report.
|
||||||
*/
|
*/
|
||||||
async receivableAgingSummary(req: Request, res: Response) {
|
async receivableAgingSummary(req: Request, res: Response) {
|
||||||
const { tenantId, settings } = req;
|
const { tenantId, settings } = req;
|
||||||
const filter = this.matchedQueryData(req);
|
const filter = this.matchedQueryData(req);
|
||||||
|
|
||||||
const organizationName = settings.get({
|
|
||||||
group: 'organization',
|
|
||||||
key: 'name',
|
|
||||||
});
|
|
||||||
const baseCurrency = settings.get({
|
|
||||||
group: 'organization',
|
|
||||||
key: 'base_currency',
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
query,
|
query,
|
||||||
|
meta,
|
||||||
} = await this.ARAgingSummaryService.ARAgingSummary(tenantId, filter);
|
} = await this.ARAgingSummaryService.ARAgingSummary(tenantId, filter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organization_name: organizationName,
|
|
||||||
base_currency: baseCurrency,
|
|
||||||
data: this.transfromToResponse(data),
|
data: this.transfromToResponse(data),
|
||||||
columns: this.transfromToResponse(columns),
|
columns: this.transfromToResponse(columns),
|
||||||
query: this.transfromToResponse(query),
|
query: this.transfromToResponse(query),
|
||||||
|
meta: this.transfromToResponse(meta),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -59,28 +59,20 @@ export default class BalanceSheetStatementController extends BaseFinancialReport
|
|||||||
...filter,
|
...filter,
|
||||||
accountsIds: castArray(filter.accountsIds),
|
accountsIds: castArray(filter.accountsIds),
|
||||||
};
|
};
|
||||||
const organizationName = settings.get({
|
|
||||||
group: 'organization',
|
|
||||||
key: 'name',
|
|
||||||
});
|
|
||||||
const baseCurrency = settings.get({
|
|
||||||
group: 'organization',
|
|
||||||
key: 'base_currency',
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
query,
|
query,
|
||||||
|
meta,
|
||||||
} = await this.balanceSheetService.balanceSheet(tenantId, filter);
|
} = await this.balanceSheetService.balanceSheet(tenantId, filter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organization_name: organizationName,
|
|
||||||
base_currency: baseCurrency,
|
|
||||||
data: this.transfromToResponse(data),
|
data: this.transfromToResponse(data),
|
||||||
columns: this.transfromToResponse(columns),
|
columns: this.transfromToResponse(columns),
|
||||||
query: this.transfromToResponse(query),
|
query: this.transfromToResponse(query),
|
||||||
|
meta: this.transfromToResponse(meta),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -52,23 +52,13 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
|
|||||||
const { tenantId, settings } = req;
|
const { tenantId, settings } = req;
|
||||||
const filter = this.matchedQueryData(req);
|
const filter = this.matchedQueryData(req);
|
||||||
|
|
||||||
const organizationName = settings.get({
|
|
||||||
group: 'organization',
|
|
||||||
key: 'name',
|
|
||||||
});
|
|
||||||
const baseCurrency = settings.get({
|
|
||||||
group: 'organization',
|
|
||||||
key: 'base_currency',
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, query } = await this.generalLedgetService.generalLedger(
|
const { data, query, meta } = await this.generalLedgetService.generalLedger(
|
||||||
tenantId,
|
tenantId,
|
||||||
filter
|
filter
|
||||||
);
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organization_name: organizationName,
|
meta: this.transfromToResponse(meta),
|
||||||
base_currency: baseCurrency,
|
|
||||||
data: this.transfromToResponse(data),
|
data: this.transfromToResponse(data),
|
||||||
query: this.transfromToResponse(query),
|
query: this.transfromToResponse(query),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -66,26 +66,17 @@ export default class JournalSheetController extends BaseFinancialReportControlle
|
|||||||
...filter,
|
...filter,
|
||||||
accountsIds: castArray(filter.accountsIds),
|
accountsIds: castArray(filter.accountsIds),
|
||||||
};
|
};
|
||||||
const organizationName = settings.get({
|
|
||||||
group: 'organization',
|
|
||||||
key: 'name',
|
|
||||||
});
|
|
||||||
const baseCurrency = settings.get({
|
|
||||||
group: 'organization',
|
|
||||||
key: 'base_currency',
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, query } = await this.journalService.journalSheet(
|
const { data, query, meta } = await this.journalService.journalSheet(
|
||||||
tenantId,
|
tenantId,
|
||||||
filter
|
filter
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organization_name: organizationName,
|
|
||||||
base_currency: baseCurrency,
|
|
||||||
data: this.transfromToResponse(data),
|
data: this.transfromToResponse(data),
|
||||||
query: this.transfromToResponse(query),
|
query: this.transfromToResponse(query),
|
||||||
|
meta: this.transfromToResponse(meta),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -54,22 +54,19 @@ export default class ProfitLossSheetController extends BaseFinancialReportContro
|
|||||||
const { tenantId, settings } = req;
|
const { tenantId, settings } = req;
|
||||||
const filter = this.matchedQueryData(req);
|
const filter = this.matchedQueryData(req);
|
||||||
|
|
||||||
const organizationName = settings.get({ group: 'organization', key: 'name' });
|
|
||||||
const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
query,
|
query,
|
||||||
|
meta
|
||||||
} = await this.profitLossSheetService.profitLossSheet(tenantId, filter);
|
} = await this.profitLossSheetService.profitLossSheet(tenantId, filter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organization_name: organizationName,
|
|
||||||
base_currency: baseCurrency,
|
|
||||||
data: this.transfromToResponse(data),
|
data: this.transfromToResponse(data),
|
||||||
columns: this.transfromToResponse(columns),
|
columns: this.transfromToResponse(columns),
|
||||||
query: this.transfromToResponse(query),
|
query: this.transfromToResponse(query),
|
||||||
|
meta: this.transfromToResponse(meta)
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -58,29 +58,21 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont
|
|||||||
...filter,
|
...filter,
|
||||||
accountsIds: castArray(filter.accountsIds),
|
accountsIds: castArray(filter.accountsIds),
|
||||||
};
|
};
|
||||||
const organizationName = settings.get({
|
|
||||||
group: 'organization',
|
|
||||||
key: 'name',
|
|
||||||
});
|
|
||||||
const baseCurrency = settings.get({
|
|
||||||
group: 'organization',
|
|
||||||
key: 'base_currency',
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
query,
|
query,
|
||||||
|
meta
|
||||||
} = await this.trialBalanceSheetService.trialBalanceSheet(
|
} = await this.trialBalanceSheetService.trialBalanceSheet(
|
||||||
tenantId,
|
tenantId,
|
||||||
filter
|
filter
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organization_name: organizationName,
|
|
||||||
base_currency: baseCurrency,
|
|
||||||
data: this.transfromToResponse(data),
|
data: this.transfromToResponse(data),
|
||||||
query: this.transfromToResponse(query),
|
query: this.transfromToResponse(query),
|
||||||
|
meta: this.transfromToResponse(meta),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { Router, Request, Response } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { check, oneOf, ValidationChain } from 'express-validator';
|
import { check, oneOf, ValidationChain } from 'express-validator';
|
||||||
import basicAuth from 'express-basic-auth';
|
import basicAuth from 'express-basic-auth';
|
||||||
import config from 'config';
|
import config from 'config';
|
||||||
import { License, Plan } from 'system/models';
|
import { License } from 'system/models';
|
||||||
|
import { ServiceError } from 'exceptions';
|
||||||
import BaseController from 'api/controllers/BaseController';
|
import BaseController from 'api/controllers/BaseController';
|
||||||
import LicenseService from 'services/Payment/License';
|
import LicenseService from 'services/Payment/License';
|
||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
import { ILicensesFilter } from 'interfaces';
|
import { ILicensesFilter, ISendLicenseDTO } from 'interfaces';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class LicensesController extends BaseController {
|
export default class LicensesController extends BaseController {
|
||||||
@@ -32,26 +33,26 @@ export default class LicensesController extends BaseController {
|
|||||||
'/generate',
|
'/generate',
|
||||||
this.generateLicenseSchema,
|
this.generateLicenseSchema,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.validatePlanExistance.bind(this)),
|
asyncMiddleware(this.generateLicense.bind(this)),
|
||||||
asyncMiddleware(this.generateLicense.bind(this))
|
this.catchServiceErrors,
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/disable/:licenseId',
|
'/disable/:licenseId',
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.validateLicenseExistance.bind(this)),
|
asyncMiddleware(this.disableLicense.bind(this)),
|
||||||
asyncMiddleware(this.validateNotDisabledLicense.bind(this)),
|
this.catchServiceErrors,
|
||||||
asyncMiddleware(this.disableLicense.bind(this))
|
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/send',
|
'/send',
|
||||||
this.sendLicenseSchemaValidation,
|
this.sendLicenseSchemaValidation,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.sendLicense.bind(this))
|
asyncMiddleware(this.sendLicense.bind(this)),
|
||||||
|
this.catchServiceErrors,
|
||||||
);
|
);
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:licenseId',
|
'/:licenseId',
|
||||||
asyncMiddleware(this.validateLicenseExistance.bind(this)),
|
asyncMiddleware(this.deleteLicense.bind(this)),
|
||||||
asyncMiddleware(this.deleteLicense.bind(this))
|
this.catchServiceErrors,
|
||||||
);
|
);
|
||||||
router.get('/', asyncMiddleware(this.listLicenses.bind(this)));
|
router.get('/', asyncMiddleware(this.listLicenses.bind(this)));
|
||||||
return router;
|
return router;
|
||||||
@@ -67,7 +68,7 @@ export default class LicensesController extends BaseController {
|
|||||||
check('period_interval')
|
check('period_interval')
|
||||||
.exists()
|
.exists()
|
||||||
.isIn(['month', 'months', 'year', 'years', 'day', 'days']),
|
.isIn(['month', 'months', 'year', 'years', 'day', 'days']),
|
||||||
check('plan_id').exists().isNumeric().toInt(),
|
check('plan_slug').exists().trim().escape(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ export default class LicensesController extends BaseController {
|
|||||||
return [
|
return [
|
||||||
check('period').exists().isNumeric(),
|
check('period').exists().isNumeric(),
|
||||||
check('period_interval').exists().trim().escape(),
|
check('period_interval').exists().trim().escape(),
|
||||||
check('plan_id').exists().isNumeric().toInt(),
|
check('plan_slug').exists().trim().escape(),
|
||||||
oneOf([
|
oneOf([
|
||||||
check('phone_number').exists().trim().escape(),
|
check('phone_number').exists().trim().escape(),
|
||||||
check('email').exists().trim().escape(),
|
check('email').exists().trim().escape(),
|
||||||
@@ -98,67 +99,6 @@ export default class LicensesController extends BaseController {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the plan existance on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validatePlanExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const body = this.matchedBodyData(req);
|
|
||||||
const planId: number = body.planId || req.params.planId;
|
|
||||||
const foundPlan = await Plan.query().findById(planId);
|
|
||||||
|
|
||||||
if (!foundPlan) {
|
|
||||||
return res.status(400).send({
|
|
||||||
erorrs: [{ type: 'PLAN.NOT.FOUND', code: 100 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Valdiate the license existance on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function}
|
|
||||||
*/
|
|
||||||
async validateLicenseExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const body = this.matchedBodyData(req);
|
|
||||||
|
|
||||||
const licenseId = body.licenseId || req.params.licenseId;
|
|
||||||
const foundLicense = await License.query().findById(licenseId);
|
|
||||||
|
|
||||||
if (!foundLicense) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'LICENSE.NOT.FOUND', code: 200 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates whether the license id is disabled.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateNotDisabledLicense(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: Function
|
|
||||||
) {
|
|
||||||
const licenseId = req.params.licenseId || req.query.licenseId;
|
|
||||||
const foundLicense = await License.query().findById(licenseId);
|
|
||||||
|
|
||||||
if (foundLicense.disabled) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'LICENSE.ALREADY.DISABLED', code: 200 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate licenses codes with given period in bulk.
|
* Generate licenses codes with given period in bulk.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
@@ -166,7 +106,7 @@ export default class LicensesController extends BaseController {
|
|||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async generateLicense(req: Request, res: Response, next: Function) {
|
async generateLicense(req: Request, res: Response, next: Function) {
|
||||||
const { loop = 10, period, periodInterval, planId } = this.matchedBodyData(
|
const { loop = 10, period, periodInterval, planSlug } = this.matchedBodyData(
|
||||||
req
|
req
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -175,7 +115,7 @@ export default class LicensesController extends BaseController {
|
|||||||
loop,
|
loop,
|
||||||
period,
|
period,
|
||||||
periodInterval,
|
periodInterval,
|
||||||
planId
|
planSlug
|
||||||
);
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
code: 100,
|
code: 100,
|
||||||
@@ -193,12 +133,16 @@ export default class LicensesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async disableLicense(req: Request, res: Response) {
|
async disableLicense(req: Request, res: Response, next: Function) {
|
||||||
const { licenseId } = req.params;
|
const { licenseId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
await this.licenseService.disableLicense(licenseId);
|
await this.licenseService.disableLicense(licenseId);
|
||||||
|
|
||||||
return res.status(200).send({ license_id: licenseId });
|
return res.status(200).send({ license_id: licenseId });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -207,12 +151,16 @@ export default class LicensesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async deleteLicense(req: Request, res: Response) {
|
async deleteLicense(req: Request, res: Response, next: Function) {
|
||||||
const { licenseId } = req.params;
|
const { licenseId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
await this.licenseService.deleteLicense(licenseId);
|
await this.licenseService.deleteLicense(licenseId);
|
||||||
|
|
||||||
return res.status(200).send({ license_id: licenseId });
|
return res.status(200).send({ license_id: licenseId });
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -221,40 +169,20 @@ export default class LicensesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async sendLicense(req: Request, res: Response) {
|
async sendLicense(req: Request, res: Response, next: Function) {
|
||||||
const {
|
const sendLicenseDTO: ISendLicenseDTO = this.matchedBodyData(req);
|
||||||
phoneNumber,
|
|
||||||
email,
|
|
||||||
period,
|
|
||||||
periodInterval,
|
|
||||||
planId,
|
|
||||||
} = this.matchedBodyData(req);
|
|
||||||
|
|
||||||
const license = await License.query()
|
try {
|
||||||
.modify('filterActiveLicense')
|
await this.licenseService.sendLicenseToCustomer(sendLicenseDTO);
|
||||||
.where('license_period', period)
|
|
||||||
.where('period_interval', periodInterval)
|
|
||||||
.where('plan_id', planId)
|
|
||||||
.first();
|
|
||||||
|
|
||||||
if (!license) {
|
|
||||||
return res.status(400).send({
|
|
||||||
status: 110,
|
|
||||||
message:
|
|
||||||
'There is no licenses availiable right now with the given period and plan.',
|
|
||||||
code: 'NO.AVALIABLE.LICENSE.CODE',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await this.licenseService.sendLicenseToCustomer(
|
|
||||||
license.licenseCode,
|
|
||||||
phoneNumber,
|
|
||||||
email
|
|
||||||
);
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
status: 100,
|
status: 100,
|
||||||
code: 'LICENSE.CODE.SENT',
|
code: 'LICENSE.CODE.SENT',
|
||||||
message: 'The license has been sent to the given customer.',
|
message: 'The license has been sent to the given customer.',
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -276,4 +204,47 @@ export default class LicensesController extends BaseController {
|
|||||||
});
|
});
|
||||||
return res.status(200).send({ licenses });
|
return res.status(200).send({ licenses });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catches all service errors.
|
||||||
|
*/
|
||||||
|
catchServiceErrors(error, req: Request, res: Response, next: NextFunction) {
|
||||||
|
if (error instanceof ServiceError) {
|
||||||
|
if (error.errorType === 'PLAN_NOT_FOUND') {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{
|
||||||
|
type: 'PLAN.NOT.FOUND',
|
||||||
|
code: 100,
|
||||||
|
message: 'The given plan not found.',
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'LICENSE_NOT_FOUND') {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{
|
||||||
|
type: 'LICENSE_NOT_FOUND',
|
||||||
|
code: 200,
|
||||||
|
message: 'The given license id not found.'
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'LICENSE_ALREADY_DISABLED') {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{
|
||||||
|
type: 'LICENSE.ALREADY.DISABLED',
|
||||||
|
code: 200,
|
||||||
|
message: 'License is already disabled.'
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'NO_AVALIABLE_LICENSE_CODE') {
|
||||||
|
return res.status(400).send({
|
||||||
|
status: 110,
|
||||||
|
message: 'There is no licenses availiable right now with the given period and plan.',
|
||||||
|
code: 'NO.AVALIABLE.LICENSE.CODE',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Inject } from 'typedi';
|
import { Inject } from 'typedi';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
import { Plan } from 'system/models';
|
import { Plan } from 'system/models';
|
||||||
import BaseController from 'api/controllers/BaseController';
|
import BaseController from 'api/controllers/BaseController';
|
||||||
import SubscriptionService from 'services/Subscription/SubscriptionService';
|
import SubscriptionService from 'services/Subscription/SubscriptionService';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Router, Request, Response, NextFunction } from 'express'
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { Container, Service, Inject } from 'typedi';
|
import { Container, Service, Inject } from 'typedi';
|
||||||
import JWTAuth from 'api/middleware/jwtAuth';
|
import JWTAuth from 'api/middleware/jwtAuth';
|
||||||
import TenancyMiddleware from 'api/middleware/TenancyMiddleware';
|
import TenancyMiddleware from 'api/middleware/TenancyMiddleware';
|
||||||
@@ -22,13 +22,9 @@ export default class SubscriptionController {
|
|||||||
router.use(AttachCurrentTenantUser);
|
router.use(AttachCurrentTenantUser);
|
||||||
router.use(TenancyMiddleware);
|
router.use(TenancyMiddleware);
|
||||||
|
|
||||||
router.use(
|
router.use('/license', Container.get(PaymentViaLicenseController).router());
|
||||||
'/license',
|
router.get('/', asyncMiddleware(this.getSubscriptions.bind(this)));
|
||||||
Container.get(PaymentViaLicenseController).router()
|
|
||||||
);
|
|
||||||
router.get('/',
|
|
||||||
asyncMiddleware(this.getSubscriptions.bind(this))
|
|
||||||
);
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +38,9 @@ export default class SubscriptionController {
|
|||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const subscriptions = await this.subscriptionService.getSubscriptions(tenantId);
|
const subscriptions = await this.subscriptionService.getSubscriptions(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
return res.status(200).send({ subscriptions });
|
return res.status(200).send({ subscriptions });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
6
server/src/exceptions/VoucherCodeRequired.ts
Normal file
6
server/src/exceptions/VoucherCodeRequired.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
export default class VoucherCodeRequired {
|
||||||
|
constructor() {
|
||||||
|
this.name = 'VoucherCodeRequired';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import TenantAlreadyInitialized from './TenantAlreadyInitialized';
|
|||||||
import TenantAlreadySeeded from './TenantAlreadySeeded';
|
import TenantAlreadySeeded from './TenantAlreadySeeded';
|
||||||
import TenantDBAlreadyExists from './TenantDBAlreadyExists';
|
import TenantDBAlreadyExists from './TenantDBAlreadyExists';
|
||||||
import TenantDatabaseNotBuilt from './TenantDatabaseNotBuilt';
|
import TenantDatabaseNotBuilt from './TenantDatabaseNotBuilt';
|
||||||
|
import VoucherCodeRequired from './VoucherCodeRequired';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
NotAllowedChangeSubscriptionPlan,
|
NotAllowedChangeSubscriptionPlan,
|
||||||
@@ -20,4 +21,5 @@ export {
|
|||||||
TenantAlreadySeeded,
|
TenantAlreadySeeded,
|
||||||
TenantDBAlreadyExists,
|
TenantDBAlreadyExists,
|
||||||
TenantDatabaseNotBuilt,
|
TenantDatabaseNotBuilt,
|
||||||
|
VoucherCodeRequired,
|
||||||
};
|
};
|
||||||
@@ -35,3 +35,15 @@ export interface IAPAgingSummaryData {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type IAPAgingSummaryColumns = IAgingPeriod[];
|
export type IAPAgingSummaryColumns = IAgingPeriod[];
|
||||||
|
|
||||||
|
|
||||||
|
export interface IARAgingSummaryMeta {
|
||||||
|
baseCurrency: string,
|
||||||
|
organizationName: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IAPAgingSummaryMeta {
|
||||||
|
baseCurrency: string,
|
||||||
|
organizationName: string,
|
||||||
|
}
|
||||||
@@ -29,3 +29,8 @@ export interface IARAgingSummaryData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type IARAgingSummaryColumns = IAgingPeriod[];
|
export type IARAgingSummaryColumns = IAgingPeriod[];
|
||||||
|
|
||||||
|
export interface IARAgingSummaryMeta {
|
||||||
|
organizationName: string,
|
||||||
|
baseCurrency: string,
|
||||||
|
}
|
||||||
@@ -15,10 +15,16 @@ export interface IBalanceSheetQuery {
|
|||||||
accountIds: number[];
|
accountIds: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IBalanceSheetMeta {
|
||||||
|
isCostComputeRunning: boolean,
|
||||||
|
organizationName: string,
|
||||||
|
baseCurrency: string,
|
||||||
|
};
|
||||||
|
|
||||||
export interface IBalanceSheetFormatNumberSettings
|
export interface IBalanceSheetFormatNumberSettings
|
||||||
extends IFormatNumberSettings {
|
extends IFormatNumberSettings {
|
||||||
type: string;
|
type: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface IBalanceSheetStatementService {
|
export interface IBalanceSheetStatementService {
|
||||||
balanceSheet(
|
balanceSheet(
|
||||||
@@ -35,6 +41,7 @@ export interface IBalanceSheetStatement {
|
|||||||
query: IBalanceSheetQuery;
|
query: IBalanceSheetQuery;
|
||||||
columns: IBalanceSheetStatementColumns;
|
columns: IBalanceSheetStatementColumns;
|
||||||
data: IBalanceSheetStatementData;
|
data: IBalanceSheetStatementData;
|
||||||
|
meta: IBalanceSheetMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBalanceSheetStructureSection {
|
export interface IBalanceSheetStructureSection {
|
||||||
|
|||||||
@@ -72,3 +72,9 @@ export interface IAccountTransaction {
|
|||||||
createdAt: string|Date,
|
createdAt: string|Date,
|
||||||
updatedAt: string|Date,
|
updatedAt: string|Date,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IGeneralLedgerMeta {
|
||||||
|
isCostComputeRunning: boolean,
|
||||||
|
organizationName: string,
|
||||||
|
baseCurrency: string,
|
||||||
|
};
|
||||||
@@ -26,3 +26,9 @@ export interface IJournalReportEntriesGroup {
|
|||||||
export interface IJournalReport {
|
export interface IJournalReport {
|
||||||
entries: IJournalReportEntriesGroup[],
|
entries: IJournalReportEntriesGroup[],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IJournalSheetMeta {
|
||||||
|
isCostComputeRunning: boolean,
|
||||||
|
organizationName: string,
|
||||||
|
baseCurrency: string,
|
||||||
|
}
|
||||||
@@ -15,3 +15,11 @@ export interface ILicensesFilter {
|
|||||||
used: boolean,
|
used: boolean,
|
||||||
sent: boolean,
|
sent: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ISendLicenseDTO {
|
||||||
|
phoneNumber: string,
|
||||||
|
email: string,
|
||||||
|
period: string,
|
||||||
|
periodInterval: string,
|
||||||
|
planSlug: string,
|
||||||
|
};
|
||||||
@@ -55,3 +55,9 @@ export interface IProfitLossSheetStatement {
|
|||||||
operatingProfit: IProfitLossSheetTotalSection;
|
operatingProfit: IProfitLossSheetTotalSection;
|
||||||
grossProfit: IProfitLossSheetTotalSection;
|
grossProfit: IProfitLossSheetTotalSection;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface IProfitLossSheetMeta {
|
||||||
|
isCostComputeRunning: boolean,
|
||||||
|
organizationName: string,
|
||||||
|
baseCurrency: string,
|
||||||
|
}
|
||||||
@@ -21,6 +21,12 @@ export interface ITrialBalanceTotal {
|
|||||||
formattedBalance: string;
|
formattedBalance: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ITrialBalanceSheetMeta {
|
||||||
|
isCostComputeRunning: boolean,
|
||||||
|
organizationName: string,
|
||||||
|
baseCurrency: string,
|
||||||
|
};
|
||||||
|
|
||||||
export interface ITrialBalanceAccount extends ITrialBalanceTotal {
|
export interface ITrialBalanceAccount extends ITrialBalanceTotal {
|
||||||
id: number;
|
id: number;
|
||||||
parentAccountId: number;
|
parentAccountId: number;
|
||||||
@@ -38,4 +44,5 @@ export type ITrialBalanceSheetData = {
|
|||||||
export interface ITrialBalanceStatement {
|
export interface ITrialBalanceStatement {
|
||||||
data: ITrialBalanceSheetData;
|
data: ITrialBalanceSheetData;
|
||||||
query: ITrialBalanceSheetQuery;
|
query: ITrialBalanceSheetQuery;
|
||||||
|
meta: ITrialBalanceSheetMeta,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import {EventDispatcher} from "event-dispatch";
|
import {EventDispatcher} from "event-dispatch";
|
||||||
// import {
|
|
||||||
// EventDispatcher,
|
|
||||||
// } from 'decorators/eventDispatcher';
|
|
||||||
import events from 'subscribers/events';
|
import events from 'subscribers/events';
|
||||||
import InventoryService from 'services/Inventory/Inventory';
|
import InventoryService from 'services/Inventory/Inventory';
|
||||||
|
|
||||||
@@ -11,7 +8,7 @@ export default class ComputeItemCostJob {
|
|||||||
eventDispatcher: EventDispatcher;
|
eventDispatcher: EventDispatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Constructor method.
|
||||||
* @param agenda
|
* @param agenda
|
||||||
*/
|
*/
|
||||||
constructor(agenda) {
|
constructor(agenda) {
|
||||||
|
|||||||
@@ -1,13 +1,24 @@
|
|||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
|
import {EventDispatcher} from "event-dispatch";
|
||||||
|
import events from 'subscribers/events';
|
||||||
import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
|
import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
|
||||||
|
|
||||||
export default class WriteInvoicesJournalEntries {
|
export default class WriteInvoicesJournalEntries {
|
||||||
|
eventDispatcher: EventDispatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
*/
|
||||||
constructor(agenda) {
|
constructor(agenda) {
|
||||||
|
const eventName = 'rewrite-invoices-journal-entries';
|
||||||
|
this.eventDispatcher = new EventDispatcher();
|
||||||
|
|
||||||
agenda.define(
|
agenda.define(
|
||||||
'rewrite-invoices-journal-entries',
|
eventName,
|
||||||
{ priority: 'normal', concurrency: 1 },
|
{ priority: 'normal', concurrency: 1 },
|
||||||
this.handler.bind(this)
|
this.handler.bind(this)
|
||||||
);
|
);
|
||||||
|
agenda.on(`complete:${eventName}`, this.onJobCompleted.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handler(job, done: Function): Promise<void> {
|
public async handler(job, done: Function): Promise<void> {
|
||||||
@@ -36,4 +47,16 @@ export default class WriteInvoicesJournalEntries {
|
|||||||
done(e);
|
done(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the job complete.
|
||||||
|
*/
|
||||||
|
async onJobCompleted(job) {
|
||||||
|
const { startingDate, itemId, tenantId } = job.attrs.data;
|
||||||
|
|
||||||
|
await this.eventDispatcher.dispatch(
|
||||||
|
events.inventory.onInventoryCostEntriesWritten,
|
||||||
|
{ startingDate, itemId, tenantId }
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ export default class AccountTransaction extends TenantModel {
|
|||||||
return ['createdAt'];
|
return ['createdAt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Virtual attributes.
|
||||||
|
*/
|
||||||
static get virtualAttributes() {
|
static get virtualAttributes() {
|
||||||
return ['referenceTypeFormatted'];
|
return ['referenceTypeFormatted'];
|
||||||
}
|
}
|
||||||
@@ -37,6 +40,7 @@ export default class AccountTransaction extends TenantModel {
|
|||||||
'SaleInvoice': 'Sale invoice',
|
'SaleInvoice': 'Sale invoice',
|
||||||
'SaleReceipt': 'Sale receipt',
|
'SaleReceipt': 'Sale receipt',
|
||||||
'PaymentReceive': 'Payment receive',
|
'PaymentReceive': 'Payment receive',
|
||||||
|
'Bill': 'Bill',
|
||||||
'BillPayment': 'Payment made',
|
'BillPayment': 'Payment made',
|
||||||
'VendorOpeningBalance': 'Vendor opening balance',
|
'VendorOpeningBalance': 'Vendor opening balance',
|
||||||
'CustomerOpeningBalance': 'Customer opening balance',
|
'CustomerOpeningBalance': 'Customer opening balance',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { IAPAgingSummaryQuery } from 'interfaces';
|
import { IAPAgingSummaryQuery, IARAgingSummaryMeta } from 'interfaces';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import APAgingSummarySheet from './APAgingSummarySheet';
|
import APAgingSummarySheet from './APAgingSummarySheet';
|
||||||
|
|
||||||
@@ -25,13 +25,36 @@ export default class PayableAgingSummaryService {
|
|||||||
divideOn1000: false,
|
divideOn1000: false,
|
||||||
showZero: false,
|
showZero: false,
|
||||||
formatMoney: 'total',
|
formatMoney: 'total',
|
||||||
negativeFormat: 'mines'
|
negativeFormat: 'mines',
|
||||||
},
|
},
|
||||||
vendorsIds: [],
|
vendorsIds: [],
|
||||||
noneZero: false,
|
noneZero: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the balance sheet meta.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @returns {IBalanceSheetMeta}
|
||||||
|
*/
|
||||||
|
reportMetadata(tenantId: number): IARAgingSummaryMeta {
|
||||||
|
const settings = this.tenancy.settings(tenantId);
|
||||||
|
|
||||||
|
const organizationName = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'name',
|
||||||
|
});
|
||||||
|
const baseCurrency = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'base_currency',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
organizationName,
|
||||||
|
baseCurrency,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve A/P aging summary report.
|
* Retrieve A/P aging summary report.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
@@ -81,6 +104,11 @@ export default class PayableAgingSummaryService {
|
|||||||
const data = APAgingSummaryReport.reportData();
|
const data = APAgingSummaryReport.reportData();
|
||||||
const columns = APAgingSummaryReport.reportColumns();
|
const columns = APAgingSummaryReport.reportColumns();
|
||||||
|
|
||||||
return { data, columns, query: filter };
|
return {
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
query: filter,
|
||||||
|
meta: this.reportMetadata(tenantId),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { IARAgingSummaryQuery } from 'interfaces';
|
import { IARAgingSummaryQuery, IARAgingSummaryMeta } from 'interfaces';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import ARAgingSummarySheet from './ARAgingSummarySheet';
|
import ARAgingSummarySheet from './ARAgingSummarySheet';
|
||||||
|
|
||||||
@@ -32,6 +32,29 @@ export default class ARAgingSummaryService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the balance sheet meta.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @returns {IBalanceSheetMeta}
|
||||||
|
*/
|
||||||
|
reportMetadata(tenantId: number): IARAgingSummaryMeta {
|
||||||
|
const settings = this.tenancy.settings(tenantId);
|
||||||
|
|
||||||
|
const organizationName = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'name',
|
||||||
|
});
|
||||||
|
const baseCurrency = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'base_currency',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
organizationName,
|
||||||
|
baseCurrency,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve A/R aging summary report.
|
* Retrieve A/R aging summary report.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
@@ -85,6 +108,11 @@ export default class ARAgingSummaryService {
|
|||||||
const data = ARAgingSummaryReport.reportData();
|
const data = ARAgingSummaryReport.reportData();
|
||||||
const columns = ARAgingSummaryReport.reportColumns();
|
const columns = ARAgingSummaryReport.reportColumns();
|
||||||
|
|
||||||
return { data, columns, query: filter };
|
return {
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
query: filter,
|
||||||
|
meta: this.reportMetadata(tenantId),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ import {
|
|||||||
IBalanceSheetStatementService,
|
IBalanceSheetStatementService,
|
||||||
IBalanceSheetQuery,
|
IBalanceSheetQuery,
|
||||||
IBalanceSheetStatement,
|
IBalanceSheetStatement,
|
||||||
|
IBalanceSheetMeta,
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import Journal from 'services/Accounting/JournalPoster';
|
import Journal from 'services/Accounting/JournalPoster';
|
||||||
import BalanceSheetStatement from './BalanceSheet';
|
import BalanceSheetStatement from './BalanceSheet';
|
||||||
|
import InventoryService from 'services/Inventory/Inventory';
|
||||||
|
import { parseBoolean } from 'utils';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class BalanceSheetStatementService
|
export default class BalanceSheetStatementService
|
||||||
@@ -18,6 +21,9 @@ export default class BalanceSheetStatementService
|
|||||||
@Inject('logger')
|
@Inject('logger')
|
||||||
logger: any;
|
logger: any;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
inventoryService: InventoryService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defaults balance sheet filter query.
|
* Defaults balance sheet filter query.
|
||||||
* @return {IBalanceSheetQuery}
|
* @return {IBalanceSheetQuery}
|
||||||
@@ -33,7 +39,7 @@ export default class BalanceSheetStatementService
|
|||||||
divideOn1000: false,
|
divideOn1000: false,
|
||||||
showZero: false,
|
showZero: false,
|
||||||
formatMoney: 'total',
|
formatMoney: 'total',
|
||||||
negativeFormat: 'mines'
|
negativeFormat: 'mines',
|
||||||
},
|
},
|
||||||
noneZero: false,
|
noneZero: false,
|
||||||
noneTransactions: false,
|
noneTransactions: false,
|
||||||
@@ -42,6 +48,33 @@ export default class BalanceSheetStatementService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the balance sheet meta.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @returns {IBalanceSheetMeta}
|
||||||
|
*/
|
||||||
|
reportMetadata(tenantId: number): IBalanceSheetMeta {
|
||||||
|
const settings = this.tenancy.settings(tenantId);
|
||||||
|
|
||||||
|
const isCostComputeRunning = this.inventoryService
|
||||||
|
.isItemsCostComputeRunning(tenantId);
|
||||||
|
|
||||||
|
const organizationName = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'name',
|
||||||
|
});
|
||||||
|
const baseCurrency = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'base_currency',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
isCostComputeRunning: parseBoolean(isCostComputeRunning, false),
|
||||||
|
organizationName,
|
||||||
|
baseCurrency
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve balance sheet statement.
|
* Retrieve balance sheet statement.
|
||||||
* -------------
|
* -------------
|
||||||
@@ -61,14 +94,19 @@ export default class BalanceSheetStatementService
|
|||||||
|
|
||||||
// Settings tenant service.
|
// Settings tenant service.
|
||||||
const settings = this.tenancy.settings(tenantId);
|
const settings = this.tenancy.settings(tenantId);
|
||||||
const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' });
|
const baseCurrency = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'base_currency',
|
||||||
|
});
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
...this.defaultQuery,
|
...this.defaultQuery,
|
||||||
...query,
|
...query,
|
||||||
};
|
};
|
||||||
this.logger.info('[balance_sheet] trying to calculate the report.', { filter, tenantId });
|
this.logger.info('[balance_sheet] trying to calculate the report.', {
|
||||||
|
filter,
|
||||||
|
tenantId,
|
||||||
|
});
|
||||||
// Retrieve all accounts on the storage.
|
// Retrieve all accounts on the storage.
|
||||||
const accounts = await accountRepository.all();
|
const accounts = await accountRepository.all();
|
||||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||||
@@ -82,7 +120,7 @@ export default class BalanceSheetStatementService
|
|||||||
const transactionsJournal = Journal.fromTransactions(
|
const transactionsJournal = Journal.fromTransactions(
|
||||||
transactions,
|
transactions,
|
||||||
tenantId,
|
tenantId,
|
||||||
accountsGraph,
|
accountsGraph
|
||||||
);
|
);
|
||||||
// Balance sheet report instance.
|
// Balance sheet report instance.
|
||||||
const balanceSheetInstanace = new BalanceSheetStatement(
|
const balanceSheetInstanace = new BalanceSheetStatement(
|
||||||
@@ -102,6 +140,7 @@ export default class BalanceSheetStatementService
|
|||||||
data: balanceSheetData,
|
data: balanceSheetData,
|
||||||
columns: balanceSheetColumns,
|
columns: balanceSheetColumns,
|
||||||
query: filter,
|
query: filter,
|
||||||
|
meta: this.reportMetadata(tenantId),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { pick, get, last } from 'lodash';
|
import { isEmpty, get, last } from 'lodash';
|
||||||
import {
|
import {
|
||||||
IGeneralLedgerSheetQuery,
|
IGeneralLedgerSheetQuery,
|
||||||
IGeneralLedgerSheetAccount,
|
IGeneralLedgerSheetAccount,
|
||||||
@@ -73,10 +73,9 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
entryReducer(
|
entryReducer(
|
||||||
entries: IGeneralLedgerSheetAccountTransaction[],
|
entries: IGeneralLedgerSheetAccountTransaction[],
|
||||||
entry: IJournalEntry,
|
entry: IJournalEntry,
|
||||||
index: number
|
openingBalance: number
|
||||||
): IGeneralLedgerSheetAccountTransaction[] {
|
): IGeneralLedgerSheetAccountTransaction[] {
|
||||||
const lastEntry = last(entries);
|
const lastEntry = last(entries);
|
||||||
const openingBalance = 0;
|
|
||||||
|
|
||||||
const contact = this.contactsMap.get(entry.contactId);
|
const contact = this.contactsMap.get(entry.contactId);
|
||||||
const amount = this.getAmount(
|
const amount = this.getAmount(
|
||||||
@@ -85,11 +84,7 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
entry.accountNormal
|
entry.accountNormal
|
||||||
);
|
);
|
||||||
const runningBalance =
|
const runningBalance =
|
||||||
(entries.length === 0
|
amount + (!isEmpty(entries) ? lastEntry.runningBalance : openingBalance);
|
||||||
? openingBalance
|
|
||||||
: lastEntry
|
|
||||||
? lastEntry.runningBalance
|
|
||||||
: 0) + amount;
|
|
||||||
|
|
||||||
const newEntry = {
|
const newEntry = {
|
||||||
date: entry.date,
|
date: entry.date,
|
||||||
@@ -182,9 +177,7 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
* @param {IAccount} account
|
* @param {IAccount} account
|
||||||
* @return {IGeneralLedgerSheetAccount}
|
* @return {IGeneralLedgerSheetAccount}
|
||||||
*/
|
*/
|
||||||
private accountMapper(
|
private accountMapper(account: IAccount): IGeneralLedgerSheetAccount {
|
||||||
account: IAccount
|
|
||||||
): IGeneralLedgerSheetAccount {
|
|
||||||
const openingBalance = this.accountOpeningBalance(account);
|
const openingBalance = this.accountOpeningBalance(account);
|
||||||
const closingBalance = this.accountClosingBalance(account);
|
const closingBalance = this.accountClosingBalance(account);
|
||||||
|
|
||||||
@@ -208,14 +201,10 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
* @param {IAccount[]} accounts -
|
* @param {IAccount[]} accounts -
|
||||||
* @return {IGeneralLedgerSheetAccount[]}
|
* @return {IGeneralLedgerSheetAccount[]}
|
||||||
*/
|
*/
|
||||||
private accountsWalker(
|
private accountsWalker(accounts: IAccount[]): IGeneralLedgerSheetAccount[] {
|
||||||
accounts: IAccount[]
|
|
||||||
): IGeneralLedgerSheetAccount[] {
|
|
||||||
return (
|
return (
|
||||||
accounts
|
accounts
|
||||||
.map((account: IAccount) =>
|
.map((account: IAccount) => this.accountMapper(account))
|
||||||
this.accountMapper(account)
|
|
||||||
)
|
|
||||||
// Filter general ledger accounts that have no transactions
|
// Filter general ledger accounts that have no transactions
|
||||||
// when`noneTransactions` is on.
|
// when`noneTransactions` is on.
|
||||||
.filter(
|
.filter(
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { Service, Inject } from 'typedi';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import { difference } from 'lodash';
|
import { difference } from 'lodash';
|
||||||
import { IGeneralLedgerSheetQuery } from 'interfaces';
|
import { IGeneralLedgerSheetQuery, IGeneralLedgerMeta } from 'interfaces';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import Journal from 'services/Accounting/JournalPoster';
|
import Journal from 'services/Accounting/JournalPoster';
|
||||||
import GeneralLedgerSheet from 'services/FinancialStatements/GeneralLedger/GeneralLedger';
|
import GeneralLedgerSheet from 'services/FinancialStatements/GeneralLedger/GeneralLedger';
|
||||||
|
import InventoryService from 'services/Inventory/Inventory';
|
||||||
import { transformToMap } from 'utils';
|
import { transformToMap, parseBoolean } from 'utils';
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
ACCOUNTS_NOT_FOUND: 'ACCOUNTS_NOT_FOUND',
|
ACCOUNTS_NOT_FOUND: 'ACCOUNTS_NOT_FOUND',
|
||||||
@@ -18,6 +18,9 @@ export default class GeneralLedgerService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
tenancy: TenancyService;
|
tenancy: TenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
inventoryService: InventoryService;
|
||||||
|
|
||||||
@Inject('logger')
|
@Inject('logger')
|
||||||
logger: any;
|
logger: any;
|
||||||
|
|
||||||
@@ -55,6 +58,33 @@ export default class GeneralLedgerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the balance sheet meta.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @returns {IGeneralLedgerMeta}
|
||||||
|
*/
|
||||||
|
reportMetadata(tenantId: number): IGeneralLedgerMeta {
|
||||||
|
const settings = this.tenancy.settings(tenantId);
|
||||||
|
|
||||||
|
const isCostComputeRunning = this.inventoryService
|
||||||
|
.isItemsCostComputeRunning(tenantId);
|
||||||
|
|
||||||
|
const organizationName = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'name',
|
||||||
|
});
|
||||||
|
const baseCurrency = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'base_currency',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
isCostComputeRunning: parseBoolean(isCostComputeRunning, false),
|
||||||
|
organizationName,
|
||||||
|
baseCurrency
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve general ledger report statement.
|
* Retrieve general ledger report statement.
|
||||||
* ----------
|
* ----------
|
||||||
@@ -68,6 +98,7 @@ export default class GeneralLedgerService {
|
|||||||
): Promise<{
|
): Promise<{
|
||||||
data: any;
|
data: any;
|
||||||
query: IGeneralLedgerSheetQuery;
|
query: IGeneralLedgerSheetQuery;
|
||||||
|
meta: IGeneralLedgerMeta
|
||||||
}> {
|
}> {
|
||||||
const {
|
const {
|
||||||
accountRepository,
|
accountRepository,
|
||||||
@@ -146,6 +177,7 @@ export default class GeneralLedgerService {
|
|||||||
return {
|
return {
|
||||||
data: reportData,
|
data: reportData,
|
||||||
query: filter,
|
query: filter,
|
||||||
|
meta: this.reportMetadata(tenantId),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { IJournalReportQuery } from 'interfaces';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { IJournalReportQuery, IJournalSheetMeta } from 'interfaces';
|
||||||
|
|
||||||
import JournalSheet from './JournalSheet';
|
import JournalSheet from './JournalSheet';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import Journal from 'services/Accounting/JournalPoster';
|
import Journal from 'services/Accounting/JournalPoster';
|
||||||
|
import InventoryService from 'services/Inventory/Inventory';
|
||||||
import { transformToMap } from 'utils';
|
import { parseBoolean, transformToMap } from 'utils';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class JournalSheetService {
|
export default class JournalSheetService {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: TenancyService;
|
tenancy: TenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
inventoryService: InventoryService;
|
||||||
|
|
||||||
@Inject('logger')
|
@Inject('logger')
|
||||||
logger: any;
|
logger: any;
|
||||||
|
|
||||||
@@ -34,6 +37,33 @@ export default class JournalSheetService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the balance sheet meta.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @returns {IBalanceSheetMeta}
|
||||||
|
*/
|
||||||
|
reportMetadata(tenantId: number): IJournalSheetMeta {
|
||||||
|
const settings = this.tenancy.settings(tenantId);
|
||||||
|
|
||||||
|
const isCostComputeRunning = this.inventoryService
|
||||||
|
.isItemsCostComputeRunning(tenantId);
|
||||||
|
|
||||||
|
const organizationName = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'name',
|
||||||
|
});
|
||||||
|
const baseCurrency = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'base_currency',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
isCostComputeRunning: parseBoolean(isCostComputeRunning, false),
|
||||||
|
organizationName,
|
||||||
|
baseCurrency
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Journal sheet.
|
* Journal sheet.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -96,6 +126,7 @@ export default class JournalSheetService {
|
|||||||
return {
|
return {
|
||||||
data: journalSheetData,
|
data: journalSheetData,
|
||||||
query: filter,
|
query: filter,
|
||||||
|
meta: this.reportMetadata(tenantId),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Journal from 'services/Accounting/JournalPoster';
|
import Journal from 'services/Accounting/JournalPoster';
|
||||||
import { IProfitLossSheetQuery } from 'interfaces';
|
import { IProfitLossSheetQuery, IProfitLossSheetMeta } from 'interfaces';
|
||||||
import ProfitLossSheet from './ProfitLossSheet';
|
import ProfitLossSheet from './ProfitLossSheet';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import AccountsService from 'services/Accounts/AccountsService';
|
import AccountsService from 'services/Accounts/AccountsService';
|
||||||
|
import InventoryService from 'services/Inventory/Inventory';
|
||||||
|
import { parseBoolean } from 'utils';
|
||||||
|
|
||||||
// Profit/Loss sheet service.
|
// Profit/Loss sheet service.
|
||||||
@Service()
|
@Service()
|
||||||
@@ -15,6 +17,9 @@ export default class ProfitLossSheetService {
|
|||||||
@Inject('logger')
|
@Inject('logger')
|
||||||
logger: any;
|
logger: any;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
inventoryService: InventoryService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
accountsService: AccountsService;
|
accountsService: AccountsService;
|
||||||
|
|
||||||
@@ -42,6 +47,34 @@ export default class ProfitLossSheetService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the trial balance sheet meta.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @returns {ITrialBalanceSheetMeta}
|
||||||
|
*/
|
||||||
|
reportMetadata(tenantId: number): IProfitLossSheetMeta {
|
||||||
|
const settings = this.tenancy.settings(tenantId);
|
||||||
|
|
||||||
|
const isCostComputeRunning = this.inventoryService.isItemsCostComputeRunning(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
const organizationName = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'name',
|
||||||
|
});
|
||||||
|
const baseCurrency = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'base_currency',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
isCostComputeRunning: parseBoolean(isCostComputeRunning, false),
|
||||||
|
organizationName,
|
||||||
|
baseCurrency,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve profit/loss sheet statement.
|
* Retrieve profit/loss sheet statement.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -107,6 +140,7 @@ export default class ProfitLossSheetService {
|
|||||||
data: profitLossData,
|
data: profitLossData,
|
||||||
columns: profitLossColumns,
|
columns: profitLossColumns,
|
||||||
query: filter,
|
query: filter,
|
||||||
|
meta: this.reportMetadata(tenantId),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,20 @@ import { Service, Inject } from 'typedi';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import Journal from 'services/Accounting/JournalPoster';
|
import Journal from 'services/Accounting/JournalPoster';
|
||||||
import { INumberFormatQuery, ITrialBalanceSheetQuery, ITrialBalanceStatement } from 'interfaces';
|
import { ITrialBalanceSheetMeta, ITrialBalanceSheetQuery, ITrialBalanceStatement } from 'interfaces';
|
||||||
import TrialBalanceSheet from './TrialBalanceSheet';
|
import TrialBalanceSheet from './TrialBalanceSheet';
|
||||||
import FinancialSheet from '../FinancialSheet';
|
import FinancialSheet from '../FinancialSheet';
|
||||||
|
import InventoryService from 'services/Inventory/Inventory';
|
||||||
|
import { parseBoolean } from 'utils';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class TrialBalanceSheetService extends FinancialSheet {
|
export default class TrialBalanceSheetService extends FinancialSheet {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: TenancyService;
|
tenancy: TenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
inventoryService: InventoryService;
|
||||||
|
|
||||||
@Inject('logger')
|
@Inject('logger')
|
||||||
logger: any;
|
logger: any;
|
||||||
|
|
||||||
@@ -36,6 +41,33 @@ export default class TrialBalanceSheetService extends FinancialSheet {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the trial balance sheet meta.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @returns {ITrialBalanceSheetMeta}
|
||||||
|
*/
|
||||||
|
reportMetadata(tenantId: number): ITrialBalanceSheetMeta {
|
||||||
|
const settings = this.tenancy.settings(tenantId);
|
||||||
|
|
||||||
|
const isCostComputeRunning = this.inventoryService.isItemsCostComputeRunning(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
const organizationName = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'name',
|
||||||
|
});
|
||||||
|
const baseCurrency = settings.get({
|
||||||
|
group: 'organization',
|
||||||
|
key: 'base_currency',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
isCostComputeRunning: parseBoolean(isCostComputeRunning, false),
|
||||||
|
organizationName,
|
||||||
|
baseCurrency,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve trial balance sheet statement.
|
* Retrieve trial balance sheet statement.
|
||||||
* -------------
|
* -------------
|
||||||
@@ -46,7 +78,7 @@ export default class TrialBalanceSheetService extends FinancialSheet {
|
|||||||
*/
|
*/
|
||||||
public async trialBalanceSheet(
|
public async trialBalanceSheet(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
query: ITrialBalanceSheetQuery,
|
query: ITrialBalanceSheetQuery
|
||||||
): Promise<ITrialBalanceStatement> {
|
): Promise<ITrialBalanceStatement> {
|
||||||
const filter = {
|
const filter = {
|
||||||
...this.defaultQuery,
|
...this.defaultQuery,
|
||||||
@@ -98,6 +130,7 @@ export default class TrialBalanceSheetService extends FinancialSheet {
|
|||||||
return {
|
return {
|
||||||
data: trialBalanceSheetData,
|
data: trialBalanceSheetData,
|
||||||
query: filter,
|
query: filter,
|
||||||
|
meta: this.reportMetadata(tenantId),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import InventoryCostLotTracker from 'services/Inventory/InventoryCostLotTracker'
|
|||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import events from 'subscribers/events';
|
import events from 'subscribers/events';
|
||||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||||
|
import SettingsMiddleware from 'api/middleware/SettingsMiddleware';
|
||||||
|
|
||||||
type TCostMethod = 'FIFO' | 'LIFO' | 'AVG';
|
type TCostMethod = 'FIFO' | 'LIFO' | 'AVG';
|
||||||
|
|
||||||
@@ -346,4 +347,37 @@ export default class InventoryService {
|
|||||||
|
|
||||||
return lotNumber;
|
return lotNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark item cost computing is running.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {boolean} isRunning -
|
||||||
|
*/
|
||||||
|
async markItemsCostComputeRunning(
|
||||||
|
tenantId: number,
|
||||||
|
isRunning: boolean = true
|
||||||
|
) {
|
||||||
|
const settings = this.tenancy.settings(tenantId);
|
||||||
|
|
||||||
|
settings.set({
|
||||||
|
key: 'cost_compute_running',
|
||||||
|
group: 'inventory',
|
||||||
|
value: isRunning,
|
||||||
|
});
|
||||||
|
await settings.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
isItemsCostComputeRunning(tenantId) {
|
||||||
|
const settings = this.tenancy.settings(tenantId);
|
||||||
|
|
||||||
|
return settings.get({
|
||||||
|
key: 'cost_compute_running',
|
||||||
|
group: 'inventory'
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
import { Service, Container, Inject } from 'typedi';
|
import { Service, Container, Inject } from 'typedi';
|
||||||
import cryptoRandomString from 'crypto-random-string';
|
import cryptoRandomString from 'crypto-random-string';
|
||||||
import { times } from 'lodash';
|
import { times } from 'lodash';
|
||||||
import { License } from "system/models";
|
import { License, Plan } from 'system/models';
|
||||||
import { ILicense } from 'interfaces';
|
import { ILicense, ISendLicenseDTO } from 'interfaces';
|
||||||
import LicenseMailMessages from 'services/Payment/LicenseMailMessages';
|
import LicenseMailMessages from 'services/Payment/LicenseMailMessages';
|
||||||
import LicenseSMSMessages from 'services/Payment/LicenseSMSMessages';
|
import LicenseSMSMessages from 'services/Payment/LicenseSMSMessages';
|
||||||
|
import { ServiceError } from 'exceptions';
|
||||||
|
|
||||||
|
const ERRORS = {
|
||||||
|
PLAN_NOT_FOUND: 'PLAN_NOT_FOUND',
|
||||||
|
LICENSE_NOT_FOUND: 'LICENSE_NOT_FOUND',
|
||||||
|
LICENSE_ALREADY_DISABLED: 'LICENSE_ALREADY_DISABLED',
|
||||||
|
NO_AVALIABLE_LICENSE_CODE: 'NO_AVALIABLE_LICENSE_CODE',
|
||||||
|
};
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class LicenseService {
|
export default class LicenseService {
|
||||||
@@ -14,49 +22,99 @@ export default class LicenseService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
mailMessages: LicenseMailMessages;
|
mailMessages: LicenseMailMessages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the plan existance on the storage.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {string} planSlug - Plan slug.
|
||||||
|
*/
|
||||||
|
private async getPlanOrThrowError(planSlug: string) {
|
||||||
|
const foundPlan = await Plan.query().where('slug', planSlug).first();
|
||||||
|
|
||||||
|
if (!foundPlan) {
|
||||||
|
throw new ServiceError(ERRORS.PLAN_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return foundPlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valdiate the license existance on the storage.
|
||||||
|
* @param {number} licenseId - License id.
|
||||||
|
*/
|
||||||
|
private async getLicenseOrThrowError(licenseId: number) {
|
||||||
|
const foundLicense = await License.query().findById(licenseId);
|
||||||
|
|
||||||
|
if (!foundLicense) {
|
||||||
|
throw new ServiceError(ERRORS.LICENSE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return foundLicense;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates whether the license id is disabled.
|
||||||
|
* @param {ILicense} license
|
||||||
|
*/
|
||||||
|
private validateNotDisabledLicense(license: ILicense) {
|
||||||
|
if (license.disabledAt) {
|
||||||
|
throw new ServiceError(ERRORS.LICENSE_ALREADY_DISABLED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the license code in the given period.
|
* Generates the license code in the given period.
|
||||||
* @param {number} licensePeriod
|
* @param {number} licensePeriod
|
||||||
* @return {Promise<ILicense>}
|
* @return {Promise<ILicense>}
|
||||||
*/
|
*/
|
||||||
async generateLicense(
|
public async generateLicense(
|
||||||
licensePeriod: number,
|
licensePeriod: number,
|
||||||
periodInterval: string = 'days',
|
periodInterval: string = 'days',
|
||||||
planId: number,
|
planSlug: string
|
||||||
): ILicense {
|
): ILicense {
|
||||||
let licenseCode: string;
|
let licenseCode: string;
|
||||||
let repeat: boolean = true;
|
let repeat: boolean = true;
|
||||||
|
|
||||||
while(repeat) {
|
// Retrieve plan or throw not found error.
|
||||||
|
const plan = await this.getPlanOrThrowError(planSlug);
|
||||||
|
|
||||||
|
while (repeat) {
|
||||||
licenseCode = cryptoRandomString({ length: 10, type: 'numeric' });
|
licenseCode = cryptoRandomString({ length: 10, type: 'numeric' });
|
||||||
const foundLicenses = await License.query().where('license_code', licenseCode);
|
const foundLicenses = await License.query().where(
|
||||||
|
'license_code',
|
||||||
|
licenseCode
|
||||||
|
);
|
||||||
|
|
||||||
if (foundLicenses.length === 0) {
|
if (foundLicenses.length === 0) {
|
||||||
repeat = false;
|
repeat = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return License.query().insert({
|
return License.query().insert({
|
||||||
licenseCode, licensePeriod, periodInterval, planId,
|
licenseCode,
|
||||||
|
licensePeriod,
|
||||||
|
periodInterval,
|
||||||
|
planId: plan.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Generates licenses.
|
||||||
* @param {number} loop
|
* @param {number} loop
|
||||||
* @param {number} licensePeriod
|
* @param {number} licensePeriod
|
||||||
* @param {string} periodInterval
|
* @param {string} periodInterval
|
||||||
* @param {number} planId
|
* @param {number} planId
|
||||||
*/
|
*/
|
||||||
async generateLicenses(
|
public async generateLicenses(
|
||||||
loop = 1,
|
loop = 1,
|
||||||
licensePeriod: number,
|
licensePeriod: number,
|
||||||
periodInterval: string = 'days',
|
periodInterval: string = 'days',
|
||||||
planId: number,
|
planSlug: string
|
||||||
) {
|
) {
|
||||||
const asyncOpers: Promise<any>[] = [];
|
const asyncOpers: Promise<any>[] = [];
|
||||||
|
|
||||||
times(loop, () => {
|
times(loop, () => {
|
||||||
const generateOper = this.generateLicense(licensePeriod, periodInterval, planId);
|
const generateOper = this.generateLicense(
|
||||||
|
licensePeriod,
|
||||||
|
periodInterval,
|
||||||
|
planSlug
|
||||||
|
);
|
||||||
asyncOpers.push(generateOper);
|
asyncOpers.push(generateOper);
|
||||||
});
|
});
|
||||||
return Promise.all(asyncOpers);
|
return Promise.all(asyncOpers);
|
||||||
@@ -64,38 +122,64 @@ export default class LicenseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Disables the given license id on the storage.
|
* Disables the given license id on the storage.
|
||||||
* @param {number} licenseId
|
* @param {string} licenseSlug - License slug.
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
async disableLicense(licenseId: number) {
|
public async disableLicense(licenseId: number) {
|
||||||
return License.markLicenseAsDisabled(licenseId, 'id');
|
const license = await this.getLicenseOrThrowError(licenseId);
|
||||||
|
|
||||||
|
this.validateNotDisabledLicense(license);
|
||||||
|
|
||||||
|
return License.markLicenseAsDisabled(license.id, 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the given license id from the storage.
|
* Deletes the given license id from the storage.
|
||||||
* @param licenseId
|
* @param licenseSlug {string} - License slug.
|
||||||
*/
|
*/
|
||||||
async deleteLicense(licenseId: number) {
|
public async deleteLicense(licenseSlug: string) {
|
||||||
return License.query().where('id', licenseId).delete();
|
const license = await this.getPlanOrThrowError(licenseSlug);
|
||||||
|
|
||||||
|
return License.query().where('id', license.id).delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends license code to the given customer via SMS or mail message.
|
* Sends license code to the given customer via SMS or mail message.
|
||||||
* @param {string} licenseCode - License code
|
* @param {string} licenseCode - License code.
|
||||||
* @param {string} phoneNumber - Phone number
|
* @param {string} phoneNumber - Phone number.
|
||||||
* @param {string} email - Email address.
|
* @param {string} email - Email address.
|
||||||
*/
|
*/
|
||||||
async sendLicenseToCustomer(licenseCode: string, phoneNumber: string, email: string) {
|
public async sendLicenseToCustomer(sendLicense: ISendLicenseDTO) {
|
||||||
const agenda = Container.get('agenda');
|
const agenda = Container.get('agenda');
|
||||||
|
const { phoneNumber, email, period, periodInterval } = sendLicense;
|
||||||
|
|
||||||
|
// Retreive plan details byt the given plan slug.
|
||||||
|
const plan = await this.getPlanOrThrowError(sendLicense.planSlug);
|
||||||
|
|
||||||
|
const license = await License.query()
|
||||||
|
.modify('filterActiveLicense')
|
||||||
|
.where('license_period', period)
|
||||||
|
.where('period_interval', periodInterval)
|
||||||
|
.where('plan_id', plan.id)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!license) {
|
||||||
|
throw new ServiceError(ERRORS.NO_AVALIABLE_LICENSE_CODE)
|
||||||
|
}
|
||||||
// Mark the license as used.
|
// Mark the license as used.
|
||||||
await License.markLicenseAsSent(licenseCode);
|
await License.markLicenseAsSent(license.licenseCode);
|
||||||
|
|
||||||
if (email) {
|
if (sendLicense.email) {
|
||||||
await agenda.schedule('1 second', 'send-license-via-email', { licenseCode, email });
|
await agenda.schedule('1 second', 'send-license-via-email', {
|
||||||
|
licenseCode: license.licenseCode,
|
||||||
|
email,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (phoneNumber) {
|
if (phoneNumber) {
|
||||||
await agenda.schedule('1 second', 'send-license-via-phone', { licenseCode, phoneNumber });
|
await agenda.schedule('1 second', 'send-license-via-phone', {
|
||||||
|
licenseCode: license.licenseCode,
|
||||||
|
phoneNumber,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,24 @@
|
|||||||
import { License } from "system/models";
|
import { License } from 'system/models';
|
||||||
import PaymentMethod from 'services/Payment/PaymentMethod';
|
import PaymentMethod from 'services/Payment/PaymentMethod';
|
||||||
import { Plan } from 'system/models';
|
import { Plan } from 'system/models';
|
||||||
import { IPaymentMethod, ILicensePaymentModel } from 'interfaces';
|
import { IPaymentMethod, ILicensePaymentModel } from 'interfaces';
|
||||||
import { ILicensePaymentModel } from "interfaces";
|
import { ILicensePaymentModel } from 'interfaces';
|
||||||
import { PaymentInputInvalid, PaymentAmountInvalidWithPlan } from 'exceptions';
|
import {
|
||||||
|
PaymentInputInvalid,
|
||||||
|
PaymentAmountInvalidWithPlan,
|
||||||
|
VoucherCodeRequired,
|
||||||
|
} from 'exceptions';
|
||||||
|
|
||||||
export default class LicensePaymentMethod extends PaymentMethod implements IPaymentMethod {
|
export default class LicensePaymentMethod
|
||||||
|
extends PaymentMethod
|
||||||
|
implements IPaymentMethod {
|
||||||
/**
|
/**
|
||||||
* Payment subscription of organization via license code.
|
* Payment subscription of organization via license code.
|
||||||
* @param {ILicensePaymentModel} licensePaymentModel -
|
* @param {ILicensePaymentModel} licensePaymentModel -
|
||||||
*/
|
*/
|
||||||
async payment(licensePaymentModel: ILicensePaymentModel, plan: Plan) {
|
async payment(licensePaymentModel: ILicensePaymentModel, plan: Plan) {
|
||||||
|
this.validateLicensePaymentModel(licensePaymentModel);
|
||||||
|
|
||||||
const license = await this.getLicenseOrThrowInvalid(licensePaymentModel);
|
const license = await this.getLicenseOrThrowInvalid(licensePaymentModel);
|
||||||
this.validatePaymentAmountWithPlan(license, plan);
|
this.validatePaymentAmountWithPlan(license, plan);
|
||||||
|
|
||||||
@@ -44,4 +52,14 @@ export default class LicensePaymentMethod extends PaymentMethod implements IPaym
|
|||||||
throw new PaymentAmountInvalidWithPlan();
|
throw new PaymentAmountInvalidWithPlan();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate voucher payload.
|
||||||
|
* @param {ILicensePaymentModel} licenseModel -
|
||||||
|
*/
|
||||||
|
validateLicensePaymentModel(licenseModel: ILicensePaymentModel) {
|
||||||
|
if (!licenseModel || !licenseModel.licenseCode) {
|
||||||
|
throw new VoucherCodeRequired();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -31,10 +31,8 @@ export default class Subscription<PaymentModel> {
|
|||||||
) {
|
) {
|
||||||
this.validateIfPlanHasPriceNoPayment(plan, paymentModel);
|
this.validateIfPlanHasPriceNoPayment(plan, paymentModel);
|
||||||
|
|
||||||
// @todo
|
|
||||||
if (plan.price > 0) {
|
|
||||||
await this.paymentContext.makePayment(paymentModel, plan);
|
await this.paymentContext.makePayment(paymentModel, plan);
|
||||||
}
|
|
||||||
const subscription = await tenant.$relatedQuery('subscriptions')
|
const subscription = await tenant.$relatedQuery('subscriptions')
|
||||||
.modify('subscriptionBySlug', subscriptionSlug)
|
.modify('subscriptionBySlug', subscriptionSlug)
|
||||||
.first();
|
.first();
|
||||||
|
|||||||
@@ -2,25 +2,52 @@ import { Container } from 'typedi';
|
|||||||
import { EventSubscriber, On } from 'event-dispatch';
|
import { EventSubscriber, On } from 'event-dispatch';
|
||||||
import { map, head } from 'lodash';
|
import { map, head } from 'lodash';
|
||||||
import events from 'subscribers/events';
|
import events from 'subscribers/events';
|
||||||
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import SaleInvoicesCost from 'services/Sales/SalesInvoicesCost';
|
import SaleInvoicesCost from 'services/Sales/SalesInvoicesCost';
|
||||||
import InventoryItemsQuantitySync from 'services/Inventory/InventoryItemsQuantitySync';
|
import InventoryItemsQuantitySync from 'services/Inventory/InventoryItemsQuantitySync';
|
||||||
import { InventoryTransaction } from 'models';
|
import InventoryService from 'services/Inventory/Inventory';
|
||||||
|
|
||||||
@EventSubscriber()
|
@EventSubscriber()
|
||||||
export class InventorySubscriber {
|
export class InventorySubscriber {
|
||||||
depends: number = 0;
|
depends: number = 0;
|
||||||
startingDate: Date;
|
startingDate: Date;
|
||||||
saleInvoicesCost: SaleInvoicesCost;
|
saleInvoicesCost: SaleInvoicesCost;
|
||||||
|
tenancy: TenancyService;
|
||||||
itemsQuantitySync: InventoryItemsQuantitySync;
|
itemsQuantitySync: InventoryItemsQuantitySync;
|
||||||
|
inventoryService: InventoryService;
|
||||||
agenda: any;
|
agenda: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
this.saleInvoicesCost = Container.get(SaleInvoicesCost);
|
this.saleInvoicesCost = Container.get(SaleInvoicesCost);
|
||||||
this.itemsQuantitySync = Container.get(InventoryItemsQuantitySync);
|
this.itemsQuantitySync = Container.get(InventoryItemsQuantitySync);
|
||||||
|
this.inventoryService = Container.get(InventoryService);
|
||||||
|
this.tenancy = Container.get(TenancyService);
|
||||||
this.agenda = Container.get('agenda');
|
this.agenda = Container.get('agenda');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks items cost compute running state.
|
||||||
|
*/
|
||||||
|
@On(events.inventory.onComputeItemCostJobScheduled)
|
||||||
|
async markGlobalSettingsComputeItems({
|
||||||
|
tenantId
|
||||||
|
}) {
|
||||||
|
await this.inventoryService.markItemsCostComputeRunning(tenantId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks items cost compute as completed.
|
||||||
|
*/
|
||||||
|
@On(events.inventory.onInventoryCostEntriesWritten)
|
||||||
|
async markGlobalSettingsComputeItemsCompeted({
|
||||||
|
tenantId
|
||||||
|
}) {
|
||||||
|
await this.inventoryService.markItemsCostComputeRunning(tenantId, false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle run writing the journal entries once the compute items jobs completed.
|
* Handle run writing the journal entries once the compute items jobs completed.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -190,7 +190,9 @@ export default {
|
|||||||
|
|
||||||
onComputeItemCostJobScheduled: 'onComputeItemCostJobScheduled',
|
onComputeItemCostJobScheduled: 'onComputeItemCostJobScheduled',
|
||||||
onComputeItemCostJobStarted: 'onComputeItemCostJobStarted',
|
onComputeItemCostJobStarted: 'onComputeItemCostJobStarted',
|
||||||
onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted'
|
onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted',
|
||||||
|
|
||||||
|
onInventoryCostEntriesWritten: 'onInventoryCostEntriesWritten'
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ exports.up = function(knex) {
|
|||||||
table.string('name');
|
table.string('name');
|
||||||
table.string('description');
|
table.string('description');
|
||||||
table.decimal('price');
|
table.decimal('price');
|
||||||
table.decimal('signup_fee');
|
|
||||||
table.string('currency', 3);
|
table.string('currency', 3);
|
||||||
|
|
||||||
table.integer('trial_period');
|
table.integer('trial_period');
|
||||||
@@ -14,7 +13,6 @@ exports.up = function(knex) {
|
|||||||
|
|
||||||
table.integer('invoice_period');
|
table.integer('invoice_period');
|
||||||
table.string('invoice_interval');
|
table.string('invoice_interval');
|
||||||
|
|
||||||
table.timestamps();
|
table.timestamps();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,21 +6,42 @@ exports.seed = (knex) => {
|
|||||||
// Inserts seed entries
|
// Inserts seed entries
|
||||||
return knex('subscription_plans').insert([
|
return knex('subscription_plans').insert([
|
||||||
{
|
{
|
||||||
id: 1,
|
name: 'Free',
|
||||||
name: 'free',
|
|
||||||
slug: 'free',
|
slug: 'free',
|
||||||
price: 0,
|
price: 0,
|
||||||
active: true,
|
active: true,
|
||||||
currency: 'LYD',
|
currency: 'LYD',
|
||||||
|
|
||||||
trial_period: 15,
|
trial_period: 7,
|
||||||
trial_interval: 'days',
|
trial_interval: 'days',
|
||||||
|
|
||||||
invoice_period: 3,
|
index: 1,
|
||||||
|
voucher_required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Starter',
|
||||||
|
slug: 'starter',
|
||||||
|
price: 500,
|
||||||
|
active: true,
|
||||||
|
currency: 'LYD',
|
||||||
|
|
||||||
|
invoice_period: 12,
|
||||||
invoice_interval: 'month',
|
invoice_interval: 'month',
|
||||||
|
|
||||||
index: 1,
|
index: 2,
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name: 'Growth',
|
||||||
|
slug: 'growth',
|
||||||
|
price: 1000,
|
||||||
|
active: true,
|
||||||
|
currency: 'LYD',
|
||||||
|
|
||||||
|
invoice_period: 12,
|
||||||
|
invoice_interval: 'month',
|
||||||
|
|
||||||
|
index: 3,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user