mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 07:40:32 +00:00
feat: listing vendors and customers.
feat: items service events.
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -157,4 +157,11 @@ export default {
|
|||||||
onDeleted: 'onVendorDeleted',
|
onDeleted: 'onVendorDeleted',
|
||||||
onBulkDeleted: 'onVendorBulkDeleted',
|
onBulkDeleted: 'onVendorBulkDeleted',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
items: {
|
||||||
|
onCreated: 'onItemCreated',
|
||||||
|
onEdited: 'onItemEdited',
|
||||||
|
onDeleted: 'onItemDeleted',
|
||||||
|
onBulkDeleted: 'onItemBulkDeleted',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user