feat: listing vendors and customers.

feat: items service events.
This commit is contained in:
Ahmed Bouhuolia
2020-10-15 21:27:51 +02:00
parent 899ea7a52d
commit 7397afe2a9
11 changed files with 119 additions and 26 deletions

View File

@@ -1,17 +1,21 @@
import { Request, Response, Router, NextFunction } from 'express'; import { Request, Response, Router, NextFunction } from 'express';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { check } from 'express-validator'; import { check, query } from 'express-validator';
import ContactsController from 'api/controllers/Contacts/Contacts'; import ContactsController from 'api/controllers/Contacts/Contacts';
import CustomersService from 'services/Contacts/CustomersService'; import CustomersService from 'services/Contacts/CustomersService';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import { ICustomerNewDTO, ICustomerEditDTO } from 'interfaces'; import { ICustomerNewDTO, ICustomerEditDTO } from 'interfaces';
import asyncMiddleware from 'api/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import DynamicListingService from 'services/DynamicListing/DynamicListService';
@Service() @Service()
export default class CustomersController extends ContactsController { export default class CustomersController extends ContactsController {
@Inject() @Inject()
customersService: CustomersService; customersService: CustomersService;
@Inject()
dynamicListService: DynamicListingService;
/** /**
* Express router. * Express router.
*/ */
@@ -51,10 +55,11 @@ export default class CustomersController extends ContactsController {
this.handlerServiceErrors, this.handlerServiceErrors,
); );
router.get('/', [ router.get('/', [
...this.validateListQuerySchema,
], ],
this.validationResult, this.validationResult,
asyncMiddleware(this.getCustomersList.bind(this)) asyncMiddleware(this.getCustomersList.bind(this)),
this.dynamicListService.handlerErrorsToResponse,
); );
router.get('/:id', [ router.get('/:id', [
...this.specificContactSchema, ...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. * Creates a new customer.
* @param {Request} req * @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) { async getCustomersList(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; 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 { 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) { } catch (error) {
next(error); next(error);
} }

View File

@@ -196,9 +196,15 @@ export default class VendorsController extends ContactsController {
filterRoles: [], filterRoles: [],
...this.matchedBodyData(req), ...this.matchedBodyData(req),
}; };
try { try {
const vendors = await this.vendorsService.getVendorsList(tenantId, vendorsFilter); const { vendors, pagination, filterMeta } = await this.vendorsService.getVendorsList(tenantId, vendorsFilter);
return res.status(200).send({ vendors });
return res.status(200).send({
vendors,
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }

View File

@@ -22,8 +22,10 @@ export default class SubscriptionController {
router.use(AttachCurrentTenantUser); router.use(AttachCurrentTenantUser);
router.use(TenancyMiddleware); router.use(TenancyMiddleware);
router.use('/license', Container.get(PaymentViaLicenseController).router()); router.use(
'/license',
Container.get(PaymentViaLicenseController).router()
);
router.get('/', router.get('/',
asyncMiddleware(this.getSubscriptions.bind(this)) asyncMiddleware(this.getSubscriptions.bind(this))
); );

View File

@@ -41,7 +41,7 @@ export interface IManualJournalsFilter extends IDynamicListFilterDTO {
pageSize: number, pageSize: number,
} }
export interface IManuaLJournalsService { export interface IManualJournalsService {
makeJournalEntries(tenantId: number, manualJournalDTO: IManualJournalDTO, authorizedUser: ISystemUser): Promise<{ manualJournal: IManualJournal }>; makeJournalEntries(tenantId: number, manualJournalDTO: IManualJournalDTO, authorizedUser: ISystemUser): Promise<{ manualJournal: IManualJournal }>;
editJournalEntries(tenantId: number, manualJournalId: number, manualJournalDTO: IManualJournalDTO, authorizedUser): Promise<{ manualJournal: IManualJournal }>; editJournalEntries(tenantId: number, manualJournalId: number, manualJournalDTO: IManualJournalDTO, authorizedUser): Promise<{ manualJournal: IManualJournal }>;
deleteManualJournal(tenantId: number, manualJournalId: number): Promise<void>; deleteManualJournal(tenantId: number, manualJournalId: number): Promise<void>;

View File

@@ -121,4 +121,13 @@ export default class Contact extends TenantModel {
} }
return Promise.all(asyncOpers); return Promise.all(asyncOpers);
} }
static get fields() {
return {
created_at: {
column: 'created_at',
}
};
}
} }

View File

@@ -10,9 +10,6 @@ import {
} from 'decorators/eventDispatcher'; } from 'decorators/eventDispatcher';
import DynamicListingService from 'services/DynamicListing/DynamicListService'; import DynamicListingService from 'services/DynamicListing/DynamicListService';
import events from 'subscribers/events'; import events from 'subscribers/events';
import JournalPoster from 'services/Accounting/JournalPoster';
import { Account } from 'models';
import AccountRepository from 'repositories/AccountRepository';
@Service() @Service()
export default class AccountsService { export default class AccountsService {

View File

@@ -144,15 +144,18 @@ export default class CustomersService {
*/ */
public async getCustomersList( public async getCustomersList(
tenantId: number, tenantId: number,
filter: ICustomersFilter customersFilter: ICustomersFilter
): Promise<{ customers: ICustomer[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> { ): Promise<{ customers: ICustomer[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> {
const { Contact } = this.tenancy.models(tenantId); 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) => { const { results, pagination } = await Contact.query().onBuild((query) => {
query.modify('customer'); query.modify('customer');
dynamicList.buildQuery()(query); dynamicList.buildQuery()(query);
}); }).pagination(
customersFilter.page - 1,
customersFilter.pageSize,
);
return { return {
customers: results, customers: results,

View File

@@ -11,7 +11,9 @@ import {
IVendorNewDTO, IVendorNewDTO,
IVendorEditDTO, IVendorEditDTO,
IVendor, IVendor,
IVendorsFilter IVendorsFilter,
IPaginationMeta,
IFilterMeta
} from 'interfaces'; } from 'interfaces';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import DynamicListingService from 'services/DynamicListing/DynamicListService'; import DynamicListingService from 'services/DynamicListing/DynamicListService';
@@ -226,14 +228,25 @@ export default class VendorsService {
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @param {IVendorsFilter} vendorsFilter - Vendors filter. * @param {IVendorsFilter} vendorsFilter - Vendors filter.
*/ */
public async getVendorsList(tenantId: number, vendorsFilter: IVendorsFilter) { public async getVendorsList(
const { Vendor } = this.tenancy.models(tenantId); 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 { results, pagination } = await Contact.query().onBuild((builder) => {
builder.modify('vendor');
const vendors = await Vendor.query().onBuild((builder) => {
dynamicFilter.buildQuery()(builder); dynamicFilter.buildQuery()(builder);
}); }).pagination(
return vendors; vendorsFilter.page - 1,
vendorsFilter.pageSize,
);
return {
vendors: results,
pagination,
filterMeta: dynamicFilter.getResponseMeta(),
};
} }
} }

View File

@@ -1,5 +1,9 @@
import { Inject } from 'typedi'; import { Inject } from 'typedi';
import { difference } from 'lodash'; import { difference } from 'lodash';
import {
EventDispatcher,
EventDispatcherInterface,
} from 'decorators/eventDispatcher';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import { import {
IItemCategory, IItemCategory,
@@ -10,6 +14,7 @@ import {
} from "interfaces"; } from "interfaces";
import DynamicListingService from 'services/DynamicListing/DynamicListService'; import DynamicListingService from 'services/DynamicListing/DynamicListService';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import events from 'subscribers/events';
const ERRORS = { const ERRORS = {
ITEM_CATEGORIES_NOT_FOUND: 'ITEM_CATEGORIES_NOT_FOUND', ITEM_CATEGORIES_NOT_FOUND: 'ITEM_CATEGORIES_NOT_FOUND',
@@ -33,6 +38,9 @@ export default class ItemCategoriesService implements IItemCategoriesService {
@Inject('logger') @Inject('logger')
logger: any; logger: any;
@EventDispatcher()
eventDispatcher: EventDispatcherInterface;
/** /**
* Retrieve item category or throw not found error. * Retrieve item category or throw not found error.
* @param {number} tenantId * @param {number} tenantId
@@ -92,6 +100,8 @@ export default class ItemCategoriesService implements IItemCategoriesService {
const itemCategoryObj = this.transformOTDToObject(itemCategoryOTD, authorizedUser); const itemCategoryObj = this.transformOTDToObject(itemCategoryOTD, authorizedUser);
const itemCategory = await ItemCategory.query().insert({ ...itemCategoryObj }); 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 }); this.logger.info('[item_category] item category inserted successfully.', { tenantId, itemCategoryOTD });
return itemCategory; return itemCategory;
@@ -188,6 +198,8 @@ export default class ItemCategoriesService implements IItemCategoriesService {
const itemCategoryObj = this.transformOTDToObject(itemCategoryOTD, authorizedUser); const itemCategoryObj = this.transformOTDToObject(itemCategoryOTD, authorizedUser);
const itemCategory = await ItemCategory.query().patchAndFetchById(itemCategoryId, { ...itemCategoryObj }); 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 }); this.logger.info('[item_category] edited successfully.', { tenantId, itemCategoryId, itemCategoryOTD });
return itemCategory; return itemCategory;
@@ -207,6 +219,8 @@ export default class ItemCategoriesService implements IItemCategoriesService {
const { ItemCategory } = this.tenancy.models(tenantId); const { ItemCategory } = this.tenancy.models(tenantId);
await ItemCategory.query().findById(itemCategoryId).delete(); await ItemCategory.query().findById(itemCategoryId).delete();
this.logger.info('[item_category] deleted successfully.', { tenantId, itemCategoryId }); 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 this.unassociateItemsWithCategories(tenantId, itemCategoriesIds);
await ItemCategory.query().whereIn('id', itemCategoriesIds).delete(); 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 }); this.logger.info('[item_category] item categories deleted successfully.', { tenantId, itemCategoriesIds });
} }
} }

View File

@@ -4,7 +4,7 @@ import moment from 'moment';
import { ServiceError } from "exceptions"; import { ServiceError } from "exceptions";
import { import {
IManualJournalDTO, IManualJournalDTO,
IManuaLJournalsService, IManualJournalsService,
IManualJournalsFilter, IManualJournalsFilter,
ISystemUser, ISystemUser,
IManualJournal, IManualJournal,
@@ -33,7 +33,7 @@ const ERRORS = {
}; };
@Service() @Service()
export default class ManualJournalsService implements IManuaLJournalsService { export default class ManualJournalsService implements IManualJournalsService {
@Inject() @Inject()
tenancy: TenancyService; tenancy: TenancyService;

View File

@@ -157,4 +157,11 @@ export default {
onDeleted: 'onVendorDeleted', onDeleted: 'onVendorDeleted',
onBulkDeleted: 'onVendorBulkDeleted', onBulkDeleted: 'onVendorBulkDeleted',
}, },
items: {
onCreated: 'onItemCreated',
onEdited: 'onItemEdited',
onDeleted: 'onItemDeleted',
onBulkDeleted: 'onItemBulkDeleted',
}
} }