diff --git a/server/.env.example b/server/.env.example index ea99a35cc..437019d76 100644 --- a/server/.env.example +++ b/server/.env.example @@ -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 diff --git a/server/src/api/controllers/Currencies.ts b/server/src/api/controllers/Currencies.ts index 06f0fbb5e..357083e34 100644 --- a/server/src/api/controllers/Currencies.ts +++ b/server/src/api/controllers/Currencies.ts @@ -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, { diff --git a/server/src/api/controllers/ItemCategories.ts b/server/src/api/controllers/ItemCategories.ts index 0d54a3c0c..f31b95991 100644 --- a/server/src/api/controllers/ItemCategories.ts +++ b/server/src/api/controllers/ItemCategories.ts @@ -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, ); diff --git a/server/src/api/controllers/Items.ts b/server/src/api/controllers/Items.ts index 672c09bb5..22ea33c28 100644 --- a/server/src/api/controllers/Items.ts +++ b/server/src/api/controllers/Items.ts @@ -23,8 +23,7 @@ export default class ItemsController extends BaseController { router() { const router = Router(); - router.post( - '/', [ + router.post('/', [ ...this.validateItemSchema, ], this.validationResult, diff --git a/server/src/api/controllers/Purchases/Bills.ts b/server/src/api/controllers/Purchases/Bills.ts index 9f7618323..83168506e 100644 --- a/server/src/api/controllers/Purchases/Bills.ts +++ b/server/src/api/controllers/Purchases/Bills.ts @@ -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)) ); diff --git a/server/src/api/controllers/Purchases/BillsPayments.ts b/server/src/api/controllers/Purchases/BillsPayments.ts index bd80c6903..478d7eb66 100644 --- a/server/src/api/controllers/Purchases/BillsPayments.ts +++ b/server/src/api/controllers/Purchases/BillsPayments.ts @@ -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; diff --git a/server/src/api/controllers/Sales/PaymentReceives.ts b/server/src/api/controllers/Sales/PaymentReceives.ts index d2693a174..0d2d1fac7 100644 --- a/server/src/api/controllers/Sales/PaymentReceives.ts +++ b/server/src/api/controllers/Sales/PaymentReceives.ts @@ -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)), ); diff --git a/server/src/api/controllers/Sales/SalesEstimates.ts b/server/src/api/controllers/Sales/SalesEstimates.ts index 9b397071c..2e5f9d401 100644 --- a/server/src/api/controllers/Sales/SalesEstimates.ts +++ b/server/src/api/controllers/Sales/SalesEstimates.ts @@ -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; diff --git a/server/src/api/controllers/Sales/SalesInvoices.ts b/server/src/api/controllers/Sales/SalesInvoices.ts index 96c0ef719..b4b9799de 100644 --- a/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/server/src/api/controllers/Sales/SalesInvoices.ts @@ -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; diff --git a/server/src/api/controllers/Sales/SalesReceipts.ts b/server/src/api/controllers/Sales/SalesReceipts.ts index 7d5e7c8e8..1eb197e24 100644 --- a/server/src/api/controllers/Sales/SalesReceipts.ts +++ b/server/src/api/controllers/Sales/SalesReceipts.ts @@ -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; diff --git a/server/src/api/controllers/Settings.ts b/server/src/api/controllers/Settings.ts index eddd3eae9..23b5b1136 100644 --- a/server/src/api/controllers/Settings.ts +++ b/server/src/api/controllers/Settings.ts @@ -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. diff --git a/server/src/api/controllers/Subscription/Licenses.ts b/server/src/api/controllers/Subscription/Licenses.ts index 781c8710f..5639d9b7c 100644 --- a/server/src/api/controllers/Subscription/Licenses.ts +++ b/server/src/api/controllers/Subscription/Licenses.ts @@ -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( diff --git a/server/src/api/controllers/Subscription/PaymentViaLicense.ts b/server/src/api/controllers/Subscription/PaymentViaLicense.ts index 56ceb1345..45b219c53 100644 --- a/server/src/api/controllers/Subscription/PaymentViaLicense.ts +++ b/server/src/api/controllers/Subscription/PaymentViaLicense.ts @@ -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)), ); diff --git a/server/src/api/middleware/validateMiddleware.js b/server/src/api/middleware/validateMiddleware.js deleted file mode 100644 index 266680e9a..000000000 --- a/server/src/api/middleware/validateMiddleware.js +++ /dev/null @@ -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(); -} \ No newline at end of file diff --git a/server/src/database/migrations/20190822214242_create_users_table.js b/server/src/database/migrations/20190822214242_create_users_table.js deleted file mode 100644 index fb9ee35f6..000000000 --- a/server/src/database/migrations/20190822214242_create_users_table.js +++ /dev/null @@ -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'); -}; diff --git a/server/src/database/migrations/20190822214904_create_account_types_table.js b/server/src/database/migrations/20190822214302_create_account_types_table.js similarity index 77% rename from server/src/database/migrations/20190822214904_create_account_types_table.js rename to server/src/database/migrations/20190822214302_create_account_types_table.js index c580efe83..3830c0ded 100644 --- a/server/src/database/migrations/20190822214904_create_account_types_table.js +++ b/server/src/database/migrations/20190822214302_create_account_types_table.js @@ -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'); diff --git a/server/src/database/migrations/20190822214303_create_accounts_table.js b/server/src/database/migrations/20190822214303_create_accounts_table.js new file mode 100644 index 000000000..d2dd03580 --- /dev/null +++ b/server/src/database/migrations/20190822214303_create_accounts_table.js @@ -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'); diff --git a/server/src/database/migrations/20190822214303_create_items_table.js b/server/src/database/migrations/20190822214303_create_items_table.js deleted file mode 100644 index 09516bfc1..000000000 --- a/server/src/database/migrations/20190822214303_create_items_table.js +++ /dev/null @@ -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'); diff --git a/server/src/database/migrations/20190822214304_create_accounts_table.js b/server/src/database/migrations/20190822214304_create_accounts_table.js deleted file mode 100644 index 038116275..000000000 --- a/server/src/database/migrations/20190822214304_create_accounts_table.js +++ /dev/null @@ -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'); diff --git a/server/src/database/migrations/20190822214304_create_items_categories_table.js b/server/src/database/migrations/20190822214304_create_items_categories_table.js new file mode 100644 index 000000000..c807352f5 --- /dev/null +++ b/server/src/database/migrations/20190822214304_create_items_categories_table.js @@ -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'); diff --git a/server/src/database/migrations/20190822214306_create_items_categories_table.js b/server/src/database/migrations/20190822214306_create_items_categories_table.js deleted file mode 100644 index a8b189137..000000000 --- a/server/src/database/migrations/20190822214306_create_items_categories_table.js +++ /dev/null @@ -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'); diff --git a/server/src/database/migrations/20190822214306_create_items_table.js b/server/src/database/migrations/20190822214306_create_items_table.js new file mode 100644 index 000000000..ff79afd56 --- /dev/null +++ b/server/src/database/migrations/20190822214306_create_items_table.js @@ -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'); diff --git a/server/src/database/migrations/20190822214905_create_views_table.js b/server/src/database/migrations/20190822214903_create_views_table.js similarity index 82% rename from server/src/database/migrations/20190822214905_create_views_table.js rename to server/src/database/migrations/20190822214903_create_views_table.js index e238d7ccb..53099e854 100644 --- a/server/src/database/migrations/20190822214905_create_views_table.js +++ b/server/src/database/migrations/20190822214903_create_views_table.js @@ -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(); diff --git a/server/src/database/migrations/20190822214302_create_settings_table.js b/server/src/database/migrations/20190822214904_create_settings_table.js similarity index 71% rename from server/src/database/migrations/20190822214302_create_settings_table.js rename to server/src/database/migrations/20190822214904_create_settings_table.js index b6aba4bef..65f3f4fdc 100644 --- a/server/src/database/migrations/20190822214302_create_settings_table.js +++ b/server/src/database/migrations/20190822214904_create_settings_table.js @@ -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'); }; diff --git a/server/src/database/migrations/20190822214905_create_views_columns.js b/server/src/database/migrations/20190822214905_create_views_columns.js index 5b902fbf3..4fc76e399 100644 --- a/server/src/database/migrations/20190822214905_create_views_columns.js +++ b/server/src/database/migrations/20190822214905_create_views_columns.js @@ -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'); diff --git a/server/src/database/migrations/20190822214905_create_views_roles_table.js b/server/src/database/migrations/20190822214905_create_views_roles_table.js index f96a15cbe..e9add11eb 100644 --- a/server/src/database/migrations/20190822214905_create_views_roles_table.js +++ b/server/src/database/migrations/20190822214905_create_views_roles_table.js @@ -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'); }; diff --git a/server/src/database/migrations/20200104232644_create_contacts_table.js b/server/src/database/migrations/20200104232644_create_contacts_table.js new file mode 100644 index 000000000..ccc7e13f0 --- /dev/null +++ b/server/src/database/migrations/20200104232644_create_contacts_table.js @@ -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'); +}; diff --git a/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js b/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js index bc02d6b4a..42adc70fb 100644 --- a/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js +++ b/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js @@ -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'); }; diff --git a/server/src/database/migrations/20200105013334_create_options_table.js b/server/src/database/migrations/20200105013334_create_options_table.js deleted file mode 100644 index 2003c6c9b..000000000 --- a/server/src/database/migrations/20200105013334_create_options_table.js +++ /dev/null @@ -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'); -}; diff --git a/server/src/database/migrations/20200105014405_create_expenses_table.js b/server/src/database/migrations/20200105014405_create_expenses_table.js index 40030dce3..5f4382e35 100644 --- a/server/src/database/migrations/20200105014405_create_expenses_table.js +++ b/server/src/database/migrations/20200105014405_create_expenses_table.js @@ -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'); }; diff --git a/server/src/database/migrations/20200105195823_create_manual_journals_table.js b/server/src/database/migrations/20200105195823_create_manual_journals_table.js index 69ba8a965..b4200c29a 100644 --- a/server/src/database/migrations/20200105195823_create_manual_journals_table.js +++ b/server/src/database/migrations/20200105195823_create_manual_journals_table.js @@ -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'); }; diff --git a/server/src/database/migrations/20200419171451_create_currencies_table.js b/server/src/database/migrations/20200419171451_create_currencies_table.js index de6201bb7..c2d847140 100644 --- a/server/src/database/migrations/20200419171451_create_currencies_table.js +++ b/server/src/database/migrations/20200419171451_create_currencies_table.js @@ -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'); }; diff --git a/server/src/database/migrations/20200419191832_create_exchange_rates_table.js b/server/src/database/migrations/20200419191832_create_exchange_rates_table.js index e347c42f7..99db76530 100644 --- a/server/src/database/migrations/20200419191832_create_exchange_rates_table.js +++ b/server/src/database/migrations/20200419191832_create_exchange_rates_table.js @@ -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'); }; diff --git a/server/src/database/migrations/20200503032011_create_media_links_table.js b/server/src/database/migrations/20200503032011_create_media_links_table.js index 52d5b4ca7..675337f94 100644 --- a/server/src/database/migrations/20200503032011_create_media_links_table.js +++ b/server/src/database/migrations/20200503032011_create_media_links_table.js @@ -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(); }) }; diff --git a/server/src/database/migrations/20200606113848_create_expense_transactions_categories_table.js b/server/src/database/migrations/20200606113848_create_expense_transactions_categories_table.js index 58568503b..b383bd668 100644 --- a/server/src/database/migrations/20200606113848_create_expense_transactions_categories_table.js +++ b/server/src/database/migrations/20200606113848_create_expense_transactions_categories_table.js @@ -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');; }; diff --git a/server/src/database/migrations/20200713192127_create_sales_estimates_table.js b/server/src/database/migrations/20200713192127_create_sales_estimates_table.js index 2dbce39ec..82d2d3ebe 100644 --- a/server/src/database/migrations/20200713192127_create_sales_estimates_table.js +++ b/server/src/database/migrations/20200713192127_create_sales_estimates_table.js @@ -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(); }); }; diff --git a/server/src/database/migrations/20200713213303_create_sales_receipt_table.js b/server/src/database/migrations/20200713213303_create_sales_receipt_table.js index 3adb04ba1..6b53bdd4d 100644 --- a/server/src/database/migrations/20200713213303_create_sales_receipt_table.js +++ b/server/src/database/migrations/20200713213303_create_sales_receipt_table.js @@ -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'); diff --git a/server/src/database/migrations/20200715193633_create_sale_invoices_table.js b/server/src/database/migrations/20200715193633_create_sale_invoices_table.js index 7f5b394b8..9fac2e9f2 100644 --- a/server/src/database/migrations/20200715193633_create_sale_invoices_table.js +++ b/server/src/database/migrations/20200715193633_create_sale_invoices_table.js @@ -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(); }); }; diff --git a/server/src/database/migrations/20200715194514_create_payment_receives_table.js b/server/src/database/migrations/20200715194514_create_payment_receives_table.js index bf299c4ee..758b32869 100644 --- a/server/src/database/migrations/20200715194514_create_payment_receives_table.js +++ b/server/src/database/migrations/20200715194514_create_payment_receives_table.js @@ -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(); }); }; diff --git a/server/src/database/migrations/20200718161031_create_payment_receives_entries_table.js b/server/src/database/migrations/20200718161031_create_payment_receives_entries_table.js index 627e46cbe..ffc9c665e 100644 --- a/server/src/database/migrations/20200718161031_create_payment_receives_entries_table.js +++ b/server/src/database/migrations/20200718161031_create_payment_receives_entries_table.js @@ -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(); }) }; diff --git a/server/src/database/migrations/20200719152005_create_bills_table.js b/server/src/database/migrations/20200719152005_create_bills_table.js index d6f3c2f7a..efc81c7d6 100644 --- a/server/src/database/migrations/20200719152005_create_bills_table.js +++ b/server/src/database/migrations/20200719152005_create_bills_table.js @@ -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(); }); }; diff --git a/server/src/database/migrations/20200719153909_create_bills_payments_table.js b/server/src/database/migrations/20200719153909_create_bills_payments_table.js index e069d0472..065f2bae6 100644 --- a/server/src/database/migrations/20200719153909_create_bills_payments_table.js +++ b/server/src/database/migrations/20200719153909_create_bills_payments_table.js @@ -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(); }); diff --git a/server/src/database/migrations/20200722164251_create_inventory_transactions_table.js b/server/src/database/migrations/20200722164251_create_inventory_transactions_table.js index ede2040c4..a62b4d905 100644 --- a/server/src/database/migrations/20200722164251_create_inventory_transactions_table.js +++ b/server/src/database/migrations/20200722164251_create_inventory_transactions_table.js @@ -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(); }); }; diff --git a/server/src/database/migrations/20200722173423_create_items_entries_table.js b/server/src/database/migrations/20200722173423_create_items_entries_table.js index 902ef253d..dc36d5b72 100644 --- a/server/src/database/migrations/20200722173423_create_items_entries_table.js +++ b/server/src/database/migrations/20200722173423_create_items_entries_table.js @@ -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(); diff --git a/server/src/database/migrations/20200728161617_create_bill_payments_entries.js b/server/src/database/migrations/20200728161617_create_bill_payments_entries.js index 7dac8c241..30af9d50f 100644 --- a/server/src/database/migrations/20200728161617_create_bill_payments_entries.js +++ b/server/src/database/migrations/20200728161617_create_bill_payments_entries.js @@ -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(); }) }; diff --git a/server/src/database/migrations/20200810121807_create_inventory_cost_lot_tracker_table.js b/server/src/database/migrations/20200810121807_create_inventory_cost_lot_tracker_table.js index 51b31cf06..344bf30e4 100644 --- a/server/src/database/migrations/20200810121807_create_inventory_cost_lot_tracker_table.js +++ b/server/src/database/migrations/20200810121807_create_inventory_cost_lot_tracker_table.js @@ -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(); }); }; diff --git a/server/src/interfaces/Register.ts b/server/src/interfaces/Authentication.ts similarity index 100% rename from server/src/interfaces/Register.ts rename to server/src/interfaces/Authentication.ts diff --git a/server/src/interfaces/index.ts b/server/src/interfaces/index.ts index 17852dea8..de5cc16ba 100644 --- a/server/src/interfaces/index.ts +++ b/server/src/interfaces/index.ts @@ -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'; diff --git a/server/src/loaders/events.ts b/server/src/loaders/events.ts index fed18f4b3..e81cf6659 100644 --- a/server/src/loaders/events.ts +++ b/server/src/loaders/events.ts @@ -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'; \ No newline at end of file diff --git a/server/src/loaders/tenantModels.ts b/server/src/loaders/tenantModels.ts index d5c124dce..e5f32d033 100644 --- a/server/src/loaders/tenantModels.ts +++ b/server/src/loaders/tenantModels.ts @@ -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, diff --git a/server/src/models/Bill.js b/server/src/models/Bill.js index bace2321a..2e7429492 100644 --- a/server/src/models/Bill.js +++ b/server/src/models/Bill.js @@ -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: { diff --git a/server/src/models/BillPayment.js b/server/src/models/BillPayment.js index 71f598615..282722a35 100644 --- a/server/src/models/BillPayment.js +++ b/server/src/models/BillPayment.js @@ -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: { diff --git a/server/src/models/Customer.js b/server/src/models/Customer.js deleted file mode 100644 index 5d0590667..000000000 --- a/server/src/models/Customer.js +++ /dev/null @@ -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); - } -} diff --git a/server/src/models/PaymentReceive.js b/server/src/models/PaymentReceive.js index d992978a9..db1df4b0a 100644 --- a/server/src/models/PaymentReceive.js +++ b/server/src/models/PaymentReceive.js @@ -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: { diff --git a/server/src/models/SaleEstimate.js b/server/src/models/SaleEstimate.js index fcbe46cb0..375682193 100644 --- a/server/src/models/SaleEstimate.js +++ b/server/src/models/SaleEstimate.js @@ -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: { diff --git a/server/src/models/SaleInvoice.js b/server/src/models/SaleInvoice.js index 0fae868fa..4070dbcf9 100644 --- a/server/src/models/SaleInvoice.js +++ b/server/src/models/SaleInvoice.js @@ -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: { diff --git a/server/src/models/SaleReceipt.js b/server/src/models/SaleReceipt.js index 9b05cda29..4fe84b9f0 100644 --- a/server/src/models/SaleReceipt.js +++ b/server/src/models/SaleReceipt.js @@ -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: { diff --git a/server/src/models/Vendor.js b/server/src/models/Vendor.js deleted file mode 100644 index 636121624..000000000 --- a/server/src/models/Vendor.js +++ /dev/null @@ -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); - } -} diff --git a/server/src/models/index.js b/server/src/models/index.js index bd91c577d..a980b2156 100644 --- a/server/src/models/index.js +++ b/server/src/models/index.js @@ -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, diff --git a/server/src/repositories/AccountRepository.ts b/server/src/repositories/AccountRepository.ts index 2452cffe4..a036ea4f9 100644 --- a/server/src/repositories/AccountRepository.ts +++ b/server/src/repositories/AccountRepository.ts @@ -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 { + 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 { + 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 { + 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 { + const { Account } = this.models; + await Account.query().deleteById(accountId); + this.flushCache(); + } + + /** + * Flush repository cache. + */ + flushCache(): void { + this.cache.delStartWith('accounts'); + } } \ No newline at end of file diff --git a/server/src/repositories/AccountTypeRepository.ts b/server/src/repositories/AccountTypeRepository.ts index 7b273076e..21c93ead5 100644 --- a/server/src/repositories/AccountTypeRepository.ts +++ b/server/src/repositories/AccountTypeRepository.ts @@ -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'); + } } \ No newline at end of file diff --git a/server/src/repositories/ContactRepository.ts b/server/src/repositories/ContactRepository.ts index b25942ec4..712e357e1 100644 --- a/server/src/repositories/ContactRepository.ts +++ b/server/src/repositories/ContactRepository.ts @@ -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} + */ + async deleteById(contactId: number): Promise { + 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`); } } \ No newline at end of file diff --git a/server/src/repositories/CustomerRepository.js b/server/src/repositories/CustomerRepository.js deleted file mode 100644 index 166476a45..000000000 --- a/server/src/repositories/CustomerRepository.js +++ /dev/null @@ -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); - } -} diff --git a/server/src/repositories/CustomerRepository.ts b/server/src/repositories/CustomerRepository.ts index ce019acc6..12c41cd93 100644 --- a/server/src/repositories/CustomerRepository.ts +++ b/server/src/repositories/CustomerRepository.ts @@ -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; diff --git a/server/src/repositories/ExpenseRepository.ts b/server/src/repositories/ExpenseRepository.ts index e83836ee0..a7502961f 100644 --- a/server/src/repositories/ExpenseRepository.ts +++ b/server/src/repositories/ExpenseRepository.ts @@ -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} + */ 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 { 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 { + 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 { + 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 { + 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} + */ + async bulkPublish(expensesIds: number): Promise { + const { Expense } = this.models; + await Expense.query().whereIn('id', expensesIds).patch({ + publishedAt: moment().toMySqlDateTime(), + }); + this.flushCache(); + } + + /** + * Flushes repository cache. + */ + flushCache() { + this.cache.delStartWith(`expense`); } } \ No newline at end of file diff --git a/server/src/repositories/VendorRepository.ts b/server/src/repositories/VendorRepository.ts index a02443a0b..264c158c8 100644 --- a/server/src/repositories/VendorRepository.ts +++ b/server/src/repositories/VendorRepository.ts @@ -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[]) { diff --git a/server/src/services/Accounting/JournalCommands.ts b/server/src/services/Accounting/JournalCommands.ts index 9e07d306b..9be420066 100644 --- a/server/src/services/Accounting/JournalCommands.ts +++ b/server/src/services/Accounting/JournalCommands.ts @@ -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 diff --git a/server/src/services/Accounts/AccountsService.ts b/server/src/services/Accounts/AccountsService.ts index 825835b7e..e758662d7 100644 --- a/server/src/services/Accounts/AccountsService.ts +++ b/server/src/services/Accounts/AccountsService.ts @@ -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); } /** diff --git a/server/src/services/Authentication/index.ts b/server/src/services/Authentication/index.ts index 90344d9cf..171c90e6a 100644 --- a/server/src/services/Authentication/index.ts +++ b/server/src/services/Authentication/index.ts @@ -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'; diff --git a/server/src/services/Contacts/ContactsService.ts b/server/src/services/Contacts/ContactsService.ts index 0577dd74f..6c98474ec 100644 --- a/server/src/services/Contacts/ContactsService.ts +++ b/server/src/services/Contacts/ContactsService.ts @@ -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} */ 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} */ 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); } /** diff --git a/server/src/services/Contacts/CustomersService.ts b/server/src/services/Contacts/CustomersService.ts index 97f85c927..399c3cc6b 100644 --- a/server/src/services/Contacts/CustomersService.ts +++ b/server/src/services/Contacts/CustomersService.ts @@ -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 { diff --git a/server/src/services/CustomFields/ResourceCustomFieldRepository.js b/server/src/services/CustomFields/ResourceCustomFieldRepository.js deleted file mode 100644 index 9c99dd48c..000000000 --- a/server/src/services/CustomFields/ResourceCustomFieldRepository.js +++ /dev/null @@ -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() { - - } -} diff --git a/server/src/services/DynamicListing/DynamicListService.ts b/server/src/services/DynamicListing/DynamicListService.ts index 54ee80db5..f8135dea3 100644 --- a/server/src/services/DynamicListing/DynamicListService.ts +++ b/server/src/services/DynamicListing/DynamicListService.ts @@ -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); } diff --git a/server/src/services/Expenses/ExpensesService.ts b/server/src/services/Expenses/ExpensesService.ts index 617e88d48..f671e0b43 100644 --- a/server/src/services/Expenses/ExpensesService.ts +++ b/server/src/services/Expenses/ExpensesService.ts @@ -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 }); } /** diff --git a/server/src/subscribers/events.ts b/server/src/subscribers/events.ts index 975036766..13a990f23 100644 --- a/server/src/subscribers/events.ts +++ b/server/src/subscribers/events.ts @@ -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', } } diff --git a/server/src/subscribers/expenses.ts b/server/src/subscribers/expenses.ts new file mode 100644 index 000000000..cd54f291d --- /dev/null +++ b/server/src/subscribers/expenses.ts @@ -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 }) { + + } +} \ No newline at end of file diff --git a/server/src/system/migrations/20190104195900_create_password_resets_table.js b/server/src/system/migrations/20190104195900_create_password_resets_table.js index bd274950e..9337949c7 100644 --- a/server/src/system/migrations/20190104195900_create_password_resets_table.js +++ b/server/src/system/migrations/20190104195900_create_password_resets_table.js @@ -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'); }); diff --git a/server/src/system/migrations/20200420134631_create_tenants_table.js b/server/src/system/migrations/20200420134631_create_tenants_table.js index 1d73550fb..ef968abf9 100644 --- a/server/src/system/migrations/20200420134631_create_tenants_table.js +++ b/server/src/system/migrations/20200420134631_create_tenants_table.js @@ -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(); diff --git a/server/src/system/migrations/20190822214242_create_users_table.js b/server/src/system/migrations/20200420134633_create_users_table.js similarity index 50% rename from server/src/system/migrations/20190822214242_create_users_table.js rename to server/src/system/migrations/20200420134633_create_users_table.js index 50fa5e42c..d4a08a226 100644 --- a/server/src/system/migrations/20190822214242_create_users_table.js +++ b/server/src/system/migrations/20200420134633_create_users_table.js @@ -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(); }); }; diff --git a/server/src/system/migrations/20200422225247_create_user_invites_table.js b/server/src/system/migrations/20200422225247_create_user_invites_table.js index 21f1e7fa6..abb723b20 100644 --- a/server/src/system/migrations/20200422225247_create_user_invites_table.js +++ b/server/src/system/migrations/20200422225247_create_user_invites_table.js @@ -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'); }); }; diff --git a/server/src/system/migrations/20200527091649_create_subscriptions_usage_table.js b/server/src/system/migrations/20200527091649_create_subscriptions_usage_table.js deleted file mode 100644 index 5ab810315..000000000 --- a/server/src/system/migrations/20200527091649_create_subscriptions_usage_table.js +++ /dev/null @@ -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'); -}; diff --git a/server/src/system/migrations/20200823234134_create_plans_table.js b/server/src/system/migrations/20200823234134_create_plans_table.js index 80392db62..2fc61a43a 100644 --- a/server/src/system/migrations/20200823234134_create_plans_table.js +++ b/server/src/system/migrations/20200823234134_create_plans_table.js @@ -17,7 +17,6 @@ exports.up = function(knex) { table.string('invoice_interval').nullable(); table.integer('index').unsigned(); - table.timestamps(); }).then(() => { return knex.seed.run({ diff --git a/server/src/system/migrations/20200823234434_create_subscription_plan_feature.js b/server/src/system/migrations/20200823234434_create_subscription_plan_feature.js index 78aaf3356..43fea2798 100644 --- a/server/src/system/migrations/20200823234434_create_subscription_plan_feature.js +++ b/server/src/system/migrations/20200823234434_create_subscription_plan_feature.js @@ -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(); }); }; diff --git a/server/src/system/migrations/20200823234636_create_subscription_plan_subscription.js b/server/src/system/migrations/20200823234636_create_subscription_plan_subscription.js index a8b7e2621..acdc9a0e6 100644 --- a/server/src/system/migrations/20200823234636_create_subscription_plan_subscription.js +++ b/server/src/system/migrations/20200823234636_create_subscription_plan_subscription.js @@ -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(); diff --git a/server/src/system/migrations/20200823235339_create_subscription_licenses_table.js b/server/src/system/migrations/20200823235339_create_subscription_licenses_table.js index 5b79cd4a2..206721cb2 100644 --- a/server/src/system/migrations/20200823235339_create_subscription_licenses_table.js +++ b/server/src/system/migrations/20200823235339_create_subscription_licenses_table.js @@ -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(); }) diff --git a/server/src/system/repositories/SystemUserRepository.ts b/server/src/system/repositories/SystemUserRepository.ts index ccfa76fa4..7ab28b3ba 100644 --- a/server/src/system/repositories/SystemUserRepository.ts +++ b/server/src/system/repositories/SystemUserRepository.ts @@ -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} */ - async patchLastLoginAt(userId: number) { - const user = await SystemUser.query().patchAndFetchById(userId, { + async patchLastLoginAt(userId: number): Promise { + 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} */ - findByCrediential(crediential: string) { + findByCrediential(crediential: string): Promise { 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} */ - getById(userId: number) { + getById(userId: number): Promise { 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} */ - getByIdAndTenant(userId: number, tenantId: number) { + getByIdAndTenant(userId: number, tenantId: number): Promise { 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} */ - getByEmail(email: string) { + getByEmail(email: string): Promise { 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} */ - getByPhoneNumber(phoneNumber: string) { + getByPhoneNumber(phoneNumber: string): Promise { 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} */ - edit(userId: number, userInput: ISystemUser) { - const user = SystemUser.query().patchAndFetchById(userId, { ...userInput }); - this.flushUserCache(user); - return user; + async edit(userId: number, userInput: ISystemUser): Promise { + await SystemUser.query().patchAndFetchById(userId, { ...userInput }); + this.flushCache(); } /** * Creates a new user. - * @param {IUser} userInput + * @param {IUser} userInput - User input. + * @return {Promise} */ - create(userInput: ISystemUser) { - return SystemUser.query().insert({ ...userInput }); + async create(userInput: ISystemUser): Promise { + 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} */ - async deleteById(userId: number) { - const user = await this.getById(userId); + async deleteById(userId: number): Promise { 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} */ - async activateById(userId: number) { - const user = await SystemUser.query().patchAndFetchById(userId, { active: 1 }); - this.flushUserCache(user); - return user; + async activateById(userId: number): Promise { + 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} */ - async inactivateById(userId: number) { - const user = await SystemUser.query().patchAndFetchById(userId, { active: 0 }); - this.flushUserCache(user); - return user; + async inactivateById(userId: number): Promise { + 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'); } } \ No newline at end of file