From 130aa6a4001f1903be09fdfc08093f3bc605fee5 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 29 Nov 2020 13:04:00 +0200 Subject: [PATCH 1/6] fix: activate/inactivate item. --- server/src/api/controllers/Items.ts | 1 + .../migrations/20190822214306_create_items_table.js | 1 + server/src/interfaces/Item.ts | 2 ++ server/src/services/Items/ItemsService.ts | 7 +++++-- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/server/src/api/controllers/Items.ts b/server/src/api/controllers/Items.ts index 804f37684..e24c0f47d 100644 --- a/server/src/api/controllers/Items.ts +++ b/server/src/api/controllers/Items.ts @@ -116,6 +116,7 @@ export default class ItemsController extends BaseController { check('category_id').optional({ nullable: true }).isInt().toInt(), check('note').optional(), + check('active').optional().isBoolean().toBoolean(), check('media_ids').optional().isArray(), check('media_ids.*').exists().isNumeric().toInt(), diff --git a/server/src/database/migrations/20190822214306_create_items_table.js b/server/src/database/migrations/20190822214306_create_items_table.js index b85d66e7d..83f03280a 100644 --- a/server/src/database/migrations/20190822214306_create_items_table.js +++ b/server/src/database/migrations/20190822214306_create_items_table.js @@ -18,6 +18,7 @@ exports.up = function (knex) { table.text('purchase_description').nullable(); table.integer('quantity_on_hand'); table.text('note').nullable(); + table.boolean('active'); table.integer('category_id').unsigned().index().references('id').inTable('items_categories'); table.integer('user_id').unsigned().index(); table.timestamps(); diff --git a/server/src/interfaces/Item.ts b/server/src/interfaces/Item.ts index 2726fbae0..f5cf5726f 100644 --- a/server/src/interfaces/Item.ts +++ b/server/src/interfaces/Item.ts @@ -22,6 +22,7 @@ export interface IItem{ quantityOnHand: number, note: string, + active: boolean, categoryId: number, userId: number, @@ -52,6 +53,7 @@ export interface IItemDTO { quantityOnHand: number, note: string, + active: boolean, categoryId: number, } diff --git a/server/src/services/Items/ItemsService.ts b/server/src/services/Items/ItemsService.ts index 3597219c4..298cb3468 100644 --- a/server/src/services/Items/ItemsService.ts +++ b/server/src/services/Items/ItemsService.ts @@ -1,4 +1,4 @@ -import { difference } from "lodash"; +import { defaultTo, difference } from "lodash"; import { Service, Inject } from "typedi"; import { IItemsFilter, IItemsService, IItemDTO, IItem } from 'interfaces'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; @@ -176,7 +176,10 @@ export default class ItemsService implements IItemsService { if (itemDTO.inventoryAccountId) { await this.validateItemInventoryAccountExistance(tenantId, itemDTO.inventoryAccountId); } - const storedItem = await Item.query().insertAndFetch({ ...itemDTO }); + const storedItem = await Item.query().insertAndFetch({ + ...itemDTO, + active: defaultTo(itemDTO.active, 1), + }); this.logger.info('[items] item inserted successfully.', { tenantId, itemDTO }); return storedItem; From 7524edb3d201c5b58190722fdb8ecfb5fb5c6dc0 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 29 Nov 2020 13:16:12 +0200 Subject: [PATCH 2/6] fix: delete item category has associated items. --- .../ItemCategories/ItemCategoriesService.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/server/src/services/ItemCategories/ItemCategoriesService.ts b/server/src/services/ItemCategories/ItemCategoriesService.ts index 3626bf88b..ce613de3c 100644 --- a/server/src/services/ItemCategories/ItemCategoriesService.ts +++ b/server/src/services/ItemCategories/ItemCategoriesService.ts @@ -224,7 +224,11 @@ export default class ItemCategoriesService implements IItemCategoriesService { */ public async deleteItemCategory(tenantId: number, itemCategoryId: number, authorizedUser: ISystemUser) { this.logger.info('[item_category] trying to delete item category.', { tenantId, itemCategoryId }); + + // Retrieve item category or throw not found error. await this.getItemCategoryOrThrowError(tenantId, itemCategoryId); + + // Unassociate items with item category. await this.unassociateItemsWithCategories(tenantId, itemCategoryId); const { ItemCategory } = this.tenancy.models(tenantId); @@ -276,11 +280,14 @@ export default class ItemCategoriesService implements IItemCategoriesService { * @param {number|number[]} itemCategoryId - * @return {Promise} */ - private async unassociateItemsWithCategories(tenantId: number, itemCategoryId: number|number[]): Promise { + private async unassociateItemsWithCategories( + tenantId: number, + itemCategoryId: number | number[], + ): Promise { const { Item } = this.tenancy.models(tenantId); const ids = Array.isArray(itemCategoryId) ? itemCategoryId : [itemCategoryId]; - await Item.query().whereIn('id', ids).patch({ category_id: null }); + await Item.query().whereIn('category_id', ids).patch({ category_id: null }); } /** @@ -288,7 +295,11 @@ export default class ItemCategoriesService implements IItemCategoriesService { * @param {number} tenantId * @param {number[]} itemCategoriesIds */ - public async deleteItemCategories(tenantId: number, itemCategoriesIds: number[], authorizedUser: ISystemUser) { + public async deleteItemCategories( + tenantId: number, + itemCategoriesIds: number[], + authorizedUser: ISystemUser, + ) { this.logger.info('[item_category] trying to delete item categories.', { tenantId, itemCategoriesIds }); const { ItemCategory } = this.tenancy.models(tenantId); From 169c499d617bcb9273fe5f15ff633412f9f5a84b Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 29 Nov 2020 13:47:28 +0200 Subject: [PATCH 3/6] fix: closing balance in customers list API. --- server/src/services/Contacts/CustomersService.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/src/services/Contacts/CustomersService.ts b/server/src/services/Contacts/CustomersService.ts index 2f801d0e2..509512dfe 100644 --- a/server/src/services/Contacts/CustomersService.ts +++ b/server/src/services/Contacts/CustomersService.ts @@ -69,7 +69,7 @@ export default class CustomersService { private transformContactToCustomer(contactModel: IContact) { return { - ...omit(contactModel, ['contactService', 'contactType']), + ...omit(contactModel.toJSON(), ['contactService', 'contactType']), customerType: contactModel.contactType, }; } @@ -174,7 +174,11 @@ export default class CustomersService { public async getCustomersList( tenantId: number, customersFilter: ICustomersFilter - ): Promise<{ customers: ICustomer[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> { + ): Promise<{ + customers: ICustomer[], + pagination: IPaginationMeta, + filterMeta: IFilterMeta, + }> { const { Contact } = this.tenancy.models(tenantId); const dynamicList = await this.dynamicListService.dynamicList(tenantId, Contact, customersFilter); From ba6a29579a57b6a65c6d1a586c81b1d237f92372 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 29 Nov 2020 14:00:44 +0200 Subject: [PATCH 4/6] fix: contacts URL validation rule. --- server/src/api/controllers/Contacts/Contacts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/api/controllers/Contacts/Contacts.ts b/server/src/api/controllers/Contacts/Contacts.ts index 312938242..3ea24623f 100644 --- a/server/src/api/controllers/Contacts/Contacts.ts +++ b/server/src/api/controllers/Contacts/Contacts.ts @@ -15,7 +15,7 @@ export default class ContactsController extends BaseController { check('display_name').exists().trim().escape(), check('email').optional({ nullable: true }).normalizeEmail().isEmail(), - check('website').optional().trim().escape(), + check('website').optional().trim().isURL(), check('work_phone').optional().trim().escape(), check('personal_phone').optional().trim().escape(), From a4195069c74d06b0b1fb085e455ac0eab56508b4 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 29 Nov 2020 17:51:40 +0200 Subject: [PATCH 5/6] fix: data type validation in accounts, users, contacts, vendors, customers, journals. --- server/src/api/controllers/Accounts.ts | 9 ++- server/src/api/controllers/Authentication.ts | 13 ++-- .../src/api/controllers/Contacts/Contacts.ts | 59 ++++++++------- .../src/api/controllers/Contacts/Customers.ts | 2 +- .../src/api/controllers/Contacts/Vendors.ts | 7 +- server/src/api/controllers/ItemCategories.ts | 19 +++-- server/src/api/controllers/Items.ts | 75 +++++++++++++------ server/src/api/controllers/ManualJournals.ts | 45 ++++++++--- server/src/data/DataTypes.js | 8 ++ 9 files changed, 159 insertions(+), 78 deletions(-) create mode 100644 server/src/data/DataTypes.js diff --git a/server/src/api/controllers/Accounts.ts b/server/src/api/controllers/Accounts.ts index 98790f1d9..f4ce2c961 100644 --- a/server/src/api/controllers/Accounts.ts +++ b/server/src/api/controllers/Accounts.ts @@ -7,6 +7,7 @@ import AccountsService from 'services/Accounts/AccountsService'; import { IAccountDTO, IAccountsFilter } from 'interfaces'; import { ServiceError } from 'exceptions'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; +import { DATATYPES_LENGTH } from 'data/DataTypes'; @Service() export default class AccountsController extends BaseController{ @@ -112,7 +113,7 @@ export default class AccountsController extends BaseController{ return [ check('name') .exists() - .isLength({ min: 3, max: 255 }) + .isLength({ min: 3, max: DATATYPES_LENGTH.STRING }) .trim() .escape(), check('code') @@ -122,16 +123,16 @@ export default class AccountsController extends BaseController{ .escape(), check('account_type_id') .exists() - .isNumeric() + .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) .toInt(), check('description') .optional({ nullable: true }) - .isLength({ max: 512 }) + .isLength({ max: DATATYPES_LENGTH.TEXT }) .trim() .escape(), check('parent_account_id') .optional({ nullable: true }) - .isNumeric() + .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) .toInt(), ]; } diff --git a/server/src/api/controllers/Authentication.ts b/server/src/api/controllers/Authentication.ts index e219e5a81..b5c50649c 100644 --- a/server/src/api/controllers/Authentication.ts +++ b/server/src/api/controllers/Authentication.ts @@ -6,6 +6,7 @@ import asyncMiddleware from 'api/middleware/asyncMiddleware'; import AuthenticationService from 'services/Authentication'; import { ILoginDTO, ISystemUser, IRegisterOTD } from 'interfaces'; import { ServiceError, ServiceErrors } from "exceptions"; +import { DATATYPES_LENGTH } from 'data/DataTypes'; @Service() export default class AuthenticationController extends BaseController{ @@ -60,12 +61,12 @@ export default class AuthenticationController extends BaseController{ */ get registerSchema(): ValidationChain[] { return [ - check('first_name').exists().trim().escape(), - check('last_name').exists().trim().escape(), - check('email').exists().isEmail().trim().escape(), - check('phone_number').exists().trim().escape(), - check('password').exists().trim().escape(), - check('country').exists().trim().escape(), + check('first_name').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('last_name').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('email').exists().isString().isEmail().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('phone_number').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('password').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('country').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), ]; } diff --git a/server/src/api/controllers/Contacts/Contacts.ts b/server/src/api/controllers/Contacts/Contacts.ts index 3ea24623f..4d8f845f3 100644 --- a/server/src/api/controllers/Contacts/Contacts.ts +++ b/server/src/api/controllers/Contacts/Contacts.ts @@ -1,5 +1,6 @@ import { check, param, query, body, ValidationChain } from 'express-validator'; import BaseController from "api/controllers/BaseController"; +import { DATATYPES_LENGTH } from 'data/DataTypes'; export default class ContactsController extends BaseController { /** @@ -7,37 +8,37 @@ export default class ContactsController extends BaseController { */ get contactDTOSchema(): ValidationChain[] { return [ - check('salutation').optional().trim().escape(), - check('first_name').optional().trim().escape(), - check('last_name').optional().trim().escape(), + check('salutation').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('first_name').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('last_name').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('company_name').optional().trim().escape(), - check('display_name').exists().trim().escape(), + check('company_name').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('display_name').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('email').optional({ nullable: true }).normalizeEmail().isEmail(), - check('website').optional().trim().isURL(), - check('work_phone').optional().trim().escape(), - check('personal_phone').optional().trim().escape(), + check('email').optional({ nullable: true }).isString().normalizeEmail().isEmail().isLength({ max: DATATYPES_LENGTH.STRING }), + check('website').optional().isString().trim().isURL().isLength({ max: DATATYPES_LENGTH.STRING }), + check('work_phone').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('personal_phone').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_1').optional().trim().escape(), - check('billing_address_2').optional().trim().escape(), - check('billing_address_city').optional().trim().escape(), - check('billing_address_country').optional().trim().escape(), - check('billing_address_email').optional().isEmail().trim().escape(), - check('billing_address_postcode').optional().trim().escape(), - check('billing_address_phone').optional().trim().escape(), - check('billing_address_state').optional().trim().escape(), + check('billing_address_1').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('billing_address_2').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('billing_address_city').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('billing_address_country').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('billing_address_email').optional().isString().isEmail().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('billing_address_postcode').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('billing_address_phone').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('billing_address_state').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_1').optional().trim().escape(), - check('shipping_address_2').optional().trim().escape(), - check('shipping_address_city').optional().trim().escape(), - check('shipping_address_country').optional().trim().escape(), - check('shipping_address_email').optional().isEmail().trim().escape(), - check('shipping_address_postcode').optional().trim().escape(), - check('shipping_address_phone').optional().trim().escape(), - check('shipping_address_state').optional().trim().escape(), + check('shipping_address_1').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('shipping_address_2').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('shipping_address_city').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('shipping_address_country').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('shipping_address_email').optional().isString().isEmail().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('shipping_address_postcode').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('shipping_address_phone').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('shipping_address_state').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('note').optional().trim().escape(), + check('note').optional().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.TEXT }), check('active').optional().isBoolean().toBoolean(), ]; } @@ -48,8 +49,10 @@ export default class ContactsController extends BaseController { */ get contactNewDTOSchema(): ValidationChain[] { return [ - check('opening_balance').optional({ nullable: true }).isNumeric().toInt(), - body('opening_balance_at').if(body('opening_balance').exists()).exists(), + check('opening_balance').optional({ nullable: true }).isInt({ min: 0, max: DATATYPES_LENGTH.DECIMAL_13_3 }).toInt(), + body('opening_balance_at') + .if(body('opening_balance').exists()).exists() + .isISO8601(), ]; } diff --git a/server/src/api/controllers/Contacts/Customers.ts b/server/src/api/controllers/Contacts/Customers.ts index 433f74b51..80a0d5ed8 100644 --- a/server/src/api/controllers/Contacts/Customers.ts +++ b/server/src/api/controllers/Contacts/Customers.ts @@ -90,7 +90,7 @@ export default class CustomersController extends ContactsController { */ get createCustomerDTOSchema() { return [ - check('currency_code').optional().trim().escape(), + check('currency_code').optional().isString().trim().escape().isLength({ max: 3, min: 3 }), ]; } diff --git a/server/src/api/controllers/Contacts/Vendors.ts b/server/src/api/controllers/Contacts/Vendors.ts index 5a7041823..8c6566194 100644 --- a/server/src/api/controllers/Contacts/Vendors.ts +++ b/server/src/api/controllers/Contacts/Vendors.ts @@ -73,7 +73,12 @@ export default class VendorsController extends ContactsController { */ get vendorDTOSchema(): ValidationChain[] { return [ - check('currency_code').optional().trim().escape(), + check('currency_code') + .optional() + .isString() + .trim() + .escape() + .isLength({ min: 3, max: 3 }), ]; } diff --git a/server/src/api/controllers/ItemCategories.ts b/server/src/api/controllers/ItemCategories.ts index 3b8a63e40..10c9255c0 100644 --- a/server/src/api/controllers/ItemCategories.ts +++ b/server/src/api/controllers/ItemCategories.ts @@ -11,6 +11,7 @@ import { IItemCategoryOTD } from 'interfaces'; import { ServiceError } from 'exceptions'; import BaseController from 'api/controllers/BaseController'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; +import { DATATYPES_LENGTH } from 'data/DataTypes'; @Service() export default class ItemsCategoriesController extends BaseController { @@ -78,26 +79,32 @@ export default class ItemsCategoriesController extends BaseController { */ get categoryValidationSchema() { return [ - check('name').exists().trim().escape(), + check('name') + .exists() + .trim() + .escape() + .isLength({ min: 0, max: DATATYPES_LENGTH.STRING }), check('parent_category_id') .optional({ nullable: true }) - .isNumeric() + .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) .toInt(), check('description') .optional() + .isString() .trim() - .escape(), + .escape() + .isLength({ max: DATATYPES_LENGTH.TEXT }), check('sell_account_id') .optional({ nullable: true }) - .isNumeric() + .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) .toInt(), check('cost_account_id') .optional({ nullable: true }) - .isNumeric() + .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) .toInt(), check('inventory_account_id') .optional({ nullable: true }) - .isNumeric() + .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) .toInt(), ] } diff --git a/server/src/api/controllers/Items.ts b/server/src/api/controllers/Items.ts index e24c0f47d..b0f83caec 100644 --- a/server/src/api/controllers/Items.ts +++ b/server/src/api/controllers/Items.ts @@ -7,6 +7,7 @@ import BaseController from 'api/controllers/BaseController'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; import { ServiceError } from 'exceptions'; import { IItemDTO } from 'interfaces'; +import { DATATYPES_LENGTH } from 'data/DataTypes'; @Service() export default class ItemsController extends BaseController { @@ -78,44 +79,74 @@ export default class ItemsController extends BaseController { */ get validateItemSchema(): ValidationChain[] { return [ - check('name').exists(), - check('type').exists().trim().escape() + check('name').exists().isString().isLength({ max: DATATYPES_LENGTH.STRING }), + check('type').exists() + .isString() + .trim() + .escape() .isIn(['service', 'non-inventory', 'inventory']), - check('code').optional({ nullable: true }).trim().escape(), + check('code') + .optional({ nullable: true }) + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.STRING }), // Purchase attributes. check('purchasable').optional().isBoolean().toBoolean(), check('cost_price') + .optional({ nullable: true }) + .isFloat({ min: 0, max: DATATYPES_LENGTH.DECIMAL_13_3 }) + .toFloat() .if(check('purchasable').equals('true')) - .exists() - .isNumeric() - .toFloat(), + .exists(), check('cost_account_id') + .optional({ nullable: true }) + .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) + .toInt() .if(check('purchasable').equals('true')) - .exists() - .isInt() - .toInt(), + .exists(), // Sell attributes. check('sellable').optional().isBoolean().toBoolean(), check('sell_price') + .optional({ nullable: true }) + .isFloat({ min: 0, max: DATATYPES_LENGTH.DECIMAL_13_3 }) + .toFloat() .if(check('sellable').equals('true')) - .exists() - .isNumeric() - .toFloat(), + .exists(), check('sell_account_id') + .optional({ nullable: true }) + .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) + .toInt() .if(check('sellable').equals('true')) - .exists() - .isInt() - .toInt(), + .exists(), check('inventory_account_id') + .optional({ nullable: true }) + .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) + .toInt() .if(check('type').equals('inventory')) - .exists() - .isInt() + .exists(), + check('sell_description') + .optional({ nullable: true }) + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.TEXT }), + check('cost_description') + .optional({ nullable: true }) + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.TEXT }), + check('category_id') + .optional({ nullable: true }) + .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) .toInt(), - check('sell_description').optional({ nullable: true }).trim().escape(), - check('cost_description').optional({ nullable: true }).trim().escape(), - - check('category_id').optional({ nullable: true }).isInt().toInt(), - check('note').optional(), + check('note') + .optional() + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.TEXT }), check('active').optional().isBoolean().toBoolean(), check('media_ids').optional().isArray(), diff --git a/server/src/api/controllers/ManualJournals.ts b/server/src/api/controllers/ManualJournals.ts index b4ad00dc8..b375d7076 100644 --- a/server/src/api/controllers/ManualJournals.ts +++ b/server/src/api/controllers/ManualJournals.ts @@ -6,6 +6,7 @@ import ManualJournalsService from 'services/ManualJournals/ManualJournalsService import { Inject, Service } from "typedi"; import { ServiceError } from 'exceptions'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; +import { DATATYPES_LENGTH } from 'data/DataTypes'; @Service() export default class ManualJournalsController extends BaseController { @@ -113,30 +114,54 @@ export default class ManualJournalsController extends BaseController { get manualJournalValidationSchema() { return [ check('date').exists().isISO8601(), - check('journal_number').exists().trim().escape(), - check('journal_type').optional({ nullable: true }).trim().escape(), - check('reference').optional({ nullable: true }), - check('description').optional().trim().escape(), + check('journal_number') + .exists() + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('journal_type') + .optional({ nullable: true }) + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('reference') + .optional({ nullable: true }) + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('description') + .optional() + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.TEXT }), check('status').optional().isBoolean().toBoolean(), check('entries').isArray({ min: 2 }), - check('entries.*.index').exists().isNumeric().toInt(), + check('entries.*.index') + .exists() + .isInt({ max: DATATYPES_LENGTH.INT_10 }).toInt(), check('entries.*.credit') .optional({ nullable: true }) .isNumeric() .isDecimal() - .isFloat({ max: 9999999999.999 }) // 13, 3 + .isFloat({ max: DATATYPES_LENGTH.INT_13_3 }) // 13, 3 .toFloat(), check('entries.*.debit') .optional({ nullable: true }) .isNumeric() .isDecimal() - .isFloat({ max: 9999999999.999 }) // 13, 3 + .isFloat({ max: DATATYPES_LENGTH.INT_13_3 }) // 13, 3 .toFloat(), - check('entries.*.account_id').isNumeric().toInt(), - check('entries.*.note').optional(), + check('entries.*.account_id').isInt({ max: DATATYPES_LENGTH.INT_10 }).toInt(), + check('entries.*.note') + .optional() + .isString() + .isLength({ max: DATATYPES_LENGTH.STRING }), check('entries.*.contact_id') .optional({ nullable: true }) - .isNumeric() + .isInt({ max: DATATYPES_LENGTH.INT_10 }) .toInt(), check('entries.*.contact_type').optional().isIn(['vendor', 'customer']), ] diff --git a/server/src/data/DataTypes.js b/server/src/data/DataTypes.js new file mode 100644 index 000000000..d5ab1086f --- /dev/null +++ b/server/src/data/DataTypes.js @@ -0,0 +1,8 @@ + +export const DATATYPES_LENGTH = { + STRING: 255, + TEXT: 65535, + INT_10: 4294967295, + DECIMAL_13_3: 9999999999.999, + DECIMAL_15_5: 999999999999.999, +}; From 62bfc1fc5ca7b0a51248deff1c46e1b62f71137b Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 29 Nov 2020 17:59:52 +0200 Subject: [PATCH 6/6] fix: add count property to item categories list response. --- server/src/services/ItemCategories/ItemCategoriesService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/src/services/ItemCategories/ItemCategoriesService.ts b/server/src/services/ItemCategories/ItemCategoriesService.ts index ce613de3c..d7e9e73b2 100644 --- a/server/src/services/ItemCategories/ItemCategoriesService.ts +++ b/server/src/services/ItemCategories/ItemCategoriesService.ts @@ -269,6 +269,9 @@ export default class ItemCategoriesService implements IItemCategoriesService { const dynamicList = await this.dynamicListService.dynamicList(tenantId, ItemCategory, filter); const itemCategories = await ItemCategory.query().onBuild((query) => { + // Subquery to calculate sumation of assocaited items to the item category. + query.select('*', ItemCategory.relatedQuery('items').count().as('count')); + dynamicList.buildQuery()(query); }); return { itemCategories, filterMeta: dynamicList.getResponseMeta() };