mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 04:10:32 +00:00
304 lines
9.0 KiB
TypeScript
304 lines
9.0 KiB
TypeScript
import { Router, Request, Response } from 'express';
|
|
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 asyncMiddleware from 'api/middleware/asyncMiddleware';
|
|
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
|
import ItemsService from 'services/Items/ItemsService';
|
|
|
|
@Service()
|
|
export default class SalesEstimatesController extends BaseController {
|
|
@Inject()
|
|
saleEstimateService: SaleEstimateService;
|
|
|
|
@Inject()
|
|
itemsService: ItemsService;
|
|
|
|
/**
|
|
* Router constructor.
|
|
*/
|
|
router() {
|
|
const router = Router();
|
|
|
|
router.post(
|
|
'/',
|
|
this.estimateValidationSchema,
|
|
this.validationResult,
|
|
asyncMiddleware(this.validateEstimateCustomerExistance.bind(this)),
|
|
asyncMiddleware(this.validateEstimateNumberExistance.bind(this)),
|
|
asyncMiddleware(this.validateEstimateEntriesItemsExistance.bind(this)),
|
|
asyncMiddleware(this.newEstimate.bind(this))
|
|
);
|
|
router.post(
|
|
'/:id', [
|
|
...this.validateSpecificEstimateSchema,
|
|
...this.estimateValidationSchema,
|
|
],
|
|
this.validationResult,
|
|
asyncMiddleware(this.validateEstimateIdExistance.bind(this)),
|
|
asyncMiddleware(this.validateEstimateCustomerExistance.bind(this)),
|
|
asyncMiddleware(this.validateEstimateNumberExistance.bind(this)),
|
|
asyncMiddleware(this.validateEstimateEntriesItemsExistance.bind(this)),
|
|
asyncMiddleware(this.valdiateInvoiceEntriesIdsExistance.bind(this)),
|
|
asyncMiddleware(this.editEstimate.bind(this))
|
|
);
|
|
router.delete(
|
|
'/:id', [
|
|
this.validateSpecificEstimateSchema,
|
|
],
|
|
this.validationResult,
|
|
asyncMiddleware(this.validateEstimateIdExistance.bind(this)),
|
|
asyncMiddleware(this.deleteEstimate.bind(this))
|
|
);
|
|
router.get(
|
|
'/:id',
|
|
this.validateSpecificEstimateSchema,
|
|
this.validationResult,
|
|
asyncMiddleware(this.validateEstimateIdExistance.bind(this)),
|
|
asyncMiddleware(this.getEstimate.bind(this))
|
|
);
|
|
router.get(
|
|
'/',
|
|
this.validateEstimateListSchema,
|
|
this.validationResult,
|
|
asyncMiddleware(this.getEstimates.bind(this))
|
|
);
|
|
return router;
|
|
}
|
|
|
|
/**
|
|
* Estimate validation schema.
|
|
*/
|
|
get estimateValidationSchema() {
|
|
return [
|
|
check('customer_id').exists().isNumeric().toInt(),
|
|
check('estimate_date').exists().isISO8601(),
|
|
check('expiration_date').optional().isISO8601(),
|
|
check('reference').optional(),
|
|
check('estimate_number').exists().trim().escape(),
|
|
|
|
check('entries').exists().isArray({ min: 1 }),
|
|
check('entries.*.index').exists().isNumeric().toInt(),
|
|
check('entries.*.item_id').exists().isNumeric().toInt(),
|
|
check('entries.*.description').optional().trim().escape(),
|
|
check('entries.*.quantity').exists().isNumeric().toInt(),
|
|
check('entries.*.rate').exists().isNumeric().toFloat(),
|
|
check('entries.*.discount').optional().isNumeric().toFloat(),
|
|
|
|
check('note').optional().trim().escape(),
|
|
check('terms_conditions').optional().trim().escape(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Specific sale estimate validation schema.
|
|
*/
|
|
get validateSpecificEstimateSchema() {
|
|
return [
|
|
param('id').exists().isNumeric().toInt(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Sales estimates list validation schema.
|
|
*/
|
|
get validateEstimateListSchema() {
|
|
return [
|
|
query('custom_view_id').optional().isNumeric().toInt(),
|
|
query('stringified_filter_roles').optional().isJSON(),
|
|
query('column_sort_by').optional(),
|
|
query('sort_order').optional().isIn(['desc', 'asc']),
|
|
query('page').optional().isNumeric().toInt(),
|
|
query('page_size').optional().isNumeric().toInt(),
|
|
]
|
|
}
|
|
|
|
/**
|
|
* Validate whether the estimate customer exists on the storage.
|
|
* @param {Request} req
|
|
* @param {Response} res
|
|
* @param {Function} next
|
|
*/
|
|
async validateEstimateCustomerExistance(req: Request, res: Response, next: Function) {
|
|
const estimate = { ...req.body };
|
|
const { Customer } = req.models
|
|
|
|
const foundCustomer = await Customer.query().findById(estimate.customer_id);
|
|
|
|
if (!foundCustomer) {
|
|
return res.status(404).send({
|
|
errors: [{ type: 'CUSTOMER.ID.NOT.FOUND', code: 200 }],
|
|
});
|
|
}
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* Validate the estimate number unique on the storage.
|
|
* @param {Request} req
|
|
* @param {Response} res
|
|
* @param {Function} next
|
|
*/
|
|
async validateEstimateNumberExistance(req: Request, res: Response, next: Function) {
|
|
const estimate = { ...req.body };
|
|
const { tenantId } = req;
|
|
|
|
const isEstNumberUnqiue = await this.saleEstimateService.isEstimateNumberUnique(
|
|
tenantId,
|
|
estimate.estimate_number,
|
|
req.params.id,
|
|
);
|
|
if (isEstNumberUnqiue) {
|
|
return res.boom.badRequest(null, {
|
|
errors: [{ type: 'ESTIMATE.NUMBER.IS.NOT.UNQIUE', code: 300 }],
|
|
});
|
|
}
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* Validate the estimate entries items ids existance on the storage.
|
|
* @param {Request} req
|
|
* @param {Response} res
|
|
* @param {Function} next
|
|
*/
|
|
async validateEstimateEntriesItemsExistance(req: Request, res: Response, next: Function) {
|
|
const tenantId = req.tenantId;
|
|
const estimate = { ...req.body };
|
|
const estimateItemsIds = estimate.entries.map(e => e.item_id);
|
|
|
|
// Validate items ids in estimate entries exists.
|
|
const notFoundItemsIds = await this.itemsService.isItemsIdsExists(tenantId, estimateItemsIds);
|
|
|
|
if (notFoundItemsIds.length > 0) {
|
|
return res.boom.badRequest(null, {
|
|
errors: [{ type: 'ITEMS.IDS.NOT.EXISTS', code: 400 }],
|
|
});
|
|
}
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* Validate whether the sale estimate id exists on the storage.
|
|
* @param {Request} req
|
|
* @param {Response} res
|
|
* @param {Function} next
|
|
*/
|
|
async validateEstimateIdExistance(req: Request, res: Response, next: Function) {
|
|
const { id: estimateId } = req.params;
|
|
const { tenantId } = req;
|
|
|
|
const storedEstimate = await this.saleEstimateService
|
|
.getEstimate(tenantId, estimateId);
|
|
|
|
if (!storedEstimate) {
|
|
return res.status(404).send({
|
|
errors: [{ type: 'SALE.ESTIMATE.ID.NOT.FOUND', code: 200 }],
|
|
});
|
|
}
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* Validate sale invoice entries ids existance on the storage.
|
|
* @param {Request} req
|
|
* @param {Response} res
|
|
* @param {Function} next
|
|
*/
|
|
async valdiateInvoiceEntriesIdsExistance(req: Request, res: Response, next: Function) {
|
|
const { ItemEntry } = req.models;
|
|
|
|
const { id: saleInvoiceId } = req.params;
|
|
const saleInvoice = { ...req.body };
|
|
const entriesIds = saleInvoice.entries
|
|
.filter(e => e.id)
|
|
.map((e) => e.id);
|
|
|
|
const foundEntries = await ItemEntry.query()
|
|
.whereIn('id', entriesIds)
|
|
.where('reference_type', 'SaleInvoice')
|
|
.where('reference_id', saleInvoiceId);
|
|
|
|
if (foundEntries.length > 0) {
|
|
return res.status(400).send({
|
|
errors: [{ type: 'ENTRIES.IDS.NOT.EXISTS', code: 300 }],
|
|
});
|
|
}
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* Handle create a new estimate with associated entries.
|
|
* @param {Request} req -
|
|
* @param {Response} res -
|
|
* @return {Response} res -
|
|
*/
|
|
async newEstimate(req: Request, res: Response) {
|
|
const { tenantId } = req;
|
|
const estimateOTD: ISaleEstimateOTD = matchedData(req, {
|
|
locations: ['body'],
|
|
includeOptionals: true,
|
|
});
|
|
const storedEstimate = await this.saleEstimateService
|
|
.createEstimate(tenantId, estimateOTD);
|
|
|
|
return res.status(200).send({ id: storedEstimate.id });
|
|
}
|
|
|
|
/**
|
|
* Handle update estimate details with associated entries.
|
|
* @param {Request} req
|
|
* @param {Response} res
|
|
*/
|
|
async editEstimate(req: Request, res: Response) {
|
|
const { id: estimateId } = req.params;
|
|
const { tenantId } = req;
|
|
|
|
const estimateOTD: ISaleEstimateOTD = matchedData(req, {
|
|
locations: ['body'],
|
|
includeOptionals: true,
|
|
});
|
|
// Update estimate with associated estimate entries.
|
|
await this.saleEstimateService.editEstimate(tenantId, estimateId, estimateOTD);
|
|
|
|
return res.status(200).send({ id: estimateId });
|
|
}
|
|
|
|
/**
|
|
* Deletes the given estimate with associated entries.
|
|
* @param {Request} req
|
|
* @param {Response} res
|
|
*/
|
|
async deleteEstimate(req: Request, res: Response) {
|
|
const { id: estimateId } = req.params;
|
|
const { tenantId } = req;
|
|
|
|
await this.saleEstimateService.deleteEstimate(tenantId, estimateId);
|
|
|
|
return res.status(200).send({ id: estimateId });
|
|
}
|
|
|
|
/**
|
|
* Retrieve the given estimate with associated entries.
|
|
*/
|
|
async getEstimate(req: Request, res: Response) {
|
|
const { id: estimateId } = req.params;
|
|
const { tenantId } = req;
|
|
|
|
const estimate = await this.saleEstimateService
|
|
.getEstimateWithEntries(tenantId, estimateId);
|
|
|
|
return res.status(200).send({ estimate });
|
|
}
|
|
|
|
/**
|
|
* Retrieve estimates with pagination metadata.
|
|
* @param {Request} req
|
|
* @param {Response} res
|
|
*/
|
|
async getEstimates(req: Request, res: Response) {
|
|
|
|
}
|
|
};
|