diff --git a/server/src/api/controllers/Contacts/Customers.ts b/server/src/api/controllers/Contacts/Customers.ts index 33e16079e..7b0322110 100644 --- a/server/src/api/controllers/Contacts/Customers.ts +++ b/server/src/api/controllers/Contacts/Customers.ts @@ -1,17 +1,21 @@ import { Request, Response, Router, NextFunction } from 'express'; import { Service, Inject } from 'typedi'; -import { check } from 'express-validator'; +import { check, query } from 'express-validator'; import ContactsController from 'api/controllers/Contacts/Contacts'; import CustomersService from 'services/Contacts/CustomersService'; import { ServiceError } from 'exceptions'; import { ICustomerNewDTO, ICustomerEditDTO } from 'interfaces'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; +import DynamicListingService from 'services/DynamicListing/DynamicListService'; @Service() export default class CustomersController extends ContactsController { @Inject() customersService: CustomersService; + @Inject() + dynamicListService: DynamicListingService; + /** * Express router. */ @@ -51,10 +55,11 @@ export default class CustomersController extends ContactsController { this.handlerServiceErrors, ); router.get('/', [ - + ...this.validateListQuerySchema, ], this.validationResult, - asyncMiddleware(this.getCustomersList.bind(this)) + asyncMiddleware(this.getCustomersList.bind(this)), + this.dynamicListService.handlerErrorsToResponse, ); router.get('/:id', [ ...this.specificContactSchema, @@ -76,6 +81,19 @@ export default class CustomersController extends ContactsController { ]; } + get validateListQuerySchema() { + return [ + query('column_sort_by').optional().trim().escape(), + query('sort_order').optional().isIn(['desc', 'asc']), + + query('page').optional().isNumeric().toInt(), + query('page_size').optional().isNumeric().toInt(), + + query('custom_view_id').optional().isNumeric().toInt(), + query('stringified_filter_roles').optional().isJSON(), + ]; + } + /** * Creates a new customer. * @param {Request} req @@ -167,12 +185,34 @@ export default class CustomersController extends ContactsController { } } - + /** + * Retrieve customers paginated and filterable list. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ async getCustomersList(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; + const filter = { + filterRoles: [], + sortOrder: 'asc', + columnSortBy: 'created_at', + page: 1, + pageSize: 12, + ...this.matchedQueryData(req), + }; + if (filter.stringifiedFilterRoles) { + filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles); + } try { - await this.customersService.getCustomersList(tenantId) + const { customers, pagination, filterMeta } = await this.customersService.getCustomersList(tenantId, filter); + + return res.status(200).send({ + customers, + pagination: this.transfromToResponse(pagination), + filter_meta: this.transfromToResponse(filterMeta), + }); } catch (error) { next(error); } diff --git a/server/src/api/controllers/Contacts/Vendors.ts b/server/src/api/controllers/Contacts/Vendors.ts index a6b3b6786..50e3e5b05 100644 --- a/server/src/api/controllers/Contacts/Vendors.ts +++ b/server/src/api/controllers/Contacts/Vendors.ts @@ -196,9 +196,15 @@ export default class VendorsController extends ContactsController { filterRoles: [], ...this.matchedBodyData(req), }; + try { - const vendors = await this.vendorsService.getVendorsList(tenantId, vendorsFilter); - return res.status(200).send({ vendors }); + const { vendors, pagination, filterMeta } = await this.vendorsService.getVendorsList(tenantId, vendorsFilter); + + return res.status(200).send({ + vendors, + pagination: this.transfromToResponse(pagination), + filter_meta: this.transfromToResponse(filterMeta), + }); } catch (error) { next(error); } diff --git a/server/src/api/controllers/Subscription/index.ts b/server/src/api/controllers/Subscription/index.ts index 1485e7249..77e727193 100644 --- a/server/src/api/controllers/Subscription/index.ts +++ b/server/src/api/controllers/Subscription/index.ts @@ -22,8 +22,10 @@ export default class SubscriptionController { router.use(AttachCurrentTenantUser); router.use(TenancyMiddleware); - router.use('/license', Container.get(PaymentViaLicenseController).router()); - + router.use( + '/license', + Container.get(PaymentViaLicenseController).router() + ); router.get('/', asyncMiddleware(this.getSubscriptions.bind(this)) ); diff --git a/server/src/interfaces/ManualJournal.ts b/server/src/interfaces/ManualJournal.ts index 2a1e0fc05..4a861d47a 100644 --- a/server/src/interfaces/ManualJournal.ts +++ b/server/src/interfaces/ManualJournal.ts @@ -41,7 +41,7 @@ export interface IManualJournalsFilter extends IDynamicListFilterDTO { pageSize: number, } -export interface IManuaLJournalsService { +export interface IManualJournalsService { makeJournalEntries(tenantId: number, manualJournalDTO: IManualJournalDTO, authorizedUser: ISystemUser): Promise<{ manualJournal: IManualJournal }>; editJournalEntries(tenantId: number, manualJournalId: number, manualJournalDTO: IManualJournalDTO, authorizedUser): Promise<{ manualJournal: IManualJournal }>; deleteManualJournal(tenantId: number, manualJournalId: number): Promise; diff --git a/server/src/models/Contact.js b/server/src/models/Contact.js index 21505c9da..9fd718ecd 100644 --- a/server/src/models/Contact.js +++ b/server/src/models/Contact.js @@ -121,4 +121,13 @@ export default class Contact extends TenantModel { } return Promise.all(asyncOpers); } + + + static get fields() { + return { + created_at: { + column: 'created_at', + } + }; + } } diff --git a/server/src/services/Accounts/AccountsService.ts b/server/src/services/Accounts/AccountsService.ts index 2c5635073..525be6c56 100644 --- a/server/src/services/Accounts/AccountsService.ts +++ b/server/src/services/Accounts/AccountsService.ts @@ -10,9 +10,6 @@ import { } from 'decorators/eventDispatcher'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; import events from 'subscribers/events'; -import JournalPoster from 'services/Accounting/JournalPoster'; -import { Account } from 'models'; -import AccountRepository from 'repositories/AccountRepository'; @Service() export default class AccountsService { diff --git a/server/src/services/Contacts/CustomersService.ts b/server/src/services/Contacts/CustomersService.ts index bec4b1d50..b98356a60 100644 --- a/server/src/services/Contacts/CustomersService.ts +++ b/server/src/services/Contacts/CustomersService.ts @@ -144,15 +144,18 @@ export default class CustomersService { */ public async getCustomersList( tenantId: number, - filter: ICustomersFilter + customersFilter: ICustomersFilter ): Promise<{ customers: ICustomer[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> { const { Contact } = this.tenancy.models(tenantId); - const dynamicList = await this.dynamicListService.dynamicList(tenantId, Contact, filter); + const dynamicList = await this.dynamicListService.dynamicList(tenantId, Contact, customersFilter); const { results, pagination } = await Contact.query().onBuild((query) => { query.modify('customer'); dynamicList.buildQuery()(query); - }); + }).pagination( + customersFilter.page - 1, + customersFilter.pageSize, + ); return { customers: results, diff --git a/server/src/services/Contacts/VendorsService.ts b/server/src/services/Contacts/VendorsService.ts index 4957656a5..8b5bb4862 100644 --- a/server/src/services/Contacts/VendorsService.ts +++ b/server/src/services/Contacts/VendorsService.ts @@ -11,7 +11,9 @@ import { IVendorNewDTO, IVendorEditDTO, IVendor, - IVendorsFilter + IVendorsFilter, + IPaginationMeta, + IFilterMeta } from 'interfaces'; import { ServiceError } from 'exceptions'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; @@ -226,14 +228,25 @@ export default class VendorsService { * @param {number} tenantId - Tenant id. * @param {IVendorsFilter} vendorsFilter - Vendors filter. */ - public async getVendorsList(tenantId: number, vendorsFilter: IVendorsFilter) { - const { Vendor } = this.tenancy.models(tenantId); + public async getVendorsList( + tenantId: number, + vendorsFilter: IVendorsFilter + ): Promise<{ vendors: IVendor[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> { + const { Contact } = this.tenancy.models(tenantId); + const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, Contact, vendorsFilter); - const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, Vendor, vendorsFilter); - - const vendors = await Vendor.query().onBuild((builder) => { + const { results, pagination } = await Contact.query().onBuild((builder) => { + builder.modify('vendor'); dynamicFilter.buildQuery()(builder); - }); - return vendors; + }).pagination( + vendorsFilter.page - 1, + vendorsFilter.pageSize, + ); + + return { + vendors: results, + pagination, + filterMeta: dynamicFilter.getResponseMeta(), + }; } } diff --git a/server/src/services/ItemCategories/ItemCategoriesService.ts b/server/src/services/ItemCategories/ItemCategoriesService.ts index cacd7a52e..14bbee307 100644 --- a/server/src/services/ItemCategories/ItemCategoriesService.ts +++ b/server/src/services/ItemCategories/ItemCategoriesService.ts @@ -1,5 +1,9 @@ import { Inject } from 'typedi'; import { difference } from 'lodash'; +import { + EventDispatcher, + EventDispatcherInterface, +} from 'decorators/eventDispatcher'; import { ServiceError } from 'exceptions'; import { IItemCategory, @@ -10,6 +14,7 @@ import { } from "interfaces"; import DynamicListingService from 'services/DynamicListing/DynamicListService'; import TenancyService from 'services/Tenancy/TenancyService'; +import events from 'subscribers/events'; const ERRORS = { ITEM_CATEGORIES_NOT_FOUND: 'ITEM_CATEGORIES_NOT_FOUND', @@ -33,6 +38,9 @@ export default class ItemCategoriesService implements IItemCategoriesService { @Inject('logger') logger: any; + @EventDispatcher() + eventDispatcher: EventDispatcherInterface; + /** * Retrieve item category or throw not found error. * @param {number} tenantId @@ -92,6 +100,8 @@ export default class ItemCategoriesService implements IItemCategoriesService { const itemCategoryObj = this.transformOTDToObject(itemCategoryOTD, authorizedUser); const itemCategory = await ItemCategory.query().insert({ ...itemCategoryObj }); + + await this.eventDispatcher.dispatch(events.items.onCreated); this.logger.info('[item_category] item category inserted successfully.', { tenantId, itemCategoryOTD }); return itemCategory; @@ -188,6 +198,8 @@ export default class ItemCategoriesService implements IItemCategoriesService { const itemCategoryObj = this.transformOTDToObject(itemCategoryOTD, authorizedUser); const itemCategory = await ItemCategory.query().patchAndFetchById(itemCategoryId, { ...itemCategoryObj }); + + await this.eventDispatcher.dispatch(events.items.onEdited); this.logger.info('[item_category] edited successfully.', { tenantId, itemCategoryId, itemCategoryOTD }); return itemCategory; @@ -207,6 +219,8 @@ export default class ItemCategoriesService implements IItemCategoriesService { const { ItemCategory } = this.tenancy.models(tenantId); await ItemCategory.query().findById(itemCategoryId).delete(); this.logger.info('[item_category] deleted successfully.', { tenantId, itemCategoryId }); + + await this.eventDispatcher.dispatch(events.items.onDeleted); } /** @@ -267,6 +281,8 @@ export default class ItemCategoriesService implements IItemCategoriesService { await this.unassociateItemsWithCategories(tenantId, itemCategoriesIds); await ItemCategory.query().whereIn('id', itemCategoriesIds).delete(); + + await this.eventDispatcher.dispatch(events.items.onBulkDeleted); this.logger.info('[item_category] item categories deleted successfully.', { tenantId, itemCategoriesIds }); } } \ No newline at end of file diff --git a/server/src/services/ManualJournals/ManualJournalsService.ts b/server/src/services/ManualJournals/ManualJournalsService.ts index 91864a82f..29159fe5b 100644 --- a/server/src/services/ManualJournals/ManualJournalsService.ts +++ b/server/src/services/ManualJournals/ManualJournalsService.ts @@ -4,7 +4,7 @@ import moment from 'moment'; import { ServiceError } from "exceptions"; import { IManualJournalDTO, - IManuaLJournalsService, + IManualJournalsService, IManualJournalsFilter, ISystemUser, IManualJournal, @@ -33,7 +33,7 @@ const ERRORS = { }; @Service() -export default class ManualJournalsService implements IManuaLJournalsService { +export default class ManualJournalsService implements IManualJournalsService { @Inject() tenancy: TenancyService; diff --git a/server/src/subscribers/events.ts b/server/src/subscribers/events.ts index 62c6d2998..c12e58303 100644 --- a/server/src/subscribers/events.ts +++ b/server/src/subscribers/events.ts @@ -157,4 +157,11 @@ export default { onDeleted: 'onVendorDeleted', onBulkDeleted: 'onVendorBulkDeleted', }, + + items: { + onCreated: 'onItemCreated', + onEdited: 'onItemEdited', + onDeleted: 'onItemDeleted', + onBulkDeleted: 'onItemBulkDeleted', + } }