mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 04:10:32 +00:00
fix: database migrations FK relations.
fix: database columns indexing.
This commit is contained in:
@@ -20,7 +20,7 @@ TENANT_DB_NAME_PERFIX=bigcapital_tenant_
|
||||
TENANT_DB_HOST=127.0.0.1
|
||||
TENANT_DB_PASSWORD=root
|
||||
TEANNT_DB_USER=root
|
||||
TENANT_DB_CHARSET=charset
|
||||
TENANT_DB_CHARSET=utf8
|
||||
TENANT_MIGRATIONS_DIR=src/database/migrations
|
||||
TENANT_SEEDS_DIR=src/database/seeds/core
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ export default class CurrenciesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
handlerServiceError(error, req, res, next) {
|
||||
handlerServiceError(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'currency_not_found') {
|
||||
return res.boom.badRequest(null, {
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
import ItemCategoriesService from 'services/ItemCategories/ItemCategoriesService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import { IItemCategoryOTD } from 'interfaces';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
@@ -27,42 +26,42 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
...this.categoryValidationSchema,
|
||||
...this.specificCategoryValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.editCategory.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
router.post('/', [
|
||||
...this.categoryValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.newCategory.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
router.delete('/', [
|
||||
...this.categoriesBulkValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.bulkDeleteCategories.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
router.delete('/:id', [
|
||||
...this.specificCategoryValidationSchema
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteItem.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
router.get('/:id', [
|
||||
...this.specificCategoryValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getCategory.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
router.get('/', [
|
||||
...this.categoriesListValidationSchema
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getList.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
|
||||
@@ -23,8 +23,7 @@ export default class ItemsController extends BaseController {
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
'/', [
|
||||
router.post('/', [
|
||||
...this.validateItemSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { check, param, query, matchedData } from 'express-validator';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { difference } from 'lodash';
|
||||
import { BillOTD } from 'interfaces';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import BillsService from 'services/Purchases/Bills';
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
@@ -30,7 +29,7 @@ export default class BillsController extends BaseController {
|
||||
router.post(
|
||||
'/',
|
||||
[...this.billValidationSchema],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateVendorExistance.bind(this)),
|
||||
asyncMiddleware(this.validateItemsIds.bind(this)),
|
||||
asyncMiddleware(this.validateBillNumberExists.bind(this)),
|
||||
@@ -40,7 +39,7 @@ export default class BillsController extends BaseController {
|
||||
router.post(
|
||||
'/:id',
|
||||
[...this.billValidationSchema, ...this.specificBillValidationSchema],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillExistance.bind(this)),
|
||||
asyncMiddleware(this.validateVendorExistance.bind(this)),
|
||||
asyncMiddleware(this.validateItemsIds.bind(this)),
|
||||
@@ -51,20 +50,20 @@ export default class BillsController extends BaseController {
|
||||
router.get(
|
||||
'/:id',
|
||||
[...this.specificBillValidationSchema],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillExistance.bind(this)),
|
||||
asyncMiddleware(this.getBill.bind(this))
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
[...this.billsListingValidationSchema],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.listingBills.bind(this))
|
||||
);
|
||||
router.delete(
|
||||
'/:id',
|
||||
[...this.specificBillValidationSchema],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillExistance.bind(this)),
|
||||
asyncMiddleware(this.deleteBill.bind(this))
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@ export default class BillsPayments extends BaseController {
|
||||
router.post('/', [
|
||||
...this.billPaymentSchemaValidation,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillPaymentVendorExistance.bind(this)),
|
||||
asyncMiddleware(this.validatePaymentAccount.bind(this)),
|
||||
asyncMiddleware(this.validatePaymentNumber.bind(this)),
|
||||
@@ -42,7 +42,7 @@ export default class BillsPayments extends BaseController {
|
||||
...this.billPaymentSchemaValidation,
|
||||
...this.specificBillPaymentValidateSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillPaymentVendorExistance.bind(this)),
|
||||
asyncMiddleware(this.validatePaymentAccount.bind(this)),
|
||||
asyncMiddleware(this.validatePaymentNumber.bind(this)),
|
||||
@@ -53,19 +53,19 @@ export default class BillsPayments extends BaseController {
|
||||
)
|
||||
router.delete('/:id',
|
||||
this.specificBillPaymentValidateSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillPaymentExistance.bind(this)),
|
||||
asyncMiddleware(this.deleteBillPayment.bind(this)),
|
||||
);
|
||||
router.get('/:id',
|
||||
this.specificBillPaymentValidateSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateBillPaymentExistance.bind(this)),
|
||||
asyncMiddleware(this.getBillPayment.bind(this)),
|
||||
);
|
||||
router.get('/',
|
||||
this.listingValidationSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getBillsPayments.bind(this))
|
||||
);
|
||||
return router;
|
||||
|
||||
@@ -4,7 +4,6 @@ import { difference } from 'lodash';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IPaymentReceive, IPaymentReceiveOTD } from 'interfaces';
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import PaymentReceiveService from 'services/Sales/PaymentsReceives';
|
||||
import SaleInvoiceService from 'services/Sales/SalesInvoices';
|
||||
@@ -34,7 +33,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
router.post(
|
||||
'/:id',
|
||||
this.editPaymentReceiveValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validatePaymentReceiveExistance.bind(this)),
|
||||
asyncMiddleware(this.validatePaymentReceiveNoExistance.bind(this)),
|
||||
asyncMiddleware(this.validateCustomerExistance.bind(this)),
|
||||
@@ -47,7 +46,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
router.post(
|
||||
'/',
|
||||
this.newPaymentReceiveValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validatePaymentReceiveNoExistance.bind(this)),
|
||||
asyncMiddleware(this.validateCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateDepositAccount.bind(this)),
|
||||
@@ -58,20 +57,20 @@ export default class PaymentReceivesController extends BaseController {
|
||||
router.get(
|
||||
'/:id',
|
||||
this.paymentReceiveValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validatePaymentReceiveExistance.bind(this)),
|
||||
asyncMiddleware(this.getPaymentReceive.bind(this))
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
this.validatePaymentReceiveList,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getPaymentReceiveList.bind(this)),
|
||||
);
|
||||
router.delete(
|
||||
'/:id',
|
||||
this.paymentReceiveValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validatePaymentReceiveExistance.bind(this)),
|
||||
asyncMiddleware(this.deletePaymentReceive.bind(this)),
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { check, param, query, matchedData } from 'express-validator';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ISaleEstimate, ISaleEstimateOTD } from 'interfaces';
|
||||
import BaseController from 'api/controllers/BaseController'
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||
import ItemsService from 'services/Items/ItemsService';
|
||||
@@ -25,7 +24,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
router.post(
|
||||
'/',
|
||||
this.estimateValidationSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateEstimateCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateEstimateNumberExistance.bind(this)),
|
||||
asyncMiddleware(this.validateEstimateEntriesItemsExistance.bind(this)),
|
||||
@@ -36,7 +35,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
...this.validateSpecificEstimateSchema,
|
||||
...this.estimateValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateEstimateIdExistance.bind(this)),
|
||||
asyncMiddleware(this.validateEstimateCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateEstimateNumberExistance.bind(this)),
|
||||
@@ -48,21 +47,21 @@ export default class SalesEstimatesController extends BaseController {
|
||||
'/:id', [
|
||||
this.validateSpecificEstimateSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateEstimateIdExistance.bind(this)),
|
||||
asyncMiddleware(this.deleteEstimate.bind(this))
|
||||
);
|
||||
router.get(
|
||||
'/:id',
|
||||
this.validateSpecificEstimateSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateEstimateIdExistance.bind(this)),
|
||||
asyncMiddleware(this.getEstimate.bind(this))
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
this.validateEstimateListSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getEstimates.bind(this))
|
||||
);
|
||||
return router;
|
||||
|
||||
@@ -3,14 +3,14 @@ import { check, param, query, matchedData } from 'express-validator';
|
||||
import { difference } from 'lodash';
|
||||
import { raw } from 'objection';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import BaseController from '../BaseController';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import SaleInvoiceService from 'services/Sales/SalesInvoices';
|
||||
import ItemsService from 'services/Items/ItemsService';
|
||||
import { ISaleInvoiceOTD } from 'interfaces';
|
||||
|
||||
@Service()
|
||||
export default class SaleInvoicesController {
|
||||
export default class SaleInvoicesController extends BaseController{
|
||||
@Inject()
|
||||
itemsService: ItemsService;
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class SaleInvoicesController {
|
||||
router.post(
|
||||
'/',
|
||||
this.saleInvoiceValidationSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateInvoiceCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateInvoiceNumberUnique.bind(this)),
|
||||
asyncMiddleware(this.validateInvoiceItemsIdsExistance.bind(this)),
|
||||
@@ -39,7 +39,7 @@ export default class SaleInvoicesController {
|
||||
...this.saleInvoiceValidationSchema,
|
||||
...this.specificSaleInvoiceValidation,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateInvoiceExistance.bind(this)),
|
||||
asyncMiddleware(this.validateInvoiceCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateInvoiceNumberUnique.bind(this)),
|
||||
@@ -52,7 +52,7 @@ export default class SaleInvoicesController {
|
||||
router.delete(
|
||||
'/:id',
|
||||
this.specificSaleInvoiceValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateInvoiceExistance.bind(this)),
|
||||
asyncMiddleware(this.deleteSaleInvoice.bind(this))
|
||||
);
|
||||
@@ -64,13 +64,14 @@ export default class SaleInvoicesController {
|
||||
router.get(
|
||||
'/:id',
|
||||
this.specificSaleInvoiceValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateInvoiceExistance.bind(this)),
|
||||
asyncMiddleware(this.getSaleInvoice.bind(this))
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
this.saleInvoiceListValidationSchema,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getSalesInvoices.bind(this))
|
||||
)
|
||||
return router;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { check, param, query, matchedData } from 'express-validator';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import AccountsService from 'services/Accounts/AccountsService';
|
||||
import ItemsService from 'services/Items/ItemsService';
|
||||
import SaleReceiptService from 'services/Sales/SalesReceipts';
|
||||
import BaseController from '../BaseController';
|
||||
|
||||
@Service()
|
||||
export default class SalesReceiptsController {
|
||||
export default class SalesReceiptsController extends BaseController{
|
||||
@Inject()
|
||||
saleReceiptService: SaleReceiptService;
|
||||
|
||||
@@ -29,7 +29,7 @@ export default class SalesReceiptsController {
|
||||
...this.specificReceiptValidationSchema,
|
||||
...this.salesReceiptsValidationSchema,
|
||||
],
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateSaleReceiptExistance.bind(this)),
|
||||
asyncMiddleware(this.validateReceiptCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateReceiptDepositAccountExistance.bind(this)),
|
||||
@@ -40,7 +40,7 @@ export default class SalesReceiptsController {
|
||||
router.post(
|
||||
'/',
|
||||
this.salesReceiptsValidationSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateReceiptCustomerExistance.bind(this)),
|
||||
asyncMiddleware(this.validateReceiptDepositAccountExistance.bind(this)),
|
||||
asyncMiddleware(this.validateReceiptItemsIdsExistance.bind(this)),
|
||||
@@ -49,14 +49,14 @@ export default class SalesReceiptsController {
|
||||
router.delete(
|
||||
'/:id',
|
||||
this.specificReceiptValidationSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateSaleReceiptExistance.bind(this)),
|
||||
asyncMiddleware(this.deleteSaleReceipt.bind(this))
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
this.listSalesReceiptsValidationSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.listingSalesReceipts.bind(this))
|
||||
);
|
||||
return router;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Service } from 'typedi';
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { body, query } from 'express-validator';
|
||||
import { pick } from 'lodash';
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
isDefinedOptionConfigurable,
|
||||
} from 'utils';
|
||||
|
||||
@Service()
|
||||
export default class SettingsController extends BaseController{
|
||||
/**
|
||||
* Router constructor.
|
||||
|
||||
@@ -6,7 +6,6 @@ import config from 'config';
|
||||
import { License, Plan } from 'system/models';
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
import LicenseService from 'services/Payment/License';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import { ILicensesFilter } from 'interfaces';
|
||||
|
||||
@@ -31,13 +30,13 @@ export default class LicensesController extends BaseController {
|
||||
router.post(
|
||||
'/generate',
|
||||
this.generateLicenseSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validatePlanExistance.bind(this)),
|
||||
asyncMiddleware(this.generateLicense.bind(this)),
|
||||
);
|
||||
router.post(
|
||||
'/disable/:licenseId',
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validateLicenseExistance.bind(this)),
|
||||
asyncMiddleware(this.validateNotDisabledLicense.bind(this)),
|
||||
asyncMiddleware(this.disableLicense.bind(this)),
|
||||
@@ -45,7 +44,7 @@ export default class LicensesController extends BaseController {
|
||||
router.post(
|
||||
'/send',
|
||||
this.sendLicenseSchemaValidation,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.sendLicense.bind(this)),
|
||||
);
|
||||
router.delete(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { check } from 'express-validator';
|
||||
import validateMiddleware from 'api/middleware/validateMiddleware';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import PaymentMethodController from 'api/controllers/Subscription/PaymentMethod';
|
||||
import {
|
||||
@@ -26,7 +25,7 @@ export default class PaymentViaLicenseController extends PaymentMethodController
|
||||
router.post(
|
||||
'/payment',
|
||||
this.paymentViaLicenseSchema,
|
||||
validateMiddleware,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.validatePlanSlugExistance.bind(this)),
|
||||
asyncMiddleware(this.paymentViaLicense.bind(this)),
|
||||
);
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { validationResult } from 'express-validator';
|
||||
|
||||
export default (req, res, next) => {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error',
|
||||
...validationErrors,
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('users', (table) => {
|
||||
table.increments();
|
||||
table.string('first_name');
|
||||
table.string('last_name');
|
||||
table.string('email').unique();
|
||||
table.string('phone_number').unique();
|
||||
table.boolean('active');
|
||||
table.integer('role_id').unique();
|
||||
table.string('language');
|
||||
table.date('last_login_at');
|
||||
|
||||
table.date('invite_accepted_at');
|
||||
table.timestamps();
|
||||
}).raw('ALTER TABLE `USERS` AUTO_INCREMENT = 1000');;
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.dropTableIfExists('users');
|
||||
};
|
||||
@@ -3,9 +3,9 @@ exports.up = (knex) => {
|
||||
return knex.schema.createTable('account_types', (table) => {
|
||||
table.increments();
|
||||
table.string('name');
|
||||
table.string('key');
|
||||
table.string('normal');
|
||||
table.string('root_type');
|
||||
table.string('key').index();
|
||||
table.string('normal').index();
|
||||
table.string('root_type').index();
|
||||
table.string('child_type');
|
||||
table.boolean('balance_sheet');
|
||||
table.boolean('income_sheet');
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('accounts', (table) => {
|
||||
table.increments('id').comment('Auto-generated id');;
|
||||
table.string('name').index();
|
||||
table.string('slug');
|
||||
table.integer('account_type_id').unsigned().references('id').inTable('account_types');
|
||||
table.integer('parent_account_id').unsigned().references('id').inTable('accounts');
|
||||
table.string('code', 10).index();
|
||||
table.text('description');
|
||||
table.boolean('active').defaultTo(true).index();
|
||||
table.integer('index').unsigned();
|
||||
table.boolean('predefined').defaultTo(false).index();
|
||||
table.decimal('amount', 15, 5);
|
||||
table.string('currency_code', 3).index();
|
||||
table.timestamps();
|
||||
}).raw('ALTER TABLE `ACCOUNTS` AUTO_INCREMENT = 1000');
|
||||
};
|
||||
|
||||
exports.down = (knex) => knex.schema.dropTableIfExists('accounts');
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('items', (table) => {
|
||||
table.increments();
|
||||
table.string('name');
|
||||
table.string('type');
|
||||
table.string('sku');
|
||||
table.boolean('sellable');
|
||||
table.boolean('purchasable');
|
||||
table.decimal('sell_price', 13, 3).unsigned();
|
||||
table.decimal('cost_price', 13, 3).unsigned();
|
||||
table.string('currency_code', 3);
|
||||
table.string('picture_uri');
|
||||
table.integer('cost_account_id').unsigned();
|
||||
table.integer('sell_account_id').unsigned();
|
||||
table.integer('inventory_account_id').unsigned();
|
||||
table.text('sell_description').nullable();
|
||||
table.text('purchase_description').nullable();
|
||||
table.integer('quantity_on_hand');
|
||||
table.text('note').nullable();
|
||||
table.integer('category_id').unsigned();
|
||||
table.integer('user_id').unsigned();
|
||||
table.timestamps();
|
||||
}).raw('ALTER TABLE `ITEMS` AUTO_INCREMENT = 1000');;
|
||||
};
|
||||
|
||||
exports.down = (knex) => knex.schema.dropTableIfExists('items');
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('accounts', (table) => {
|
||||
table.bigIncrements('id').comment('Auto-generated id');;
|
||||
table.string('name');
|
||||
table.string('slug');
|
||||
table.integer('account_type_id').unsigned();
|
||||
table.integer('parent_account_id').unsigned();
|
||||
table.string('code', 10);
|
||||
table.text('description');
|
||||
table.boolean('active').defaultTo(true);
|
||||
table.integer('index').unsigned();
|
||||
table.boolean('predefined').defaultTo(false);
|
||||
table.decimal('amount', 15, 5);
|
||||
table.string('currency_code', 3);
|
||||
table.timestamps();
|
||||
}).raw('ALTER TABLE `ACCOUNTS` AUTO_INCREMENT = 1000');
|
||||
};
|
||||
|
||||
exports.down = (knex) => knex.schema.dropTableIfExists('accounts');
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('items_categories', (table) => {
|
||||
table.increments();
|
||||
table.string('name').index();
|
||||
table.integer('parent_category_id').unsigned().references('id').inTable('items_categories');
|
||||
table.text('description');
|
||||
table.integer('user_id').unsigned().index();
|
||||
|
||||
table.integer('cost_account_id').unsigned().references('id').inTable('accounts');
|
||||
table.integer('sell_account_id').unsigned().references('id').inTable('accounts');
|
||||
table.integer('inventory_account_id').unsigned().references('id').inTable('accounts');
|
||||
|
||||
table.string('cost_method');
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = (knex) => knex.schema.dropTableIfExists('items_categories');
|
||||
@@ -1,19 +0,0 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('items_categories', (table) => {
|
||||
table.increments();
|
||||
table.string('name');
|
||||
table.integer('parent_category_id').unsigned();
|
||||
table.text('description');
|
||||
table.integer('user_id').unsigned();
|
||||
|
||||
table.integer('cost_account_id').unsigned();
|
||||
table.integer('sell_account_id').unsigned();
|
||||
table.integer('inventory_account_id').unsigned();
|
||||
|
||||
table.string('cost_method');
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = (knex) => knex.schema.dropTableIfExists('items_categories');
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('items', (table) => {
|
||||
table.increments();
|
||||
table.string('name').index();
|
||||
table.string('type').index();
|
||||
table.string('sku');
|
||||
table.boolean('sellable').index();
|
||||
table.boolean('purchasable').index();
|
||||
table.decimal('sell_price', 13, 3).unsigned();
|
||||
table.decimal('cost_price', 13, 3).unsigned();
|
||||
table.string('currency_code', 3);
|
||||
table.string('picture_uri');
|
||||
table.integer('cost_account_id').nullable().unsigned().references('id').inTable('accounts');
|
||||
table.integer('sell_account_id').nullable().unsigned().references('id').inTable('accounts');
|
||||
table.integer('inventory_account_id').unsigned().references('id').inTable('accounts');
|
||||
table.text('sell_description').nullable();
|
||||
table.text('purchase_description').nullable();
|
||||
table.integer('quantity_on_hand');
|
||||
table.text('note').nullable();
|
||||
table.integer('category_id').unsigned().index().references('id').inTable('items_categories');
|
||||
table.integer('user_id').unsigned().index();
|
||||
table.timestamps();
|
||||
}).raw('ALTER TABLE `ITEMS` AUTO_INCREMENT = 1000');
|
||||
};
|
||||
|
||||
exports.down = (knex) => knex.schema.dropTableIfExists('items');
|
||||
@@ -2,9 +2,9 @@
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('views', (table) => {
|
||||
table.increments();
|
||||
table.string('name');
|
||||
table.string('name').index();
|
||||
table.boolean('predefined');
|
||||
table.string('resource_model');
|
||||
table.string('resource_model').index();
|
||||
table.boolean('favourite');
|
||||
table.string('roles_logic_expression');
|
||||
table.timestamps();
|
||||
@@ -2,10 +2,10 @@
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('settings', (table) => {
|
||||
table.increments();
|
||||
table.integer('user_id').unsigned();
|
||||
table.string('group');
|
||||
table.integer('user_id').unsigned().index();
|
||||
table.string('group').index();
|
||||
table.string('type');
|
||||
table.string('key');
|
||||
table.string('key').index();
|
||||
table.string('value');
|
||||
}).raw('ALTER TABLE `SETTINGS` AUTO_INCREMENT = 2000');
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('view_has_columns', (table) => {
|
||||
table.increments();
|
||||
table.integer('view_id').unsigned();
|
||||
table.integer('view_id').unsigned().index().references('id').inTable('views');
|
||||
table.string('field_key');
|
||||
table.integer('index').unsigned();
|
||||
}).raw('ALTER TABLE `ITEMS_CATEGORIES` AUTO_INCREMENT = 1000');
|
||||
|
||||
@@ -3,10 +3,10 @@ exports.up = function (knex) {
|
||||
return knex.schema.createTable('view_roles', (table) => {
|
||||
table.increments();
|
||||
table.integer('index');
|
||||
table.string('field_key');
|
||||
table.string('field_key').index();
|
||||
table.string('comparator');
|
||||
table.string('value');
|
||||
table.integer('view_id').unsigned();
|
||||
table.integer('view_id').unsigned().index().references('id').inTable('views');
|
||||
}).raw('ALTER TABLE `VIEW_ROLES` AUTO_INCREMENT = 1000');
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('contacts', table => {
|
||||
table.increments();
|
||||
|
||||
table.string('contact_service');
|
||||
table.string('contact_type');
|
||||
|
||||
table.decimal('balance', 13, 3).defaultTo(0);
|
||||
table.decimal('opening_balance', 13, 3).defaultTo(0);
|
||||
|
||||
table.string('first_name').nullable();
|
||||
table.string('last_name').nullable();
|
||||
table.string('company_name').nullable();
|
||||
|
||||
table.string('display_name');
|
||||
|
||||
table.string('email').nullable();
|
||||
table.string('work_phone').nullable();
|
||||
table.string('personal_phone').nullable();
|
||||
|
||||
table.string('billing_address_1').nullable();
|
||||
table.string('billing_address_2').nullable();
|
||||
table.string('billing_address_city').nullable();
|
||||
table.string('billing_address_country').nullable();
|
||||
table.string('billing_address_email').nullable();
|
||||
table.string('billing_address_zipcode').nullable();
|
||||
table.string('billing_address_phone').nullable();
|
||||
table.string('billing_address_state').nullable(),
|
||||
|
||||
table.string('shipping_address_1').nullable();
|
||||
table.string('shipping_address_2').nullable();
|
||||
table.string('shipping_address_city').nullable();
|
||||
table.string('shipping_address_country').nullable();
|
||||
table.string('shipping_address_email').nullable();
|
||||
table.string('shipping_address_zipcode').nullable();
|
||||
table.string('shipping_address_phone').nullable();
|
||||
table.string('shipping_address_state').nullable();
|
||||
|
||||
table.text('note');
|
||||
table.boolean('active').defaultTo(true);
|
||||
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.dropTableIfExists('contacts');
|
||||
};
|
||||
@@ -4,17 +4,17 @@ exports.up = function(knex) {
|
||||
table.increments();
|
||||
table.decimal('credit', 13, 3);
|
||||
table.decimal('debit', 13, 3);
|
||||
table.string('transaction_type');
|
||||
table.string('reference_type');
|
||||
table.integer('reference_id');
|
||||
table.integer('account_id').unsigned();
|
||||
table.string('contact_type').nullable();
|
||||
table.integer('contact_id').unsigned().nullable();
|
||||
table.string('transaction_type').index();
|
||||
table.string('reference_type').index();
|
||||
table.integer('reference_id').index();
|
||||
table.integer('account_id').unsigned().index().references('id').inTable('accounts');
|
||||
table.string('contact_type').nullable().index();
|
||||
table.integer('contact_id').unsigned().nullable().index().references('id').inTable('contacts');
|
||||
table.string('note');
|
||||
table.boolean('draft').defaultTo(false);
|
||||
table.integer('user_id').unsigned();
|
||||
table.integer('user_id').unsigned().index();
|
||||
table.integer('index').unsigned();
|
||||
table.date('date');
|
||||
table.date('date').index();
|
||||
table.timestamps();
|
||||
}).raw('ALTER TABLE `ACCOUNTS_TRANSACTIONS` AUTO_INCREMENT = 1000');
|
||||
};
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('options', (table) => {
|
||||
table.increments();
|
||||
table.string('key');
|
||||
table.string('value');
|
||||
table.string('group');
|
||||
table.string('type');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.dropTableIfExists('options');
|
||||
};
|
||||
@@ -5,12 +5,12 @@ exports.up = function(knex) {
|
||||
table.decimal('total_amount', 13, 3);
|
||||
table.string('currency_code', 3);
|
||||
table.text('description');
|
||||
table.integer('payment_account_id').unsigned();
|
||||
table.integer('payee_id').unsigned();
|
||||
table.integer('payment_account_id').unsigned().references('id').inTable('accounts');
|
||||
table.integer('payee_id').unsigned().references('id').inTable('contacts');;
|
||||
table.string('reference_no');
|
||||
table.date('published_at');
|
||||
table.integer('user_id').unsigned();
|
||||
table.date('payment_date');
|
||||
table.date('published_at').index();
|
||||
table.integer('user_id').unsigned().index();
|
||||
table.date('payment_date').index();
|
||||
table.timestamps();
|
||||
}).raw('ALTER TABLE `EXPENSES_TRANSACTIONS` AUTO_INCREMENT = 1000');
|
||||
};
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('manual_journals', (table) => {
|
||||
table.increments();
|
||||
table.string('journal_number');
|
||||
table.string('reference');
|
||||
table.string('journal_type');
|
||||
table.string('journal_number').index();
|
||||
table.string('reference').index();
|
||||
table.string('journal_type').index();
|
||||
table.decimal('amount', 13, 3);
|
||||
table.date('date');
|
||||
table.boolean('status').defaultTo(false);
|
||||
table.date('date').index();
|
||||
table.boolean('status').defaultTo(false).index();
|
||||
table.string('description');
|
||||
table.string('attachment_file');
|
||||
table.integer('user_id').unsigned();
|
||||
table.integer('user_id').unsigned().index();
|
||||
table.timestamps();
|
||||
}).raw('ALTER TABLE `MANUAL_JOURNALS` AUTO_INCREMENT = 1000');
|
||||
};
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('currencies', table => {
|
||||
table.increments();
|
||||
table.string('currency_name');
|
||||
table.string('currency_code', 4);
|
||||
table.string('currency_name').index();
|
||||
table.string('currency_code', 4).index();
|
||||
table.timestamps();
|
||||
}).raw('ALTER TABLE `CURRENCIES` AUTO_INCREMENT = 1000');
|
||||
};
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('exchange_rates', table => {
|
||||
table.increments();
|
||||
table.string('currency_code', 4);
|
||||
table.string('currency_code', 4).index();
|
||||
table.decimal('exchange_rate');
|
||||
table.date('date');
|
||||
table.date('date').index();
|
||||
table.timestamps();
|
||||
}).raw('ALTER TABLE `EXCHANGE_RATES` AUTO_INCREMENT = 1000');
|
||||
};
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('media_links', table => {
|
||||
table.increments();
|
||||
table.string('model_name');
|
||||
table.integer('media_id').unsigned();
|
||||
table.integer('model_id').unsigned();
|
||||
table.string('model_name').index();
|
||||
table.integer('media_id').unsigned().index();
|
||||
table.integer('model_id').unsigned().index();
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('expense_transaction_categories', table => {
|
||||
table.increments();
|
||||
table.integer('expense_account_id').unsigned();
|
||||
table.integer('expense_account_id').unsigned().index().references('id').inTable('accounts');
|
||||
table.integer('index').unsigned();
|
||||
table.text('description');
|
||||
table.decimal('amount', 13, 3);
|
||||
table.integer('expense_id').unsigned();
|
||||
table.integer('expense_id').unsigned().index().references('id').inTable('expenses_transactions');
|
||||
table.timestamps();
|
||||
}).raw('ALTER TABLE `EXPENSE_TRANSACTION_CATEGORIES` AUTO_INCREMENT = 1000');;
|
||||
};
|
||||
|
||||
@@ -3,15 +3,15 @@ exports.up = function(knex) {
|
||||
return knex.schema.createTable('sales_estimates', (table) => {
|
||||
table.increments();
|
||||
table.decimal('amount', 13, 3);
|
||||
table.integer('customer_id').unsigned();
|
||||
table.date('estimate_date');
|
||||
table.date('expiration_date');
|
||||
table.integer('customer_id').unsigned().index().references('id').inTable('contacts');
|
||||
table.date('estimate_date').index();
|
||||
table.date('expiration_date').index();
|
||||
table.string('reference');
|
||||
table.string('estimate_number');
|
||||
table.string('estimate_number').index();
|
||||
table.text('note');
|
||||
table.text('terms_conditions');
|
||||
|
||||
table.integer('user_id').unsigned();
|
||||
table.integer('user_id').unsigned().index();
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,9 +3,9 @@ exports.up = function(knex) {
|
||||
return knex.schema.createTable('sales_receipts', table => {
|
||||
table.increments();
|
||||
table.decimal('amount', 13, 3);
|
||||
table.integer('deposit_account_id').unsigned();
|
||||
table.integer('customer_id').unsigned();
|
||||
table.date('receipt_date');
|
||||
table.integer('deposit_account_id').unsigned().index().references('id').inTable('accounts');
|
||||
table.integer('customer_id').unsigned().index().references('id').inTable('contacts');
|
||||
table.date('receipt_date').index();
|
||||
table.string('reference_no');
|
||||
table.string('email_send_to');
|
||||
table.text('receipt_message');
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('sales_invoices', table => {
|
||||
table.increments();
|
||||
table.integer('customer_id');
|
||||
table.date('invoice_date');
|
||||
table.integer('customer_id').unsigned().index().references('id').inTable('contacts')
|
||||
table.date('invoice_date').index();
|
||||
table.date('due_date');
|
||||
table.string('invoice_no');
|
||||
table.string('invoice_no').index();
|
||||
table.string('reference_no');
|
||||
table.string('status');
|
||||
table.string('status').index();
|
||||
|
||||
table.text('invoice_message');
|
||||
table.text('terms_conditions');
|
||||
@@ -15,7 +15,7 @@ exports.up = function(knex) {
|
||||
table.decimal('balance', 13, 3);
|
||||
table.decimal('payment_amount', 13, 3);
|
||||
|
||||
table.string('inv_lot_number');
|
||||
table.string('inv_lot_number').index();
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,14 +3,14 @@ const { knexSnakeCaseMappers } = require("objection");
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('payment_receives', (table) => {
|
||||
table.increments();
|
||||
table.integer('customer_id').unsigned();
|
||||
table.date('payment_date');
|
||||
table.integer('customer_id').unsigned().index().references('id').inTable('contacts');
|
||||
table.date('payment_date').index();
|
||||
table.decimal('amount', 13, 3).defaultTo(0);
|
||||
table.string('reference_no');
|
||||
table.integer('deposit_account_id').unsigned();
|
||||
table.string('reference_no').index();
|
||||
table.integer('deposit_account_id').unsigned().references('id').inTable('accounts');
|
||||
table.string('payment_receive_no');
|
||||
table.text('description');
|
||||
table.integer('user_id').unsigned();
|
||||
table.integer('user_id').unsigned().index();
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('payment_receives_entries', table => {
|
||||
table.increments();
|
||||
table.integer('payment_receive_id').unsigned();
|
||||
table.integer('invoice_id').unsigned();
|
||||
table.integer('payment_receive_id').unsigned().index().references('id').inTable('payment_receives');
|
||||
table.integer('invoice_id').unsigned().index().references('id').inTable('sales_invoices');
|
||||
table.decimal('payment_amount').unsigned();
|
||||
})
|
||||
};
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('bills', (table) => {
|
||||
table.increments();
|
||||
table.integer('vendor_id').unsigned();
|
||||
table.integer('vendor_id').unsigned().index().references('id').inTable('contacts');
|
||||
table.string('bill_number');
|
||||
table.date('bill_date');
|
||||
table.date('due_date');
|
||||
table.date('bill_date').index();
|
||||
table.date('due_date').index();
|
||||
table.string('reference_no');
|
||||
table.string('status');
|
||||
table.string('status').index();
|
||||
table.text('note');
|
||||
|
||||
table.decimal('amount', 13, 3).defaultTo(0);
|
||||
table.decimal('payment_amount', 13, 3).defaultTo(0);
|
||||
|
||||
table.string('inv_lot_number');
|
||||
table.string('inv_lot_number').index();
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('bills_payments', table => {
|
||||
table.increments();
|
||||
table.integer('vendor_id').unsigned();
|
||||
table.integer('vendor_id').unsigned().index().references('id').inTable('contacts');
|
||||
table.decimal('amount', 13, 3).defaultTo(0);
|
||||
table.integer('payment_account_id');
|
||||
table.string('payment_number');
|
||||
table.date('payment_date');
|
||||
table.integer('payment_account_id').unsigned().references('id').inTable('accounts');
|
||||
table.string('payment_number').index();
|
||||
table.date('payment_date').index();
|
||||
table.string('payment_method');
|
||||
table.string('reference');
|
||||
table.integer('user_id').unsigned();
|
||||
table.integer('user_id').unsigned().index();
|
||||
table.text('description');
|
||||
table.timestamps();
|
||||
});
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('inventory_transactions', table => {
|
||||
table.increments('id');
|
||||
table.date('date');
|
||||
table.date('date').index();
|
||||
|
||||
table.string('direction');
|
||||
table.string('direction').index();
|
||||
|
||||
table.integer('item_id').unsigned();
|
||||
table.integer('item_id').unsigned().index().references('id').inTable('items');
|
||||
table.integer('quantity').unsigned();
|
||||
table.decimal('rate', 13, 3).unsigned();
|
||||
|
||||
table.integer('lot_number');
|
||||
table.integer('lot_number').index();
|
||||
|
||||
table.string('transaction_type');
|
||||
table.integer('transaction_id').unsigned();
|
||||
table.string('transaction_type').index();
|
||||
table.integer('transaction_id').unsigned().index();
|
||||
|
||||
table.integer('entry_id').unsigned();
|
||||
table.integer('entry_id').unsigned().index();
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('items_entries', (table) => {
|
||||
table.increments();
|
||||
table.string('reference_type');
|
||||
table.string('reference_id');
|
||||
table.string('reference_type').index();
|
||||
table.string('reference_id').index();
|
||||
|
||||
table.integer('index').unsigned();
|
||||
table.integer('item_id');
|
||||
table.integer('item_id').unsigned().index().references('id').inTable('items');
|
||||
table.text('description');
|
||||
table.integer('discount').unsigned();
|
||||
table.integer('quantity').unsigned();
|
||||
|
||||
@@ -3,8 +3,8 @@ exports.up = function(knex) {
|
||||
return knex.schema.createTable('bills_payments_entries', table => {
|
||||
table.increments();
|
||||
|
||||
table.integer('bill_payment_id').unsigned();
|
||||
table.integer('bill_id').unsigned();
|
||||
table.integer('bill_payment_id').unsigned().index().references('id').inTable('bills_payments');
|
||||
table.integer('bill_id').unsigned().index();
|
||||
table.decimal('payment_amount', 13, 3).unsigned();
|
||||
})
|
||||
};
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('inventory_cost_lot_tracker', table => {
|
||||
table.increments();
|
||||
table.date('date');
|
||||
table.string('direction');
|
||||
table.date('date').index();
|
||||
table.string('direction').index();
|
||||
|
||||
table.integer('item_id').unsigned();
|
||||
table.integer('quantity').unsigned();
|
||||
table.integer('item_id').unsigned().index();
|
||||
table.integer('quantity').unsigned().index();
|
||||
table.decimal('rate', 13, 3);
|
||||
table.integer('remaining');
|
||||
table.integer('cost');
|
||||
table.integer('lot_number');
|
||||
table.integer('lot_number').index();
|
||||
|
||||
table.string('transaction_type');
|
||||
table.integer('transaction_id').unsigned();
|
||||
table.integer('entry_id').unsigned();
|
||||
table.string('transaction_type').index();
|
||||
table.integer('transaction_id').unsigned().index();
|
||||
table.integer('entry_id').unsigned().index();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export * from './Payment';
|
||||
export * from './SaleInvoice';
|
||||
export * from './PaymentReceive';
|
||||
export * from './SaleEstimate';
|
||||
export * from './Register';
|
||||
export * from './Authentication';
|
||||
export * from './User';
|
||||
export * from './Metable';
|
||||
export * from './Options';
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Container } from 'typedi';
|
||||
|
||||
// Here we import all events.
|
||||
import 'subscribers/authentication';
|
||||
import 'subscribers/organization';
|
||||
import 'subscribers/manualJournals';
|
||||
import 'subscribers/expenses';
|
||||
@@ -10,9 +10,7 @@ import Bill from 'models/Bill';
|
||||
import BillPayment from 'models/BillPayment';
|
||||
import BillPaymentEntry from 'models/BillPaymentEntry';
|
||||
import Currency from 'models/Currency';
|
||||
import Customer from 'models/Customer';
|
||||
import Contact from 'models/Contact';
|
||||
import Vendor from 'models/Vendor';
|
||||
import ExchangeRate from 'models/ExchangeRate';
|
||||
import Expense from 'models/Expense';
|
||||
import ExpenseCategory from 'models/ExpenseCategory';
|
||||
@@ -49,8 +47,6 @@ export default (knex) => {
|
||||
BillPayment,
|
||||
BillPaymentEntry,
|
||||
Currency,
|
||||
Customer,
|
||||
Vendor,
|
||||
ExchangeRate,
|
||||
Expense,
|
||||
ExpenseCategory,
|
||||
|
||||
@@ -36,17 +36,20 @@ export default class Bill extends TenantModel {
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Vendor = require('models/Vendor');
|
||||
const Contact = require('models/Contact');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
|
||||
return {
|
||||
vendor: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Vendor.default,
|
||||
modelClass: Contact.default,
|
||||
join: {
|
||||
from: 'bills.vendorId',
|
||||
to: 'vendors.id',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_type', 'Vendor');
|
||||
}
|
||||
},
|
||||
|
||||
entries: {
|
||||
|
||||
@@ -22,7 +22,7 @@ export default class BillPayment extends TenantModel {
|
||||
static get relationMappings() {
|
||||
const BillPaymentEntry = require('models/BillPaymentEntry');
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const Vendor = require('models/Vendor');
|
||||
const Contact = require('models/Contact');
|
||||
const Account = require('models/Account');
|
||||
|
||||
return {
|
||||
@@ -37,11 +37,14 @@ export default class BillPayment extends TenantModel {
|
||||
|
||||
vendor: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Vendor.default,
|
||||
modelClass: Contact.default,
|
||||
join: {
|
||||
from: 'bills_payments.vendorId',
|
||||
to: 'vendors.id',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_type', 'Vendor');
|
||||
}
|
||||
},
|
||||
|
||||
paymentAccount: {
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class Customer extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'customers';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
filterCustomerIds(query, customerIds) {
|
||||
query.whereIn('id', customerIds);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Change vendor balance.
|
||||
* @param {Integer} customerId
|
||||
* @param {Numeric} amount
|
||||
*/
|
||||
static async changeBalance(customerId, amount) {
|
||||
const changeMethod = (amount > 0) ? 'increment' : 'decrement';
|
||||
|
||||
return this.query()
|
||||
.where('id', customerId)
|
||||
[changeMethod]('balance', Math.abs(amount));
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the given customer balance.
|
||||
* @param {Integer} customerId
|
||||
* @param {Integer} amount
|
||||
*/
|
||||
static async incrementBalance(customerId, amount) {
|
||||
return this.query()
|
||||
.where('id', customerId)
|
||||
.increment('balance', amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement the given customer balance.
|
||||
* @param {integer} customerId -
|
||||
* @param {integer} amount -
|
||||
*/
|
||||
static async decrementBalance(customerId, amount) {
|
||||
await this.query()
|
||||
.where('id', customerId)
|
||||
.decrement('balance', amount);
|
||||
}
|
||||
|
||||
static changeDiffBalance(customerId, oldCustomerId, amount, oldAmount) {
|
||||
const diffAmount = amount - oldAmount;
|
||||
const asyncOpers = [];
|
||||
|
||||
if (customerId != oldCustomerId) {
|
||||
const oldCustomerOper = this.changeBalance(oldCustomerId, (oldAmount * -1));
|
||||
const customerOper = this.changeBalance(customerId, amount);
|
||||
|
||||
asyncOpers.push(customerOper);
|
||||
asyncOpers.push(oldCustomerOper);
|
||||
} else {
|
||||
const balanceChangeOper = this.changeBalance(customerId, diffAmount);
|
||||
asyncOpers.push(balanceChangeOper);
|
||||
}
|
||||
return Promise.all(asyncOpers);
|
||||
}
|
||||
}
|
||||
@@ -22,17 +22,20 @@ export default class PaymentReceive extends TenantModel {
|
||||
static get relationMappings() {
|
||||
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const Customer = require('models/Customer');
|
||||
const Contact = require('models/Contact');
|
||||
const Account = require('models/Account');
|
||||
|
||||
return {
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Customer.default,
|
||||
modelClass: Contact.default,
|
||||
join: {
|
||||
from: 'payment_receives.customerId',
|
||||
to: 'customers.id',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_type', 'Customer');
|
||||
}
|
||||
},
|
||||
|
||||
depositAccount: {
|
||||
|
||||
@@ -21,16 +21,19 @@ export default class SaleEstimate extends TenantModel {
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const Customer = require('models/Customer');
|
||||
const Contact = require('models/Contact');
|
||||
|
||||
return {
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Customer.default,
|
||||
modelClass: Contact.default,
|
||||
join: {
|
||||
from: 'sales_estimates.customerId',
|
||||
to: 'customers.id',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_type', 'Customer');
|
||||
}
|
||||
},
|
||||
|
||||
entries: {
|
||||
|
||||
@@ -57,7 +57,7 @@ export default class SaleInvoice extends TenantModel {
|
||||
static get relationMappings() {
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const Customer = require('models/Customer');
|
||||
const Contact = require('models/Contact');
|
||||
const InventoryCostLotTracker = require('models/InventoryCostLotTracker');
|
||||
|
||||
return {
|
||||
@@ -75,11 +75,14 @@ export default class SaleInvoice extends TenantModel {
|
||||
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Customer.default,
|
||||
modelClass: Contact.default,
|
||||
join: {
|
||||
from: 'sales_invoices.customerId',
|
||||
to: 'customers.id',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_type', 'Customer');
|
||||
}
|
||||
},
|
||||
|
||||
transactions: {
|
||||
|
||||
@@ -20,7 +20,7 @@ export default class SaleReceipt extends TenantModel {
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Customer = require('models/Customer');
|
||||
const Contact = require('models/Contact');
|
||||
const Account = require('models/Account');
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
@@ -28,11 +28,14 @@ export default class SaleReceipt extends TenantModel {
|
||||
return {
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Customer.default,
|
||||
modelClass: Contact.default,
|
||||
join: {
|
||||
from: 'sales_receipts.customerId',
|
||||
to: 'customers.id',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_type', 'Customer');
|
||||
}
|
||||
},
|
||||
|
||||
depositAccount: {
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class Vendor extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'vendors';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the vendor balance.
|
||||
* @param {Integer} customerId
|
||||
* @param {Number} amount
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async changeBalance(vendorId, amount) {
|
||||
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
||||
|
||||
return this.query()
|
||||
.where('id', vendorId)
|
||||
[changeMethod]('balance', Math.abs(amount));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} vendorId - Specific vendor id.
|
||||
* @param {number} oldVendorId - The given old vendor id.
|
||||
* @param {number} amount - The new change amount.
|
||||
* @param {number} oldAmount - The old stored amount.
|
||||
*/
|
||||
static changeDiffBalance(vendorId, oldVendorId, amount, oldAmount) {
|
||||
const diffAmount = (amount - oldAmount);
|
||||
const asyncOpers = [];
|
||||
|
||||
if (vendorId != oldVendorId) {
|
||||
const oldVendorOper = Vendor.changeBalance(
|
||||
oldVendorId,
|
||||
(oldAmount * -1)
|
||||
);
|
||||
const vendorOper = Vendor.changeBalance(
|
||||
vendorId,
|
||||
amount,
|
||||
);
|
||||
asyncOpers.push(vendorOper);
|
||||
asyncOpers.push(oldVendorOper);
|
||||
} else {
|
||||
const balanceChangeOper = Vendor.changeBalance(vendorId, diffAmount);
|
||||
asyncOpers.push(balanceChangeOper);
|
||||
}
|
||||
return Promise.all(asyncOpers);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import Customer from './Customer';
|
||||
import Vendor from './Vendor';
|
||||
import Option from './Option';
|
||||
import SaleEstimate from './SaleEstimate';
|
||||
import SaleEstimateEntry from './SaleEstimateEntry';
|
||||
@@ -22,8 +20,6 @@ import AccountType from './AccountType';
|
||||
import InventoryLotCostTracker from './InventoryCostLotTracker';
|
||||
|
||||
export {
|
||||
Customer,
|
||||
Vendor,
|
||||
SaleEstimate,
|
||||
SaleEstimateEntry,
|
||||
SaleReceipt,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import TenantRepository from 'repositories/TenantRepository';
|
||||
import { IAccount } from 'interfaces';
|
||||
import { Account } from 'models';
|
||||
|
||||
export default class AccountRepository extends TenantRepository {
|
||||
models: any;
|
||||
@@ -57,7 +58,7 @@ export default class AccountRepository extends TenantRepository {
|
||||
|
||||
/**
|
||||
* Retrieve the account by the given id.
|
||||
* @param {number} id - Account id.
|
||||
* @param {number} id - Account id.
|
||||
* @return {IAccount}
|
||||
*/
|
||||
getById(id: number): IAccount {
|
||||
@@ -67,4 +68,63 @@ export default class AccountRepository extends TenantRepository {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts by the given ids.
|
||||
* @param {number[]} ids -
|
||||
* @return {IAccount[]}
|
||||
*/
|
||||
findByIds(accountsIds: number[]) {
|
||||
const { Account } = this.models;
|
||||
return Account.query().whereIn('id', accountsIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the given account.
|
||||
* @param {number} accountId -
|
||||
* @return {void}
|
||||
*/
|
||||
async activate(accountId: number): Promise<void> {
|
||||
const { Account } = this.models;
|
||||
await Account.query().findById(accountId).patch({ active: 1 })
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new accounts to the storage.
|
||||
* @param {IAccount} account
|
||||
*/
|
||||
async insert(account: IAccount): Promise<void> {
|
||||
const { Account } = this.models;
|
||||
await Account.query().insertAndFetch({ ...account });
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates account of the given account.
|
||||
* @param {number} accountId - Account id.
|
||||
* @param {IAccount} account
|
||||
* @return {void}
|
||||
*/
|
||||
async edit(accountId: number, account: IAccount): Promise<void> {
|
||||
const { Account } = this.models;
|
||||
await Account.query().findById(accountId).patch({ ...account });
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given account by id.
|
||||
* @param {number} accountId - Account id.
|
||||
*/
|
||||
async deleteById(accountId: number): Promise<void> {
|
||||
const { Account } = this.models;
|
||||
await Account.query().deleteById(accountId);
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush repository cache.
|
||||
*/
|
||||
flushCache(): void {
|
||||
this.cache.delStartWith('accounts');
|
||||
}
|
||||
}
|
||||
@@ -76,4 +76,11 @@ export default class AccountTypeRepository extends TenantRepository {
|
||||
return AccountType.query().where('root_type', rootType);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush repository cache.
|
||||
*/
|
||||
flushCache() {
|
||||
this.cache.delStartWith('accountType');
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import TenantRepository from 'repositories/TenantRepository';
|
||||
import { IContact } from 'interfaces';
|
||||
import Contact from 'models/Contact';
|
||||
|
||||
export default class ContactRepository extends TenantRepository {
|
||||
cache: any;
|
||||
@@ -17,21 +19,70 @@ export default class ContactRepository extends TenantRepository {
|
||||
this.cache = this.tenancy.cache(tenantId);
|
||||
}
|
||||
|
||||
findById(contactId: number) {
|
||||
/**
|
||||
* Retrieve the given contact model.
|
||||
* @param {number} contactId
|
||||
*/
|
||||
findById(contactId: number): IContact {
|
||||
const { Contact } = this.models;
|
||||
return this.cache.get(`contact.id.${contactId}`, () => {
|
||||
return this.cache.get(`contacts.id.${contactId}`, () => {
|
||||
return Contact.query().findById(contactId);
|
||||
})
|
||||
}
|
||||
|
||||
findByIds(contactIds: number[]) {
|
||||
/**
|
||||
* Retrieve the given contacts model.
|
||||
* @param {number[]} contactIds - Contacts ids.
|
||||
*/
|
||||
findByIds(contactIds: number[]): IContact[] {
|
||||
const { Contact } = this.models;
|
||||
return this.cache.get(`contact.ids.${contactIds.join(',')}`, () => {
|
||||
return this.cache.get(`contacts.ids.${contactIds.join(',')}`, () => {
|
||||
return Contact.query().whereIn('id', contactIds);
|
||||
});
|
||||
}
|
||||
|
||||
insert(contact) {
|
||||
/**
|
||||
* Inserts a new contact model.
|
||||
* @param contact
|
||||
*/
|
||||
async insert(contact) {
|
||||
await Contact.query().insert({ ...contact })
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the contact details.
|
||||
* @param {number} contactId - Contact id.
|
||||
* @param {IContact} contact - Contact input.
|
||||
*/
|
||||
async update(contactId: number, contact: IContact) {
|
||||
await Contact.query().findById(contactId).patch({ ...contact });
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes contact of the given id.
|
||||
* @param {number} contactId -
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async deleteById(contactId: number): Promise<void> {
|
||||
await Contact.query().where('id', contactId).delete();
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes contacts in bulk.
|
||||
* @param {number[]} contactsIds
|
||||
*/
|
||||
async bulkDelete(contactsIds: number[]) {
|
||||
await Contact.query().whereIn('id', contactsIds);
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush contact repository cache.
|
||||
*/
|
||||
flushCache() {
|
||||
this.cache.delStartWith(`contacts`);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Customer } from 'models';
|
||||
|
||||
export default class CustomerRepository {
|
||||
|
||||
static changeDiffBalance(customerId, oldCustomerId, amount, oldAmount) {
|
||||
const diffAmount = amount - oldAmount;
|
||||
const asyncOpers = [];
|
||||
|
||||
if (customerId != oldCustomerId) {
|
||||
const oldCustomerOper = Customer.changeBalance(
|
||||
oldCustomerId,
|
||||
(oldAmount * -1)
|
||||
);
|
||||
const customerOper = Customer.changeBalance(
|
||||
customerId,
|
||||
amount,
|
||||
);
|
||||
asyncOpers.push(customerOper);
|
||||
asyncOpers.push(oldCustomerOper);
|
||||
} else {
|
||||
const balanceChangeOper = Customer.changeBalance(customerId, diffAmount);
|
||||
asyncOpers.push(balanceChangeOper);
|
||||
}
|
||||
return Promise.all(asyncOpers);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export default class CustomerRepository extends TenantRepository {
|
||||
|
||||
/**
|
||||
* Retrieve customer details of the given id.
|
||||
* @param {number} customerId -
|
||||
* @param {number} customerId - Customer id.
|
||||
*/
|
||||
getById(customerId: number) {
|
||||
const { Contact } = this.models;
|
||||
|
||||
@@ -7,6 +7,10 @@ export default class ExpenseRepository extends TenantRepository {
|
||||
repositories: any;
|
||||
cache: any;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {number} tenantId
|
||||
*/
|
||||
constructor(tenantId: number) {
|
||||
super(tenantId);
|
||||
|
||||
@@ -14,38 +18,97 @@ export default class ExpenseRepository extends TenantRepository {
|
||||
this.cache = this.tenancy.cache(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given expense by id.
|
||||
* @param {number} expenseId
|
||||
* @return {Promise<IExpense>}
|
||||
*/
|
||||
getById(expenseId: number) {
|
||||
const { Expense } = this.models;
|
||||
return this.cache.get(`expense.id.${expenseId}`, () => {
|
||||
return Expense.query().findById(expenseId);
|
||||
})
|
||||
}
|
||||
|
||||
create(expense: IExpense) {
|
||||
const { Expense } = this.models;
|
||||
return Expense.query().insert({ ...expense });
|
||||
}
|
||||
|
||||
update(expenseId: number, expense: IExpense) {
|
||||
const { Expense } = this.models;
|
||||
return Expense.query().patchAndFetchById(expenseId, { ...expense });
|
||||
}
|
||||
|
||||
publish(expenseId: number) {
|
||||
const { Expense } = this.models;
|
||||
|
||||
return Expense.query().findById(expenseId).patch({
|
||||
publishedAt: moment().toMySqlDateTime(),
|
||||
return Expense.query().findById(expenseId).withGraphFetched('categories');
|
||||
});
|
||||
}
|
||||
|
||||
delete(expenseId: number) {
|
||||
/**
|
||||
* Inserts a new expense object.
|
||||
* @param {IExpense} expense -
|
||||
*/
|
||||
async create(expense: IExpense): Promise<void> {
|
||||
const { Expense } = this.models;
|
||||
return Expense.query().findById(expenseId).delete();
|
||||
await Expense.query().insert({ ...expense });
|
||||
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
bulkDelete(expensesIds: number[]) {
|
||||
/**
|
||||
* Updates the given expense details.
|
||||
* @param {number} expenseId
|
||||
* @param {IExpense} expense
|
||||
*/
|
||||
async update(expenseId: number, expense: IExpense) {
|
||||
const { Expense } = this.models;
|
||||
return Expense.query().whereIn('id', expensesIds).delete();
|
||||
|
||||
await Expense.query().findById(expenseId).patch({ ...expense });
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish the given expense.
|
||||
* @param {number} expenseId
|
||||
*/
|
||||
async publish(expenseId: number): Promise<void> {
|
||||
const { Expense } = this.models;
|
||||
|
||||
await Expense.query().findById(expenseId).patch({
|
||||
publishedAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given expense.
|
||||
* @param {number} expenseId
|
||||
*/
|
||||
async delete(expenseId: number): Promise<void> {
|
||||
const { Expense } = this.models;
|
||||
|
||||
await Expense.query().where('id', expenseId).delete();
|
||||
await Expense.query().where('expense_id', expenseId).delete();
|
||||
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes expenses in bulk.
|
||||
* @param {number[]} expensesIds
|
||||
*/
|
||||
async bulkDelete(expensesIds: number[]): Promise<void> {
|
||||
const { Expense } = this.models;
|
||||
|
||||
await Expense.query().whereIn('expense_id', expensesIds).delete();
|
||||
await Expense.query().whereIn('id', expensesIds).delete();
|
||||
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes the given expenses in bulk.
|
||||
* @param {number[]} expensesIds
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async bulkPublish(expensesIds: number): Promise<void> {
|
||||
const { Expense } = this.models;
|
||||
await Expense.query().whereIn('id', expensesIds).patch({
|
||||
publishedAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes repository cache.
|
||||
*/
|
||||
flushCache() {
|
||||
this.cache.delStartWith(`expense`);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { IVendor } from "interfaces";
|
||||
import TenantRepository from "./TenantRepository";
|
||||
|
||||
|
||||
@@ -18,7 +19,7 @@ export default class VendorRepository extends TenantRepository {
|
||||
|
||||
/**
|
||||
* Retrieve the bill that associated to the given vendor id.
|
||||
* @param {number} vendorId
|
||||
* @param {number} vendorId - Vendor id.
|
||||
*/
|
||||
getBills(vendorId: number) {
|
||||
const { Bill } = this.models;
|
||||
@@ -29,16 +30,17 @@ export default class VendorRepository extends TenantRepository {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Retrieve all the given vendors.
|
||||
* @param {numner[]} vendorsIds
|
||||
* @return {IVendor}
|
||||
*/
|
||||
vendors(vendorsIds: number[]) {
|
||||
vendors(vendorsIds: number[]): IVendor[] {
|
||||
const { Contact } = this.models;
|
||||
return Contact.query().modifier('vendor').whereIn('id', vendorsIds);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Retrieve vendors with associated bills.
|
||||
* @param {number[]} vendorIds
|
||||
*/
|
||||
vendorsWithBills(vendorIds: number[]) {
|
||||
|
||||
@@ -2,9 +2,12 @@ import { sumBy, chain } from 'lodash';
|
||||
import JournalPoster from "./JournalPoster";
|
||||
import JournalEntry from "./JournalEntry";
|
||||
import { AccountTransaction } from 'models';
|
||||
import { IInventoryTransaction, IManualJournal } from 'interfaces';
|
||||
import AccountsService from '../Accounts/AccountsService';
|
||||
import { IInventoryTransaction, IInventoryTransaction } from '../../interfaces';
|
||||
import {
|
||||
IInventoryTransaction,
|
||||
IManualJournal,
|
||||
IExpense,
|
||||
IExpenseCategory,
|
||||
} from 'interfaces';
|
||||
|
||||
interface IInventoryCostEntity {
|
||||
date: Date,
|
||||
@@ -120,6 +123,36 @@ export default class JournalCommands{
|
||||
this.journal.credit(creditEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes journal entries of expense model object.
|
||||
* @param {IExpense} expense
|
||||
*/
|
||||
expense(expense: IExpense) {
|
||||
const mixinEntry = {
|
||||
referenceType: 'Expense',
|
||||
referenceId: expense.id,
|
||||
date: expense.paymentDate,
|
||||
userId: expense.userId,
|
||||
draft: !expense.publishedAt,
|
||||
};
|
||||
const paymentJournalEntry = new JournalEntry({
|
||||
credit: expense.totalAmount,
|
||||
account: expense.paymentAccountId,
|
||||
...mixinEntry,
|
||||
});
|
||||
this.journal.credit(paymentJournalEntry);
|
||||
|
||||
expense.categories.forEach((category: IExpenseCategory) => {
|
||||
const expenseJournalEntry = new JournalEntry({
|
||||
account: category.expenseAccountId,
|
||||
debit: category.amount,
|
||||
note: category.description,
|
||||
...mixinEntry,
|
||||
});
|
||||
this.journal.debit(expenseJournalEntry);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number|number[]} referenceId
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { difference } from 'lodash';
|
||||
import { kebabCase } from 'lodash'
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import { IAccountDTO, IAccount, IAccountsFilter } from 'interfaces';
|
||||
import { difference } from 'lodash';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
} from 'decorators/eventDispatcher';
|
||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
import events from 'subscribers/events';
|
||||
|
||||
@Service()
|
||||
export default class AccountsService {
|
||||
@@ -17,6 +22,9 @@ export default class AccountsService {
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
@EventDispatcher()
|
||||
eventDispatcher: EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Retrieve account type or throws service error.
|
||||
* @param {number} tenantId -
|
||||
@@ -104,10 +112,10 @@ export default class AccountsService {
|
||||
* @return {IAccount}
|
||||
*/
|
||||
private async getAccountOrThrowError(tenantId: number, accountId: number) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[accounts] validating the account existance.', { tenantId, accountId });
|
||||
const account = await Account.query().findById(accountId);
|
||||
const account = await accountRepository.findById(accountId);
|
||||
|
||||
if (!account) {
|
||||
this.logger.info('[accounts] the given account not found.', { accountId });
|
||||
@@ -159,8 +167,8 @@ export default class AccountsService {
|
||||
* @returns {IAccount}
|
||||
*/
|
||||
public async newAccount(tenantId: number, accountDTO: IAccountDTO) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Validate account name uniquiness.
|
||||
await this.validateAccountNameUniquiness(tenantId, accountDTO.name);
|
||||
|
||||
@@ -176,11 +184,15 @@ export default class AccountsService {
|
||||
);
|
||||
this.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
||||
}
|
||||
const account = await Account.query().insertAndFetch({
|
||||
const account = await accountRepository.insert({
|
||||
...accountDTO,
|
||||
slug: kebabCase(accountDTO.name),
|
||||
});
|
||||
this.logger.info('[account] account created successfully.', { account, accountDTO });
|
||||
|
||||
// Triggers `onAccountCreated` event.
|
||||
this.eventDispatcher.dispatch(events.accounts.onCreated);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
@@ -191,7 +203,7 @@ export default class AccountsService {
|
||||
* @param {IAccountDTO} accountDTO
|
||||
*/
|
||||
public async editAccount(tenantId: number, accountId: number, accountDTO: IAccountDTO) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
const oldAccount = await this.getAccountOrThrowError(tenantId, accountId);
|
||||
|
||||
// Validate account name uniquiness.
|
||||
@@ -214,12 +226,13 @@ export default class AccountsService {
|
||||
this.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
||||
}
|
||||
// Update the account on the storage.
|
||||
const account = await Account.query().patchAndFetchById(
|
||||
oldAccount.id, { ...accountDTO }
|
||||
);
|
||||
const account = await accountRepository.edit(oldAccount.id, accountDTO);
|
||||
this.logger.info('[account] account edited successfully.', {
|
||||
account, accountDTO, tenantId
|
||||
});
|
||||
// Triggers `onAccountEdited` event.
|
||||
this.eventDispatcher.dispatch(events.accounts.onEdited);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
@@ -309,7 +322,7 @@ export default class AccountsService {
|
||||
* @param {number} accountId
|
||||
*/
|
||||
public async deleteAccount(tenantId: number, accountId: number) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
const account = await this.getAccountOrThrowError(tenantId, accountId);
|
||||
|
||||
this.throwErrorIfAccountPredefined(account);
|
||||
@@ -317,10 +330,13 @@ export default class AccountsService {
|
||||
await this.throwErrorIfAccountHasChildren(tenantId, accountId);
|
||||
await this.throwErrorIfAccountHasTransactions(tenantId, accountId);
|
||||
|
||||
await Account.query().deleteById(account.id);
|
||||
await accountRepository.deleteById(account.id);
|
||||
this.logger.info('[account] account has been deleted successfully.', {
|
||||
tenantId, accountId,
|
||||
})
|
||||
});
|
||||
|
||||
// Triggers `onAccountDeleted` event.
|
||||
this.eventDispatcher.dispatch(events.accounts.onDeleted);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -400,6 +416,9 @@ export default class AccountsService {
|
||||
this.logger.info('[account] given accounts deleted in bulk successfully.', {
|
||||
tenantId, accountsIds
|
||||
});
|
||||
|
||||
// Triggers `onBulkDeleted` event.
|
||||
this.eventDispatcher.dispatch(events.accounts.onBulkDeleted);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -418,6 +437,9 @@ export default class AccountsService {
|
||||
active: activate ? 1 : 0,
|
||||
});
|
||||
this.logger.info('[account] accounts have been activated successfully.', { tenantId, accountsIds });
|
||||
|
||||
// Triggers `onAccountBulkActivated` event.
|
||||
this.eventDispatcher.dispatch(events.accounts.onActivated);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -436,6 +458,9 @@ export default class AccountsService {
|
||||
active: activate ? 1 : 0,
|
||||
})
|
||||
this.logger.info('[account] account have been activated successfully.', { tenantId, accountId });
|
||||
|
||||
// Triggers `onAccountActivated` event.
|
||||
this.eventDispatcher.dispatch(events.accounts.onActivated);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,12 +7,13 @@ import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
} from 'decorators/eventDispatcher';
|
||||
import { SystemUser, PasswordReset } from 'system/models';
|
||||
import { PasswordReset } from 'system/models';
|
||||
import {
|
||||
IRegisterDTO,
|
||||
ITenant,
|
||||
ISystemUser,
|
||||
IPasswordReset,
|
||||
IAuthenticationService,
|
||||
} from 'interfaces';
|
||||
import { hashPassword } from 'utils';
|
||||
import { ServiceError, ServiceErrors } from 'exceptions';
|
||||
|
||||
@@ -45,10 +45,10 @@ export default class ContactsService {
|
||||
* @param {IContactDTO} contactDTO
|
||||
*/
|
||||
async newContact(tenantId: number, contactDTO: IContactNewDTO, contactService: TContactService) {
|
||||
const { Contact } = this.tenancy.models(tenantId);
|
||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[contacts] trying to insert contact to the storage.', { tenantId, contactDTO });
|
||||
const contact = await Contact.query().insert({ contactService, ...contactDTO });
|
||||
const contact = await contactRepository.insert({ contactService, ...contactDTO });
|
||||
|
||||
this.logger.info('[contacts] contact inserted successfully.', { tenantId, contact });
|
||||
return contact;
|
||||
@@ -77,11 +77,11 @@ export default class ContactsService {
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async deleteContact(tenantId: number, contactId: number, contactService: TContactService) {
|
||||
const { Contact } = this.tenancy.models(tenantId);
|
||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||
const contact = await this.getContactByIdOrThrowError(tenantId, contactId, contactService);
|
||||
|
||||
this.logger.info('[contacts] trying to delete the given contact.', { tenantId, contactId });
|
||||
await Contact.query().findById(contactId).delete();
|
||||
await contactRepository.deleteById(contactId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,10 +124,10 @@ export default class ContactsService {
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async deleteBulkContacts(tenantId: number, contactsIds: number[], contactService: TContactService) {
|
||||
const { Contact } = this.tenancy.models(tenantId);
|
||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||
this.getContactsOrThrowErrorNotFound(tenantId, contactsIds, contactService);
|
||||
|
||||
await Contact.query().whereIn('id', contactsIds).delete();
|
||||
await contactRepository.bulkDelete(contactsIds);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,10 +6,10 @@ import ContactsService from 'services/Contacts/ContactsService';
|
||||
import {
|
||||
ICustomerNewDTO,
|
||||
ICustomerEditDTO,
|
||||
ICustomer,
|
||||
} from 'interfaces';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import { ICustomer } from 'src/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class CustomersService {
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
import Resource from 'models/Resource';
|
||||
import ResourceField from 'models/ResourceField';
|
||||
import ResourceFieldMetadata from 'models/ResourceFieldMetadata';
|
||||
import ResourceFieldMetadataCollection from 'collection/ResourceFieldMetadataCollection';
|
||||
|
||||
export default class ResourceCustomFieldRepository {
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
constructor(model) {
|
||||
if (typeof model === 'function') {
|
||||
this.resourceName = model.name;
|
||||
} else if (typeof model === 'string') {
|
||||
this.resourceName = model;
|
||||
}
|
||||
// Custom fields of the given resource.
|
||||
this.customFields = [];
|
||||
this.filledCustomFields = {};
|
||||
|
||||
// metadata of custom fields of the given resource.
|
||||
this.fieldsMetadata = {};
|
||||
this.resource = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches metadata of custom fields of the given resource.
|
||||
* @param {Integer} id - Resource item id.
|
||||
*/
|
||||
async fetchCustomFieldsMetadata(id) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('Please define the resource item id.');
|
||||
}
|
||||
if (!this.resource) {
|
||||
throw new Error('Target resource model is not found.');
|
||||
}
|
||||
const metadata = await ResourceFieldMetadata.query()
|
||||
.where('resource_id', this.resource.id)
|
||||
.where('resource_item_id', id);
|
||||
|
||||
this.fieldsMetadata[id] = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load resource.
|
||||
*/
|
||||
async loadResource() {
|
||||
const resource = await Resource.query().where('name', this.resourceName).first();
|
||||
|
||||
if (!resource) {
|
||||
throw new Error('There is no stored resource in the storage with the given model name.');
|
||||
}
|
||||
this.setResource(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load metadata of the resource.
|
||||
*/
|
||||
async loadResourceCustomFields() {
|
||||
if (typeof this.resource.id === 'undefined') {
|
||||
throw new Error('Please fetch resource details before fetch custom fields of the resource.');
|
||||
}
|
||||
const customFields = await ResourceField.query()
|
||||
.where('resource_id', this.resource.id)
|
||||
.modify('whereNotPredefined');
|
||||
|
||||
this.setResourceCustomFields(customFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets resource model.
|
||||
* @param {Resource} resource -
|
||||
*/
|
||||
setResource(resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets resource custom fields collection.
|
||||
* @param {Array} customFields -
|
||||
*/
|
||||
setResourceCustomFields(customFields) {
|
||||
this.customFields = customFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve metadata of the resource custom fields.
|
||||
* @param {Integer} itemId -
|
||||
*/
|
||||
getMetadata(itemId) {
|
||||
return this.fieldsMetadata[itemId] || this.fieldsMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill metadata of the custom fields that associated to the resource.
|
||||
* @param {Inter} id - Resource item id.
|
||||
* @param {Array} attributes -
|
||||
*/
|
||||
fillCustomFields(id, attributes) {
|
||||
if (typeof this.filledCustomFields[id] === 'undefined') {
|
||||
this.filledCustomFields[id] = [];
|
||||
}
|
||||
attributes.forEach((attr) => {
|
||||
this.filledCustomFields[id].push(attr);
|
||||
|
||||
if (!this.fieldsMetadata[id]) {
|
||||
this.fieldsMetadata[id] = new ResourceFieldMetadataCollection();
|
||||
}
|
||||
this.fieldsMetadata[id].setMeta(attr.key, attr.value, {
|
||||
resource_id: this.resource.id,
|
||||
resource_item_id: id,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the instered, updated and deleted custom fields metadata.
|
||||
* @param {Integer} id - Optional resource item id.
|
||||
*/
|
||||
async saveCustomFields(id) {
|
||||
if (id) {
|
||||
if (typeof this.fieldsMetadata[id] === 'undefined') {
|
||||
throw new Error('There is no resource item with the given id.');
|
||||
}
|
||||
await this.fieldsMetadata[id].saveMeta();
|
||||
} else {
|
||||
const opers = [];
|
||||
this.fieldsMetadata.forEach((metadata) => {
|
||||
const oper = metadata.saveMeta();
|
||||
opers.push(oper);
|
||||
});
|
||||
await Promise.all(opers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the exist custom fields.
|
||||
*/
|
||||
validateExistCustomFields() {
|
||||
|
||||
}
|
||||
|
||||
toArray() {
|
||||
return this.fieldsMetadata.toArray();
|
||||
}
|
||||
|
||||
async load() {
|
||||
await this.loadResource();
|
||||
await this.loadResourceCustomFields();
|
||||
}
|
||||
|
||||
static forgeMetadataCollection() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,7 @@ export default class DynamicListService implements IDynamicListService {
|
||||
this.validateFilterRolesSchema(filter.filterRoles);
|
||||
this.validateRolesFieldsExistance(model, filter.filterRoles);
|
||||
|
||||
// Validate the accounts resource fields.
|
||||
// Validate the model resource fields.
|
||||
const filterRoles = new DynamicFilterFilterRoles(filter.filterRoles);
|
||||
dynamicFilter.setFilter(filterRoles);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { Service, Inject } from "typedi";
|
||||
import { difference, sumBy, omit } from 'lodash';
|
||||
import moment from "moment";
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
} from 'decorators/eventDispatcher';
|
||||
import { ServiceError } from "exceptions";
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import JournalPoster from 'services/Accounting/JournalPoster';
|
||||
import JournalEntry from 'services/Accounting/JournalEntry';
|
||||
import JournalCommands from 'services/Accounting/JournalCommands';
|
||||
import { IExpense, IAccount, IExpenseDTO, IExpenseCategory, IExpensesService, ISystemUser } from 'interfaces';
|
||||
import { IExpense, IAccount, IExpenseDTO, IExpensesService, ISystemUser } from 'interfaces';
|
||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
import events from 'subscribers/events';
|
||||
|
||||
const ERRORS = {
|
||||
EXPENSE_NOT_FOUND: 'expense_not_found',
|
||||
@@ -30,6 +34,9 @@ export default class ExpensesService implements IExpensesService {
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
@EventDispatcher()
|
||||
eventDispatcher: EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Retrieve the payment account details or returns not found server error in case the
|
||||
* given account not found on the storage.
|
||||
@@ -158,11 +165,10 @@ export default class ExpensesService implements IExpensesService {
|
||||
* @param {IExpense} expense
|
||||
* @param {IUser} authorizedUser
|
||||
*/
|
||||
private async writeJournalEntries(
|
||||
public async writeJournalEntries(
|
||||
tenantId: number,
|
||||
expense: IExpense,
|
||||
revertOld: boolean,
|
||||
authorizedUser: ISystemUser
|
||||
) {
|
||||
this.logger.info('[expense[ trying to write expense journal entries.', { tenantId, expense });
|
||||
const journal = new JournalPoster(tenantId);
|
||||
@@ -171,29 +177,8 @@ export default class ExpensesService implements IExpensesService {
|
||||
if (revertOld) {
|
||||
await journalCommands.revertJournalEntries(expense.id, 'Expense');
|
||||
}
|
||||
const mixinEntry = {
|
||||
referenceType: 'Expense',
|
||||
referenceId: expense.id,
|
||||
date: expense.paymentDate,
|
||||
userId: authorizedUser.id,
|
||||
draft: !expense.publish,
|
||||
};
|
||||
const paymentJournalEntry = new JournalEntry({
|
||||
credit: expense.totalAmount,
|
||||
account: expense.paymentAccountId,
|
||||
...mixinEntry,
|
||||
});
|
||||
journal.credit(paymentJournalEntry);
|
||||
|
||||
expense.categories.forEach((category: IExpenseCategory) => {
|
||||
const expenseJournalEntry = new JournalEntry({
|
||||
account: category.expenseAccountId,
|
||||
debit: category.amount,
|
||||
note: category.description,
|
||||
...mixinEntry,
|
||||
});
|
||||
journal.debit(expenseJournalEntry);
|
||||
});
|
||||
journalCommands.expense(expense);
|
||||
|
||||
return Promise.all([
|
||||
journal.saveBalance(),
|
||||
journal.saveEntries(),
|
||||
@@ -229,7 +214,7 @@ export default class ExpensesService implements IExpensesService {
|
||||
* @param {IExpense} expense
|
||||
*/
|
||||
private validateExpenseIsNotPublished(expense: IExpense) {
|
||||
if (expense.published) {
|
||||
if (expense.publishedAt) {
|
||||
throw new ServiceError(ERRORS.EXPENSE_ACCOUNT_ALREADY_PUBLISED);
|
||||
}
|
||||
}
|
||||
@@ -291,33 +276,29 @@ export default class ExpensesService implements IExpensesService {
|
||||
const { expenseRepository } = this.tenancy.repositories(tenantId);
|
||||
const expense = await this.getExpenseOrThrowError(tenantId, expenseId);
|
||||
|
||||
// 1. Validate payment account existance on the storage.
|
||||
// - Validate payment account existance on the storage.
|
||||
const paymentAccount = await this.getPaymentAccountOrThrowError(
|
||||
tenantId,
|
||||
expenseDTO.paymentAccountId,
|
||||
);
|
||||
// 2. Validate expense accounts exist on the storage.
|
||||
// - Validate expense accounts exist on the storage.
|
||||
const expensesAccounts = await this.getExpensesAccountsOrThrowError(
|
||||
tenantId,
|
||||
this.mapExpensesAccountsIdsFromDTO(expenseDTO),
|
||||
);
|
||||
// 3. Validate payment account type.
|
||||
// - Validate payment account type.
|
||||
await this.validatePaymentAccountType(tenantId, paymentAccount);
|
||||
|
||||
// 4. Validate expenses accounts type.
|
||||
// - Validate expenses accounts type.
|
||||
await this.validateExpensesAccountsType(tenantId, expensesAccounts);
|
||||
|
||||
// 5. Validate the given expense categories not equal zero.
|
||||
// - Validate the given expense categories not equal zero.
|
||||
this.validateCategoriesNotEqualZero(expenseDTO);
|
||||
|
||||
// 6. Update the expense on the storage.
|
||||
// - Update the expense on the storage.
|
||||
const expenseObj = this.expenseDTOToModel(expenseDTO);
|
||||
const expenseModel = await expenseRepository.update(expenseId, expenseObj, null);
|
||||
|
||||
// 7. In case expense published, write journal entries.
|
||||
if (expenseObj.published) {
|
||||
await this.writeJournalEntries(tenantId, expenseModel, true, authorizedUser);
|
||||
}
|
||||
this.logger.info('[expense] the expense updated on the storage successfully.', { tenantId, expenseDTO });
|
||||
return expenseModel;
|
||||
}
|
||||
@@ -364,13 +345,12 @@ export default class ExpensesService implements IExpensesService {
|
||||
// 6. Save the expense to the storage.
|
||||
const expenseObj = this.expenseDTOToModel(expenseDTO, authorizedUser);
|
||||
const expenseModel = await expenseRepository.create(expenseObj);
|
||||
|
||||
// 7. In case expense published, write journal entries.
|
||||
if (expenseObj.published) {
|
||||
await this.writeJournalEntries(tenantId, expenseModel, false, authorizedUser);
|
||||
}
|
||||
|
||||
this.logger.info('[expense] the expense stored to the storage successfully.', { tenantId, expenseDTO });
|
||||
|
||||
// Triggers `onExpenseCreated` event.
|
||||
this.eventDispatcher.dispatch(events.expenses.onCreated, { tenantId, expenseId: expenseModel.id });
|
||||
|
||||
return expenseModel;
|
||||
}
|
||||
|
||||
@@ -394,6 +374,9 @@ export default class ExpensesService implements IExpensesService {
|
||||
await expenseRepository.publish(expenseId);
|
||||
|
||||
this.logger.info('[expense] the expense published successfully.', { tenantId, expenseId });
|
||||
|
||||
// Triggers `onExpensePublished` event.
|
||||
this.eventDispatcher.dispatch(events.expenses.onPublished, { tenantId, expenseId });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -409,10 +392,10 @@ export default class ExpensesService implements IExpensesService {
|
||||
this.logger.info('[expense] trying to delete the expense.', { tenantId, expenseId });
|
||||
await expenseRepository.delete(expenseId);
|
||||
|
||||
if (expense.published) {
|
||||
await this.revertJournalEntries(tenantId, expenseId);
|
||||
}
|
||||
this.logger.info('[expense] the expense deleted successfully.', { tenantId, expenseId });
|
||||
|
||||
// Triggers `onExpenseDeleted` event.
|
||||
this.eventDispatcher.dispatch(events.expenses.onDeleted, { tenantId, expenseId });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -427,9 +410,11 @@ export default class ExpensesService implements IExpensesService {
|
||||
|
||||
this.logger.info('[expense] trying to delete the given expenses.', { tenantId, expensesIds });
|
||||
await expenseRepository.bulkDelete(expensesIds);
|
||||
await this.revertJournalEntries(tenantId, expensesIds);
|
||||
|
||||
this.logger.info('[expense] the given expenses deleted successfully.', { tenantId, expensesIds });
|
||||
|
||||
// Triggers `onExpenseBulkDeleted` event.
|
||||
this.eventDispatcher.dispatch(events.expenses.onBulkDeleted, { tenantId, expensesIds });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -443,9 +428,12 @@ export default class ExpensesService implements IExpensesService {
|
||||
const { expenseRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[expense] trying to publish the given expenses.', { tenantId, expensesIds });
|
||||
await expenseRepository.publishBulk(expensesIds);
|
||||
await expenseRepository.bulkPublish(expensesIds);
|
||||
|
||||
this.logger.info('[expense] the given expenses ids published successfully.', { tenantId, expensesIds });
|
||||
|
||||
// Triggers `onExpenseBulkDeleted` event.
|
||||
this.eventDispatcher.dispatch(events.expenses.onBulkPublished, { tenantId, expensesIds });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,6 +37,18 @@ export default {
|
||||
tenantSeeded: 'onTenantSeeded',
|
||||
},
|
||||
|
||||
/**
|
||||
* Accounts service.
|
||||
*/
|
||||
accounts: {
|
||||
onCreated: 'onAccountCreated',
|
||||
onEdited: 'onAccountEdited',
|
||||
onDeleted: 'onAccountDeleted',
|
||||
onBulkDeleted: 'onBulkDeleted',
|
||||
onBulkActivated: 'onAccountBulkActivated',
|
||||
onActivated: 'onAccountActivated'
|
||||
},
|
||||
|
||||
/**
|
||||
* Manual journals service.
|
||||
*/
|
||||
@@ -47,5 +59,18 @@ export default {
|
||||
onDeletedBulk: 'onManualJournalCreatedBulk',
|
||||
onPublished: 'onManualJournalPublished',
|
||||
onPublishedBulk: 'onManualJournalPublishedBulk',
|
||||
},
|
||||
|
||||
/**
|
||||
* Expenses service.
|
||||
*/
|
||||
expenses: {
|
||||
onCreated: 'onExpenseCreated',
|
||||
onEdited: 'onExpenseEdited',
|
||||
onDeleted: 'onExpenseDelted',
|
||||
onPublished: 'onExpensePublished',
|
||||
|
||||
onBulkDeleted: 'onExpenseBulkDeleted',
|
||||
onBulkPublished: 'onBulkPublished',
|
||||
}
|
||||
}
|
||||
|
||||
64
server/src/subscribers/expenses.ts
Normal file
64
server/src/subscribers/expenses.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Container, Inject, Service } from 'typedi';
|
||||
import { EventSubscriber, On } from 'event-dispatch';
|
||||
import events from 'subscribers/events';
|
||||
import ExpensesService from 'services/Expenses/ExpensesService';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
} from 'decorators/eventDispatcher';
|
||||
|
||||
@Service()
|
||||
export default class ExpensesSubscriber {
|
||||
|
||||
constructor(
|
||||
@Inject()
|
||||
tenancy: TenancyService,
|
||||
|
||||
@EventDispatcher()
|
||||
eventDispatcher: EventDispatcherInterface,
|
||||
) {
|
||||
console.log(this, 'XXXX');
|
||||
// this.eventDispatcher.on(events.expenses.onCreated, this.onExpenseCreated);
|
||||
}
|
||||
|
||||
public onExpenseCreated({ expenseId, tenantId }) {
|
||||
console.log(this)
|
||||
|
||||
// // 7. In case expense published, write journal entries.
|
||||
// if (expenseObj.publishedAt) {
|
||||
// await this.writeJournalEntries(tenantId, expenseModel, false);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
// @On(events.expenses.onEdited)
|
||||
public onExpenseEdited({ expenseId, tenantId }) {
|
||||
// - In case expense published, write journal entries.
|
||||
// if (expenseObj.publishedAt) {
|
||||
// await this.writeJournalEntries(tenantId, expenseModel, true, authorizedUser);
|
||||
// }
|
||||
}
|
||||
|
||||
// @On(events.expenses.onDeleted)
|
||||
public onExpenseDeleted({ expenseId, tenantId }) {
|
||||
// if (expense.published) {
|
||||
// await this.revertJournalEntries(tenantId, expenseId);
|
||||
// }
|
||||
}
|
||||
|
||||
// @On(events.expenses.onPublished)
|
||||
public onExpensePublished({ expenseId, tenantId }) {
|
||||
|
||||
}
|
||||
|
||||
// @On(events.expenses.onBulkDeleted)
|
||||
public onExpenseBulkDeleted({ expensesIds, tenantId }) {
|
||||
|
||||
}
|
||||
|
||||
// @On(events.expenses.onBulkPublished)
|
||||
public onExpenseBulkPublished({ expensesIds, tenantId }) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
exports.up = (knex) => knex.schema.createTable('password_resets', (table) => {
|
||||
table.increments();
|
||||
table.string('email');
|
||||
table.string('token');
|
||||
table.string('email').index();
|
||||
table.string('token').index();
|
||||
table.timestamp('created_at');
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('tenants', (table) => {
|
||||
table.bigIncrements();
|
||||
table.string('organization_id');
|
||||
table.string('organization_id').index();
|
||||
|
||||
table.dateTime('under_maintenance_since').nullable();
|
||||
table.dateTime('initialized_at').nullable();
|
||||
|
||||
@@ -4,18 +4,15 @@ exports.up = function (knex) {
|
||||
table.increments();
|
||||
table.string('first_name');
|
||||
table.string('last_name');
|
||||
table.string('email').unique();
|
||||
table.string('phone_number').unique();
|
||||
table.string('email').unique().index();
|
||||
table.string('phone_number').unique().index();
|
||||
table.string('password');
|
||||
table.boolean('active');
|
||||
table.boolean('active').index();
|
||||
table.string('language');
|
||||
|
||||
table.integer('tenant_id').unsigned();
|
||||
|
||||
table.date('invite_accepted_at');
|
||||
table.date('last_login_at');
|
||||
|
||||
table.dateTime('deleted_at');
|
||||
table.bigInteger('tenant_id').unsigned().index().references('id').inTable('tenants');
|
||||
table.date('invite_accepted_at').index();
|
||||
table.date('last_login_at').index();
|
||||
table.dateTime('deleted_at').index();
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
@@ -2,9 +2,9 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('user_invites', (table) => {
|
||||
table.increments();
|
||||
table.string('email');
|
||||
table.string('token').unique();
|
||||
table.integer('tenant_id').unsigned();
|
||||
table.string('email').index();
|
||||
table.string('token').unique().index();
|
||||
table.bigInteger('tenant_id').unsigned().index().references('id').inTable('tenants');
|
||||
table.datetime('created_at');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('subscriptions_usage', table => {
|
||||
table.increments();
|
||||
table.integer('user_id');
|
||||
table.integer('plan_id');
|
||||
|
||||
table.dateTime('trial_ends_at');
|
||||
|
||||
table.dateTime('subscription_starts_at');
|
||||
table.dateTime('subscription_ends_at');
|
||||
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.dropTableIfExists('subscriptions_usage');
|
||||
};
|
||||
@@ -17,7 +17,6 @@ exports.up = function(knex) {
|
||||
table.string('invoice_interval').nullable();
|
||||
|
||||
table.integer('index').unsigned();
|
||||
|
||||
table.timestamps();
|
||||
}).then(() => {
|
||||
return knex.seed.run({
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('subscription_plan_features', table => {
|
||||
table.increments();
|
||||
|
||||
table.integer('plan_id').unsigned();
|
||||
table.integer('plan_id').unsigned().index().references('id').inTable('subscription_plans');
|
||||
table.string('slug');
|
||||
table.string('name');
|
||||
table.string('description');
|
||||
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,8 +4,8 @@ exports.up = function(knex) {
|
||||
table.increments('id');
|
||||
table.string('slug');
|
||||
|
||||
table.integer('plan_id').unsigned();
|
||||
table.integer('tenant_id').unsigned();
|
||||
table.integer('plan_id').unsigned().index().references('id').inTable('subscription_plans');
|
||||
table.bigInteger('tenant_id').unsigned().index().references('id').inTable('tenants');
|
||||
|
||||
table.dateTime('trial_started_at').nullable();
|
||||
table.dateTime('trial_ends_at').nullable();
|
||||
|
||||
@@ -3,15 +3,15 @@ exports.up = function(knex) {
|
||||
return knex.schema.createTable('subscription_licenses', table => {
|
||||
table.increments();
|
||||
|
||||
table.string('license_code').unique();
|
||||
table.integer('plan_id').unsigned();
|
||||
table.string('license_code').unique().index();
|
||||
table.integer('plan_id').unsigned().index().references('id').inTable('subscription_plans');
|
||||
|
||||
table.integer('license_period').unsigned();
|
||||
table.string('period_interval');
|
||||
|
||||
table.dateTime('sent_at');
|
||||
table.dateTime('disabled_at');
|
||||
table.dateTime('used_at');
|
||||
table.dateTime('sent_at').index();
|
||||
table.dateTime('disabled_at').index();
|
||||
table.dateTime('used_at').index();
|
||||
|
||||
table.timestamps();
|
||||
})
|
||||
|
||||
@@ -12,21 +12,22 @@ export default class SystemUserRepository extends SystemRepository {
|
||||
/**
|
||||
* Patches the last login date to the given system user.
|
||||
* @param {number} userId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async patchLastLoginAt(userId: number) {
|
||||
const user = await SystemUser.query().patchAndFetchById(userId, {
|
||||
async patchLastLoginAt(userId: number): Promise<void> {
|
||||
await SystemUser.query().patchAndFetchById(userId, {
|
||||
last_login_at: moment().toMySqlDateTime()
|
||||
});
|
||||
this.flushUserCache(user);
|
||||
return user;
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds system user by crediential.
|
||||
* @param {string} crediential - Phone number or email.
|
||||
* @return {ISystemUser}
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
findByCrediential(crediential: string) {
|
||||
findByCrediential(crediential: string): Promise<ISystemUser> {
|
||||
return SystemUser.query().whereNotDeleted()
|
||||
.findOne('email', crediential)
|
||||
.orWhere('phone_number', crediential);
|
||||
@@ -34,9 +35,10 @@ export default class SystemUserRepository extends SystemRepository {
|
||||
|
||||
/**
|
||||
* Retrieve system user details of the given id.
|
||||
* @param {number} userId
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
getById(userId: number) {
|
||||
getById(userId: number): Promise<ISystemUser> {
|
||||
return this.cache.get(`systemUser.id.${userId}`, () => {
|
||||
return SystemUser.query().whereNotDeleted().findById(userId);
|
||||
});
|
||||
@@ -44,10 +46,11 @@ export default class SystemUserRepository extends SystemRepository {
|
||||
|
||||
/**
|
||||
* Retrieve user by id and tenant id.
|
||||
* @param {number} userId
|
||||
* @param {number} tenantId
|
||||
* @param {number} userId - User id.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
getByIdAndTenant(userId: number, tenantId: number) {
|
||||
getByIdAndTenant(userId: number, tenantId: number): Promise<ISystemUser> {
|
||||
return this.cache.get(`systemUser.id.${userId}.tenant.${tenantId}`, () => {
|
||||
return SystemUser.query().whereNotDeleted()
|
||||
.findOne({ id: userId, tenant_id: tenantId });
|
||||
@@ -56,9 +59,10 @@ export default class SystemUserRepository extends SystemRepository {
|
||||
|
||||
/**
|
||||
* Retrieve system user details by the given email.
|
||||
* @param {string} email
|
||||
* @param {string} email - Email
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
getByEmail(email: string) {
|
||||
getByEmail(email: string): Promise<ISystemUser> {
|
||||
return this.cache.get(`systemUser.email.${email}`, () => {
|
||||
return SystemUser.query().whereNotDeleted().findOne('email', email);
|
||||
});
|
||||
@@ -66,9 +70,10 @@ export default class SystemUserRepository extends SystemRepository {
|
||||
|
||||
/**
|
||||
* Retrieve user by phone number.
|
||||
* @param {string} phoneNumber
|
||||
* @param {string} phoneNumber - Phone number
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
getByPhoneNumber(phoneNumber: string) {
|
||||
getByPhoneNumber(phoneNumber: string): Promise<ISystemUser> {
|
||||
return this.cache.get(`systemUser.phoneNumber.${phoneNumber}`, () => {
|
||||
return SystemUser.query().whereNotDeleted().findOne('phoneNumber', phoneNumber);
|
||||
});
|
||||
@@ -76,62 +81,61 @@ export default class SystemUserRepository extends SystemRepository {
|
||||
|
||||
/**
|
||||
* Edits details.
|
||||
* @param {number} userId
|
||||
* @param {number} user
|
||||
* @param {number} userId - User id.
|
||||
* @param {number} user - User input.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
edit(userId: number, userInput: ISystemUser) {
|
||||
const user = SystemUser.query().patchAndFetchById(userId, { ...userInput });
|
||||
this.flushUserCache(user);
|
||||
return user;
|
||||
async edit(userId: number, userInput: ISystemUser): Promise<void> {
|
||||
await SystemUser.query().patchAndFetchById(userId, { ...userInput });
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user.
|
||||
* @param {IUser} userInput
|
||||
* @param {IUser} userInput - User input.
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
create(userInput: ISystemUser) {
|
||||
return SystemUser.query().insert({ ...userInput });
|
||||
async create(userInput: ISystemUser): Promise<ISystemUser> {
|
||||
const systemUser = await SystemUser.query().insert({ ...userInput });
|
||||
this.flushCache();
|
||||
|
||||
return systemUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes user by the given id.
|
||||
* @param {number} userId
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async deleteById(userId: number) {
|
||||
const user = await this.getById(userId);
|
||||
async deleteById(userId: number): Promise<void> {
|
||||
await SystemUser.query().where('id', userId).delete();
|
||||
this.flushUserCache(user);
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate user by the given id.
|
||||
* @param {number} userId
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async activateById(userId: number) {
|
||||
const user = await SystemUser.query().patchAndFetchById(userId, { active: 1 });
|
||||
this.flushUserCache(user);
|
||||
return user;
|
||||
async activateById(userId: number): Promise<void> {
|
||||
await SystemUser.query().patchAndFetchById(userId, { active: 1 });
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivate user by the given id.
|
||||
* @param {number} userId
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async inactivateById(userId: number) {
|
||||
const user = await SystemUser.query().patchAndFetchById(userId, { active: 0 });
|
||||
this.flushUserCache(user);
|
||||
return user;
|
||||
async inactivateById(userId: number): Promise<void> {
|
||||
await SystemUser.query().patchAndFetchById(userId, { active: 0 });
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush user cache.
|
||||
* @param {IUser} user
|
||||
* Flushes user repository cache.
|
||||
*/
|
||||
flushUserCache(user: ISystemUser) {
|
||||
this.cache.del(`systemUser.phoneNumber.${user.phoneNumber}`);
|
||||
this.cache.del(`systemUser.email.${user.email}`);
|
||||
|
||||
this.cache.del(`systemUser.id.${user.id}`);
|
||||
this.cache.del(`systemUser.id.${user.id}.tenant.${user.tenantId}`);
|
||||
flushCache() {
|
||||
this.cache.delStartWith('systemUser');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user