add server to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 11:57:50 +02:00
parent 28e309981b
commit 80b97b5fdc
1303 changed files with 137049 additions and 0 deletions

View File

@@ -0,0 +1,547 @@
import { Router, Request, Response, NextFunction } from 'express';
import { check, param, query } from 'express-validator';
import { Service, Inject } from 'typedi';
import { AbilitySubject, BillAction, IBillDTO, IBillEditDTO } from '@/interfaces';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BillsService from '@/services/Purchases/Bills';
import BaseController from '@/api/controllers/BaseController';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { ServiceError } from '@/exceptions';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import BillPaymentsService from '@/services/Purchases/BillPaymentsService';
@Service()
export default class BillsController extends BaseController {
@Inject()
private billsService: BillsService;
@Inject()
private dynamicListService: DynamicListingService;
@Inject()
private billPayments: BillPaymentsService;
/**
* Router constructor.
*/
router() {
const router = Router();
router.post(
'/',
CheckPolicies(BillAction.Create, AbilitySubject.Bill),
[...this.billValidationSchema],
this.validationResult,
asyncMiddleware(this.newBill.bind(this)),
this.handleServiceError
);
router.post(
'/:id/open',
CheckPolicies(BillAction.Edit, AbilitySubject.Bill),
[...this.specificBillValidationSchema],
this.validationResult,
asyncMiddleware(this.openBill.bind(this)),
this.handleServiceError
);
router.post(
'/:id',
CheckPolicies(BillAction.Edit, AbilitySubject.Bill),
[...this.billEditValidationSchema, ...this.specificBillValidationSchema],
this.validationResult,
asyncMiddleware(this.editBill.bind(this)),
this.handleServiceError
);
router.get(
'/due',
CheckPolicies(BillAction.View, AbilitySubject.Bill),
[...this.dueBillsListingValidationSchema],
this.validationResult,
asyncMiddleware(this.getDueBills.bind(this)),
this.handleServiceError
);
router.get(
'/:id',
CheckPolicies(BillAction.View, AbilitySubject.Bill),
[...this.specificBillValidationSchema],
this.validationResult,
asyncMiddleware(this.getBill.bind(this)),
this.handleServiceError
);
router.get(
'/:id/payment-transactions',
[param('id').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.getBillPaymentsTransactions),
this.handleServiceError
);
router.get(
'/',
CheckPolicies(BillAction.View, AbilitySubject.Bill),
[...this.billsListingValidationSchema],
this.validationResult,
asyncMiddleware(this.billsList.bind(this)),
this.handleServiceError,
this.dynamicListService.handlerErrorsToResponse
);
router.delete(
'/:id',
CheckPolicies(BillAction.Delete, AbilitySubject.Bill),
[...this.specificBillValidationSchema],
this.validationResult,
asyncMiddleware(this.deleteBill.bind(this)),
this.handleServiceError
);
return router;
}
/**
* Common validation schema.
*/
get billValidationSchema() {
return [
check('bill_number').exists().trim().escape(),
check('reference_no').optional().trim().escape(),
check('bill_date').exists().isISO8601(),
check('due_date').optional().isISO8601(),
check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('project_id').optional({ nullable: true }).isNumeric().toInt(),
check('note').optional().trim().escape(),
check('open').default(false).isBoolean().toBoolean(),
check('entries').isArray({ min: 1 }),
check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()
.toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.landed_cost')
.optional({ nullable: true })
.isBoolean()
.toBoolean(),
check('entries.*.warehouse_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
];
}
/**
* Common validation schema.
*/
get billEditValidationSchema() {
return [
check('bill_number').optional().trim().escape(),
check('reference_no').optional().trim().escape(),
check('bill_date').exists().isISO8601(),
check('due_date').optional().isISO8601(),
check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('project_id').optional({ nullable: true }).isNumeric().toInt(),
check('note').optional().trim().escape(),
check('open').default(false).isBoolean().toBoolean(),
check('entries').isArray({ min: 1 }),
check('entries.*.id').optional().isNumeric().toInt(),
check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()
.toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.landed_cost')
.optional({ nullable: true })
.isBoolean()
.toBoolean(),
];
}
/**
* Bill validation schema.
*/
get specificBillValidationSchema() {
return [param('id').exists().isNumeric().toInt()];
}
/**
* Bills list validation schema.
*/
get billsListingValidationSchema() {
return [
query('view_slug').optional().isString().trim(),
query('stringified_filter_roles').optional().isJSON(),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('column_sort_by').optional(),
query('sort_order').optional().isIn(['desc', 'asc']),
query('search_keyword').optional({ nullable: true }).isString().trim(),
];
}
get dueBillsListingValidationSchema() {
return [
query('vendor_id').optional().trim().escape(),
query('payment_made_id').optional().trim().escape(),
];
}
/**
* Creates a new bill and records journal transactions.
* @param {Request} req
* @param {Response} res
* @param {Function} next
*/
async newBill(req: Request, res: Response, next: NextFunction) {
const { tenantId, user } = req;
const billDTO: IBillDTO = this.matchedBodyData(req);
try {
const storedBill = await this.billsService.createBill(
tenantId,
billDTO,
user
);
return res.status(200).send({
id: storedBill.id,
message: 'The bill has been created successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Edit bill details with associated entries and rewrites journal transactions.
* @param {Request} req
* @param {Response} res
*/
async editBill(req: Request, res: Response, next: NextFunction) {
const { id: billId } = req.params;
const { tenantId, user } = req;
const billDTO: IBillEditDTO = this.matchedBodyData(req);
try {
await this.billsService.editBill(tenantId, billId, billDTO, user);
return res.status(200).send({
id: billId,
message: 'The bill has been edited successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Open the given bill.
* @param {Request} req -
* @param {Response} res -
*/
async openBill(req: Request, res: Response, next: NextFunction) {
const { id: billId } = req.params;
const { tenantId } = req;
try {
await this.billsService.openBill(tenantId, billId);
return res.status(200).send({
id: billId,
message: 'The bill has been opened successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Retrieve the given bill details with associated item entries.
* @param {Request} req
* @param {Response} res
* @return {Response}
*/
async getBill(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: billId } = req.params;
try {
const bill = await this.billsService.getBill(tenantId, billId);
return res.status(200).send(this.transfromToResponse({ bill }));
} catch (error) {
next(error);
}
}
/**
* Deletes the given bill with associated entries and journal transactions.
* @param {Request} req -
* @param {Response} res -
* @return {Response}
*/
async deleteBill(req: Request, res: Response, next: NextFunction) {
const billId = req.params.id;
const { tenantId } = req;
try {
await this.billsService.deleteBill(tenantId, billId);
return res.status(200).send({
id: billId,
message: 'The given bill deleted successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Listing bills with pagination meta.
* @param {Request} req -
* @param {Response} res -
* @return {Response}
*/
public async billsList(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const filter = {
page: 1,
pageSize: 12,
sortOrder: 'desc',
columnSortBy: 'created_at',
...this.matchedQueryData(req),
};
try {
const { bills, pagination, filterMeta } =
await this.billsService.getBills(tenantId, filter);
return res.status(200).send({
bills: this.transfromToResponse(bills),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) {
next(error);
}
}
/**
* Listing all due bills of the given vendor.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async getDueBills(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { vendorId } = this.matchedQueryData(req);
try {
const bills = await this.billsService.getDueBills(tenantId, vendorId);
return res.status(200).send({ bills });
} catch (error) {
next(error);
}
}
/**
* Retrieve payments transactions of specific bill.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public getBillPaymentsTransactions = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: billId } = req.params;
try {
const billPayments = await this.billPayments.getBillPayments(
tenantId,
billId
);
return res.status(200).send({
data: billPayments,
});
} catch (error) {
next(error);
}
};
/**
* Handles service errors.
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private handleServiceError(
error: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
if (error.errorType === 'BILL_NOT_FOUND') {
return res.status(400).send({
errors: [{ type: 'BILL_NOT_FOUND', code: 100 }],
});
}
if (error.errorType === 'BILL_NUMBER_EXISTS') {
return res.status(400).send({
errors: [{ type: 'BILL.NUMBER.EXISTS', code: 500 }],
});
}
if (error.errorType === 'BILL_VENDOR_NOT_FOUND') {
return res.status(400).send({
errors: [{ type: 'BILL_VENDOR_NOT_FOUND', code: 600 }],
});
}
if (error.errorType === 'BILL_ITEMS_NOT_PURCHASABLE') {
return res.status(400).send({
errors: [{ type: 'BILL_ITEMS_NOT_PURCHASABLE', code: 700 }],
});
}
if (error.errorType === 'NOT_PURCHASE_ABLE_ITEMS') {
return res.status(400).send({
errors: [{ type: 'NOT_PURCHASE_ABLE_ITEMS', code: 800 }],
});
}
if (error.errorType === 'BILL_ITEMS_NOT_FOUND') {
return res.status(400).send({
errors: [{ type: 'ITEMS.IDS.NOT.FOUND', code: 400 }],
});
}
if (error.errorType === 'BILL_ENTRIES_IDS_NOT_FOUND') {
return res.status(400).send({
errors: [{ type: 'BILL_ENTRIES_IDS_NOT_FOUND', code: 900 }],
});
}
if (error.errorType === 'ITEMS_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'ITEMS_NOT_FOUND', code: 1000 }],
});
}
if (error.errorType === 'BILL_ALREADY_OPEN') {
return res.boom.badRequest(null, {
errors: [{ type: 'BILL_ALREADY_OPEN', code: 1100 }],
});
}
if (error.errorType === 'contact_not_found') {
return res.boom.badRequest(null, {
errors: [
{
type: 'VENDOR_NOT_FOUND',
message: 'Vendor not found.',
code: 1200,
},
],
});
}
if (error.errorType === 'BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES') {
return res.status(400).send({
errors: [
{
type: 'BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES',
message:
'Cannot delete bill that has associated payment transactions.',
code: 1200,
},
],
});
}
if (error.errorType === 'BILL_HAS_ASSOCIATED_LANDED_COSTS') {
return res.status(400).send({
errors: [
{
type: 'BILL_HAS_ASSOCIATED_LANDED_COSTS',
message:
'Cannot delete bill that has associated landed cost transactions.',
code: 1300,
},
],
});
}
if (error.errorType === 'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED') {
return res.status(400).send({
errors: [
{
type: 'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED',
code: 1400,
message:
'Bill entries that have landed cost type can not be deleted.',
},
],
});
}
if (
error.errorType === 'LOCATED_COST_ENTRIES_SHOULD_BIGGE_THAN_NEW_ENTRIES'
) {
return res.status(400).send({
errors: [
{
type: 'LOCATED_COST_ENTRIES_SHOULD_BIGGE_THAN_NEW_ENTRIES',
code: 1500,
},
],
});
}
if (error.errorType === 'LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS') {
return res.status(400).send({
errors: [
{
type: 'LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS',
message:
'Landed cost entries should be only with inventory items.',
code: 1600,
},
],
});
}
if (error.errorType === 'BILL_HAS_APPLIED_TO_VENDOR_CREDIT') {
return res.status(400).send({
errors: [{ type: 'BILL_HAS_APPLIED_TO_VENDOR_CREDIT', code: 1700 }],
});
}
if (error.errorType === 'TRANSACTIONS_DATE_LOCKED') {
return res.boom.badRequest(null, {
errors: [
{
type: 'TRANSACTIONS_DATE_LOCKED',
code: 4000,
data: { ...error.payload },
},
],
});
}
}
next(error);
}
}

View File

@@ -0,0 +1,455 @@
import { Router, Request, Response, NextFunction } from 'express';
import { Service, Inject } from 'typedi';
import { check, param, query, ValidationChain } from 'express-validator';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import { ServiceError } from '@/exceptions';
import BaseController from '@/api/controllers/BaseController';
import BillPaymentsService from '@/services/Purchases/BillPayments/BillPayments';
import BillPaymentsPages from '@/services/Purchases/BillPayments/BillPaymentsPages';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import { AbilitySubject, IPaymentMadeAction } from '@/interfaces';
/**
* Bills payments controller.
* @service
*/
@Service()
export default class BillsPayments extends BaseController {
@Inject()
billPaymentService: BillPaymentsService;
@Inject()
dynamicListService: DynamicListingService;
@Inject()
billPaymentsPages: BillPaymentsPages;
/**
* Router constructor.
*/
router() {
const router = Router();
router.post(
'/',
CheckPolicies(IPaymentMadeAction.Create, AbilitySubject.PaymentMade),
[...this.billPaymentSchemaValidation],
this.validationResult,
asyncMiddleware(this.createBillPayment.bind(this)),
this.handleServiceError
);
router.post(
'/:id',
CheckPolicies(IPaymentMadeAction.Edit, AbilitySubject.PaymentMade),
[
...this.billPaymentSchemaValidation,
...this.specificBillPaymentValidateSchema,
],
this.validationResult,
asyncMiddleware(this.editBillPayment.bind(this)),
this.handleServiceError
);
router.delete(
'/:id',
CheckPolicies(IPaymentMadeAction.Delete, AbilitySubject.PaymentMade),
[...this.specificBillPaymentValidateSchema],
this.validationResult,
asyncMiddleware(this.deleteBillPayment.bind(this)),
this.handleServiceError
);
router.get(
'/new-page/entries',
CheckPolicies(IPaymentMadeAction.View, AbilitySubject.PaymentMade),
[query('vendor_id').exists()],
this.validationResult,
asyncMiddleware(this.getBillPaymentNewPageEntries.bind(this)),
this.handleServiceError
);
router.get(
'/:id/edit-page',
CheckPolicies(IPaymentMadeAction.View, AbilitySubject.PaymentMade),
this.specificBillPaymentValidateSchema,
this.validationResult,
asyncMiddleware(this.getBillPaymentEditPage.bind(this)),
this.handleServiceError
);
router.get(
'/:id/bills',
CheckPolicies(IPaymentMadeAction.View, AbilitySubject.PaymentMade),
this.specificBillPaymentValidateSchema,
this.validationResult,
asyncMiddleware(this.getPaymentBills.bind(this)),
this.handleServiceError
);
router.get(
'/:id',
CheckPolicies(IPaymentMadeAction.View, AbilitySubject.PaymentMade),
this.specificBillPaymentValidateSchema,
this.validationResult,
asyncMiddleware(this.getBillPayment.bind(this)),
this.handleServiceError
);
router.get(
'/',
CheckPolicies(IPaymentMadeAction.View, AbilitySubject.PaymentMade),
this.listingValidationSchema,
this.validationResult,
asyncMiddleware(this.getBillsPayments.bind(this)),
this.handleServiceError,
this.dynamicListService.handlerErrorsToResponse
);
return router;
}
/**
* Bill payments schema validation.
* @return {ValidationChain[]}
*/
get billPaymentSchemaValidation(): ValidationChain[] {
return [
check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('payment_account_id').exists().isNumeric().toInt(),
check('payment_number').optional({ nullable: true }).trim().escape(),
check('payment_date').exists(),
check('statement').optional().trim().escape(),
check('reference').optional().trim().escape(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('entries').exists().isArray({ min: 1 }),
check('entries.*.index').optional().isNumeric().toInt(),
check('entries.*.bill_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toInt(),
];
}
/**
* Specific bill payment schema validation.
* @returns {ValidationChain[]}
*/
get specificBillPaymentValidateSchema(): ValidationChain[] {
return [param('id').exists().isNumeric().toInt()];
}
/**
* Bills payment list validation schema.
* @returns {ValidationChain[]}
*/
get listingValidationSchema(): ValidationChain[] {
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(),
query('search_keyword').optional({ nullable: true }).isString().trim(),
];
}
/**
* Retrieve bill payment new page entries.
* @param {Request} req -
* @param {Response} res -
*/
async getBillPaymentNewPageEntries(req: Request, res: Response) {
const { tenantId } = req;
const { vendorId } = this.matchedQueryData(req);
try {
const entries = await this.billPaymentsPages.getNewPageEntries(
tenantId,
vendorId
);
return res.status(200).send({
entries: this.transfromToResponse(entries),
});
} catch (error) {}
}
/**
* Retrieve the bill payment edit page details.
* @param {Request} req
* @param {Response} res
*/
async getBillPaymentEditPage(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: paymentReceiveId } = req.params;
try {
const { billPayment, entries } =
await this.billPaymentsPages.getBillPaymentEditPage(
tenantId,
paymentReceiveId
);
return res.status(200).send({
bill_payment: this.transfromToResponse(billPayment),
entries: this.transfromToResponse(entries),
});
} catch (error) {
next(error);
}
}
/**
* Creates a bill payment.
* @async
* @param {Request} req
* @param {Response} res
* @param {Response} res
*/
async createBillPayment(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const billPaymentDTO = this.matchedBodyData(req);
try {
const billPayment = await this.billPaymentService.createBillPayment(
tenantId,
billPaymentDTO
);
return res.status(200).send({
id: billPayment.id,
message: 'Payment made has been created successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Edits the given bill payment details.
* @param {Request} req
* @param {Response} res
*/
async editBillPayment(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const billPaymentDTO = this.matchedBodyData(req);
const { id: billPaymentId } = req.params;
try {
const paymentMade = await this.billPaymentService.editBillPayment(
tenantId,
billPaymentId,
billPaymentDTO
);
return res.status(200).send({
id: paymentMade.id,
message: 'Payment made has been edited successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Deletes the bill payment and revert the journal
* transactions with accounts balance.
* @param {Request} req -
* @param {Response} res -
* @return {Response} res -
*/
async deleteBillPayment(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: billPaymentId } = req.params;
try {
await this.billPaymentService.deleteBillPayment(tenantId, billPaymentId);
return res.status(200).send({
id: billPaymentId,
message: 'Payment made has been deleted successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Retrieve the bill payment.
* @param {Request} req
* @param {Response} res
*/
async getBillPayment(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: billPaymentId } = req.params;
try {
const billPayment = await this.billPaymentService.getBillPayment(
tenantId,
billPaymentId
);
return res.status(200).send({
bill_payment: this.transfromToResponse(billPayment),
});
} catch (error) {
next(error);
}
}
/**
* Retrieve associated bills for the given payment made.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async getPaymentBills(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: billPaymentId } = req.params;
try {
const bills = await this.billPaymentService.getPaymentBills(
tenantId,
billPaymentId
);
return res.status(200).send({ bills });
} catch (error) {
next(error);
}
}
/**
* Retrieve bills payments listing with pagination metadata.
* @param {Request} req -
* @param {Response} res -
* @return {Response}
*/
async getBillsPayments(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const billPaymentsFilter = {
page: 1,
pageSize: 12,
filterRoles: [],
sortOrder: 'desc',
columnSortBy: 'created_at',
...this.matchedQueryData(req),
};
try {
const { billPayments, pagination, filterMeta } =
await this.billPaymentService.listBillPayments(
tenantId,
billPaymentsFilter
);
return res.status(200).send({
bill_payments: this.transfromToResponse(billPayments),
pagination: this.transfromToResponse(pagination),
filter_meta: this.transfromToResponse(filterMeta),
});
} catch (error) {
next(error);
}
}
/**
* Handle service errors.
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
handleServiceError(
error: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
if (error.errorType === 'PAYMENT_MADE_NOT_FOUND') {
return res.status(404).send({
message: 'Payment made not found.',
errors: [{ type: 'BILL_NOT_FOUND', code: 100 }],
});
}
if (error.errorType === 'VENDOR_NOT_FOUND') {
return res.status(400).send({
errors: [{ type: 'BILL.PAYMENT.VENDOR.NOT.FOUND', code: 200 }],
});
}
if (error.errorType === 'PAYMENT_ACCOUNT_NOT_CURRENT_ASSET_TYPE') {
return res.status(400).send({
errors: [
{ type: 'PAYMENT_ACCOUNT.NOT.CURRENT_ASSET.TYPE', code: 300 },
],
});
}
if (error.errorType === 'BILL_PAYMENT_NUMBER_NOT_UNQIUE') {
return res.status(400).send({
errors: [{ type: 'PAYMENT.NUMBER.NOT.UNIQUE', code: 400 }],
});
}
if (error.errorType === 'PAYMENT_ACCOUNT_NOT_FOUND') {
return res.status(400).send({
errors: [{ type: 'PAYMENT.ACCOUNT.NOT.FOUND', code: 500 }],
});
}
if (error.errorType === 'PAYMENT_ACCOUNT_NOT_FOUND') {
return res.status(400).send({
errors: [{ type: 'PAYMENT.ACCOUNT.NOT.FOUND', code: 600 }],
});
}
if (error.errorType === '') {
return res.status(400).send({
errors: [{ type: 'BILLS.IDS.NOT.EXISTS', code: 700 }],
});
}
if (error.errorType === 'BILL_PAYMENT_ENTRIES_NOT_FOUND') {
return res.status(400).send({
errors: [{ type: 'ENTEIES.IDS.NOT.FOUND', code: 800 }],
});
}
if (error.errorType === 'INVALID_BILL_PAYMENT_AMOUNT') {
return res.status(400).send({
errors: [{ type: 'INVALID_BILL_PAYMENT_AMOUNT', code: 900 }],
});
}
if (error.errorType === 'BILL_ENTRIES_IDS_NOT_FOUND') {
return res.status(400).send({
errors: [{ type: 'BILLS_NOT_FOUND', code: 1000 }],
});
}
if (error.errorType === 'PAYMENT_NUMBER_SHOULD_NOT_MODIFY') {
return res.status(400).send({
errors: [{ type: 'PAYMENT_NUMBER_SHOULD_NOT_MODIFY', code: 1100 }],
});
}
if (error.errorType === 'BILLS_NOT_OPENED_YET') {
return res.status(400).send({
errors: [
{
type: 'BILLS_NOT_OPENED_YET',
message: 'The given bills are not opened yet.',
code: 1200,
},
],
});
}
if (error.errorType === 'TRANSACTIONS_DATE_LOCKED') {
return res.boom.badRequest(null, {
errors: [
{
type: 'TRANSACTIONS_DATE_LOCKED',
code: 4000,
data: { ...error.payload },
},
],
});
}
if (error.errorType === 'WITHDRAWAL_ACCOUNT_CURRENCY_INVALID') {
return res.boom.badRequest(null, {
errors: [{ type: 'WITHDRAWAL_ACCOUNT_CURRENCY_INVALID', code: 1300 }],
});
}
}
next(error);
}
}

View File

@@ -0,0 +1,305 @@
import { Router, Request, Response, NextFunction } from 'express';
import { check, param, query } from 'express-validator';
import { Service, Inject } from 'typedi';
import { ServiceError } from '@/exceptions';
import BillAllocatedCostTransactions from '@/services/Purchases/LandedCost/BillAllocatedLandedCostTransactions';
import BaseController from '../BaseController';
import AllocateLandedCost from '@/services/Purchases/LandedCost/AllocateLandedCost';
import RevertAllocatedLandedCost from '@/services/Purchases/LandedCost/RevertAllocatedLandedCost';
import LandedCostTranasctions from '@/services/Purchases/LandedCost/LandedCostTransactions';
@Service()
export default class BillAllocateLandedCost extends BaseController {
@Inject()
allocateLandedCost: AllocateLandedCost;
@Inject()
billAllocatedCostTransactions: BillAllocatedCostTransactions;
@Inject()
revertAllocatedLandedCost: RevertAllocatedLandedCost;
@Inject()
landedCostTranasctions: LandedCostTranasctions;
/**
* Router constructor.
*/
public router() {
const router = Router();
router.post(
'/bills/:billId/allocate',
[
check('transaction_id').exists().isInt(),
check('transaction_type').exists().isIn(['Expense', 'Bill']),
check('transaction_entry_id').exists().isInt(),
check('allocation_method').exists().isIn(['value', 'quantity']),
check('description').optional({ nullable: true }),
check('items').isArray({ min: 1 }),
check('items.*.entry_id').isInt(),
check('items.*.cost').isDecimal(),
],
this.validationResult,
this.calculateLandedCost,
this.handleServiceErrors
);
router.delete(
'/:allocatedLandedCostId',
[param('allocatedLandedCostId').exists().isInt()],
this.validationResult,
this.deleteAllocatedLandedCost,
this.handleServiceErrors
);
router.get(
'/transactions',
[query('transaction_type').exists().isIn(['Expense', 'Bill'])],
this.validationResult,
this.getLandedCostTransactions,
this.handleServiceErrors
);
router.get(
'/bills/:billId/transactions',
[param('billId').exists()],
this.validationResult,
this.getBillLandedCostTransactions,
this.handleServiceErrors
);
return router;
}
/**
* Retrieve the landed cost transactions of the given query.
* @param {Request} req - Request
* @param {Response} res - Response.
* @param {NextFunction} next - Next function.
*/
private getLandedCostTransactions = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const query = this.matchedQueryData(req);
try {
const transactions =
await this.landedCostTranasctions.getLandedCostTransactions(
tenantId,
query
);
return res.status(200).send({ transactions });
} catch (error) {
next(error);
}
};
/**
* Allocate landed cost.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Response}
*/
public calculateLandedCost = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { billId: purchaseInvoiceId } = req.params;
const landedCostDTO = this.matchedBodyData(req);
try {
const billLandedCost = await this.allocateLandedCost.allocateLandedCost(
tenantId,
landedCostDTO,
purchaseInvoiceId
);
return res.status(200).send({
id: billLandedCost.id,
message: 'The items cost are located successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Deletes the allocated landed cost.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Response}
*/
public deleteAllocatedLandedCost = async (
req: Request,
res: Response,
next: NextFunction
): Promise<Response> => {
const { tenantId } = req;
const { allocatedLandedCostId } = req.params;
try {
await this.revertAllocatedLandedCost.deleteAllocatedLandedCost(
tenantId,
allocatedLandedCostId
);
return res.status(200).send({
id: allocatedLandedCostId,
message: 'The allocated landed cost are delete successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieve the list unlocated landed costs.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public listLandedCosts = async (
req: Request,
res: Response,
next: NextFunction
) => {
const query = this.matchedQueryData(req);
const { tenantId } = req;
try {
const transactions =
await this.landedCostTranasctions.getLandedCostTransactions(
tenantId,
query
);
return res.status(200).send({ transactions });
} catch (error) {
next(error);
}
};
/**
* Retrieve the bill landed cost transactions.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public getBillLandedCostTransactions = async (
req: Request,
res: Response,
next: NextFunction
): Promise<Response> => {
const { tenantId } = req;
const { billId } = req.params;
try {
const transactions =
await this.billAllocatedCostTransactions.getBillLandedCostTransactions(
tenantId,
billId
);
return res.status(200).send({
billId,
transactions: this.transfromToResponse(transactions),
});
} catch (error) {
next(error);
}
};
/**
* Handle service errors.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @param {Error} error
*/
public handleServiceErrors(
error: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
if (error.errorType === 'BILL_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'BILL_NOT_FOUND',
message: 'The give bill id not found.',
code: 100,
},
],
});
}
if (error.errorType === 'LANDED_COST_TRANSACTION_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'LANDED_COST_TRANSACTION_NOT_FOUND',
message: 'The given landed cost transaction id not found.',
code: 200,
},
],
});
}
if (error.errorType === 'LANDED_COST_ENTRY_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'LANDED_COST_ENTRY_NOT_FOUND',
message: 'The given landed cost tranasction entry id not found.',
code: 300,
},
],
});
}
if (error.errorType === 'COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT') {
return res.status(400).send({
errors: [
{
type: 'COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT',
code: 400,
},
],
});
}
if (error.errorType === 'LANDED_COST_ITEMS_IDS_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'LANDED_COST_ITEMS_IDS_NOT_FOUND',
message: 'The given entries ids of purchase invoice not found.',
code: 500,
},
],
});
}
if (error.errorType === 'BILL_LANDED_COST_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'BILL_LANDED_COST_NOT_FOUND',
message: 'The given bill located landed cost not found.',
code: 600,
},
],
});
}
if (error.errorType === 'COST_TRASNACTION_NOT_FOUND') {
return res.status(400).send({
errors: [{ type: 'COST_TRASNACTION_NOT_FOUND', code: 500 }],
});
}
}
next(error);
}
}

View File

@@ -0,0 +1,660 @@
import { Router, Request, Response, NextFunction } from 'express';
import { check, param, query } from 'express-validator';
import { Service, Inject } from 'typedi';
import {
AbilitySubject,
IVendorCreditCreateDTO,
IVendorCreditEditDTO,
VendorCreditAction,
} from '@/interfaces';
import BaseController from '@/api/controllers/BaseController';
import TenancyService from '@/services/Tenancy/TenancyService';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { ServiceError } from '@/exceptions';
import CheckPolicies from '@/api/middleware/CheckPolicies';
import CreateVendorCredit from '@/services/Purchases/VendorCredits/CreateVendorCredit';
import EditVendorCredit from '@/services/Purchases/VendorCredits/EditVendorCredit';
import DeleteVendorCredit from '@/services/Purchases/VendorCredits/DeleteVendorCredit';
import GetVendorCredit from '@/services/Purchases/VendorCredits/GetVendorCredit';
import ListVendorCredits from '@/services/Purchases/VendorCredits/ListVendorCredits';
import CreateRefundVendorCredit from '@/services/Purchases/VendorCredits/RefundVendorCredits/CreateRefundVendorCredit';
import DeleteRefundVendorCredit from '@/services/Purchases/VendorCredits/RefundVendorCredits/DeleteRefundVendorCredit';
import ListVendorCreditRefunds from '@/services/Purchases/VendorCredits/RefundVendorCredits/ListRefundVendorCredits';
import OpenVendorCredit from '@/services/Purchases/VendorCredits/OpenVendorCredit';
import GetRefundVendorCredit from '@/services/Purchases/VendorCredits/RefundVendorCredits/GetRefundVendorCredit';
@Service()
export default class VendorCreditController extends BaseController {
@Inject()
createVendorCreditService: CreateVendorCredit;
@Inject()
editVendorCreditService: EditVendorCredit;
@Inject()
deleteVendorCreditService: DeleteVendorCredit;
@Inject()
getVendorCreditService: GetVendorCredit;
@Inject()
listCreditNotesService: ListVendorCredits;
@Inject()
tenancy: TenancyService;
@Inject()
dynamicListService: DynamicListingService;
@Inject()
createRefundCredit: CreateRefundVendorCredit;
@Inject()
deleteRefundCredit: DeleteRefundVendorCredit;
@Inject()
listRefundCredit: ListVendorCreditRefunds;
@Inject()
openVendorCreditService: OpenVendorCredit;
@Inject()
getRefundCredit: GetRefundVendorCredit;
/**
* Router constructor.
*/
router() {
const router = Router();
router.post(
'/',
CheckPolicies(VendorCreditAction.Create, AbilitySubject.VendorCredit),
this.vendorCreditCreateDTOSchema,
this.validationResult,
this.asyncMiddleware(this.newVendorCredit),
this.handleServiceError
);
router.post(
'/:id',
CheckPolicies(VendorCreditAction.Edit, AbilitySubject.VendorCredit),
this.vendorCreditEditDTOSchema,
this.validationResult,
this.asyncMiddleware(this.editVendorCredit),
this.handleServiceError
);
router.get(
'/:id',
CheckPolicies(VendorCreditAction.View, AbilitySubject.VendorCredit),
[],
this.validationResult,
this.asyncMiddleware(this.getVendorCredit),
this.handleServiceError
);
router.get(
'/',
CheckPolicies(VendorCreditAction.View, AbilitySubject.VendorCredit),
this.billsListingValidationSchema,
this.validationResult,
this.asyncMiddleware(this.getVendorCreditsList),
this.handleServiceError,
this.dynamicListService.handlerErrorsToResponse
);
router.delete(
'/:id',
CheckPolicies(VendorCreditAction.Delete, AbilitySubject.VendorCredit),
this.deleteDTOValidationSchema,
this.validationResult,
this.asyncMiddleware(this.deleteVendorCredit),
this.handleServiceError
);
router.post(
'/:id/open',
[param('id').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.openVendorCreditTransaction),
this.handleServiceError
);
router.get(
'/:id/refund',
[param('id').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.vendorCreditRefundTransactions),
this.handleServiceError
);
router.post(
'/:id/refund',
CheckPolicies(VendorCreditAction.Refund, AbilitySubject.VendorCredit),
this.vendorCreditRefundValidationSchema,
this.validationResult,
this.asyncMiddleware(this.refundVendorCredit),
this.handleServiceError
);
router.get(
'/refunds/:refundId',
this.getRefundCreditTransactionSchema,
this.validationResult,
this.asyncMiddleware(this.getRefundCreditTransaction),
this.handleServiceError
);
router.delete(
'/refunds/:refundId',
CheckPolicies(VendorCreditAction.Refund, AbilitySubject.VendorCredit),
this.deleteRefundVendorCreditSchema,
this.validationResult,
this.asyncMiddleware(this.deleteRefundVendorCredit),
this.handleServiceError
);
return router;
}
/**
* Common validation schema.
*/
get vendorCreditCreateDTOSchema() {
return [
check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('vendor_credit_number')
.optional({ nullable: true })
.trim()
.escape(),
check('reference_no').optional().trim().escape(),
check('vendor_credit_date').exists().isISO8601().toDate(),
check('note').optional().trim().escape(),
check('open').default(false).isBoolean().toBoolean(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('entries').isArray({ min: 1 }),
check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()
.toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.warehouse_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
];
}
/**
* Common validation schema.
*/
get vendorCreditEditDTOSchema() {
return [
param('id').exists().isNumeric().toInt(),
check('vendor_id').exists().isNumeric().toInt(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('vendor_credit_number')
.optional({ nullable: true })
.trim()
.escape(),
check('reference_no').optional().trim().escape(),
check('vendor_credit_date').exists().isISO8601().toDate(),
check('note').optional().trim().escape(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('entries').isArray({ min: 1 }),
check('entries.*.id').optional().isNumeric().toInt(),
check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()
.toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.warehouse_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
];
}
/**
* Bills list validation schema.
*/
get billsListingValidationSchema() {
return [
query('view_slug').optional().isString().trim(),
query('stringified_filter_roles').optional().isJSON(),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('column_sort_by').optional(),
query('sort_order').optional().isIn(['desc', 'asc']),
query('search_keyword').optional({ nullable: true }).isString().trim(),
];
}
/**
*
*/
get deleteDTOValidationSchema() {
return [param('id').exists().isNumeric().toInt()];
}
get getRefundCreditTransactionSchema() {
return [param('refundId').exists().isNumeric().toInt()];
}
get deleteRefundVendorCreditSchema() {
return [];
}
/**
* Refund vendor credit validation schema.
*/
get vendorCreditRefundValidationSchema() {
return [
check('deposit_account_id').exists().isNumeric().toInt(),
check('description').exists(),
check('amount').exists().isNumeric().toFloat(),
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
check('reference_no').optional(),
check('date').exists().isISO8601().toDate(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
/**
* Creates a new bill and records journal transactions.
* @param {Request} req
* @param {Response} res
* @param {Function} next
*/
private newVendorCredit = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId, user } = req;
const vendorCreditCreateDTO: IVendorCreditCreateDTO =
this.matchedBodyData(req);
try {
const vendorCredit = await this.createVendorCreditService.newVendorCredit(
tenantId,
vendorCreditCreateDTO,
user
);
return res.status(200).send({
id: vendorCredit.id,
message: 'The vendor credit has been created successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Edit bill details with associated entries and rewrites journal transactions.
* @param {Request} req
* @param {Response} res
*/
private editVendorCredit = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { id: billId } = req.params;
const { tenantId, user } = req;
const vendorCreditEditDTO: IVendorCreditEditDTO = this.matchedBodyData(req);
try {
await this.editVendorCreditService.editVendorCredit(
tenantId,
billId,
vendorCreditEditDTO,
user
);
return res.status(200).send({
id: billId,
message: 'The vendor credit has been edited successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieve the given bill details with associated item entries.
* @param {Request} req
* @param {Response} res
* @return {Response}
*/
private getVendorCredit = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: billId } = req.params;
try {
const data = await this.getVendorCreditService.getVendorCredit(
tenantId,
billId
);
return res.status(200).send({ data });
} catch (error) {
next(error);
}
};
/**
* Deletes the given bill with associated entries and journal transactions.
* @param {Request} req -
* @param {Response} res -
* @return {Response}
*/
private deleteVendorCredit = async (
req: Request,
res: Response,
next: NextFunction
) => {
const vendorCreditId = req.params.id;
const { tenantId } = req;
try {
await this.deleteVendorCreditService.deleteVendorCredit(
tenantId,
vendorCreditId
);
return res.status(200).send({
id: vendorCreditId,
message: 'The given vendor credit has been deleted successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieve vendor credits list.
* @param req
* @param res
* @param next
* @returns
*/
private getVendorCreditsList = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const filter = {
sortOrder: 'desc',
columnSortBy: 'created_at',
page: 1,
pageSize: 12,
...this.matchedQueryData(req),
};
try {
const { vendorCredits, pagination, filterMeta } =
await this.listCreditNotesService.getVendorCredits(tenantId, filter);
return res.status(200).send({ vendorCredits, pagination, filterMeta });
} catch (error) {
next(error);
}
};
/**
* Refunds vendor credit.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns
*/
private refundVendorCredit = async (
req: Request,
res: Response,
next: NextFunction
) => {
const refundDTO = this.matchedBodyData(req);
const { id: vendorCreditId } = req.params;
const { tenantId } = req;
try {
const refundVendorCredit = await this.createRefundCredit.createRefund(
tenantId,
vendorCreditId,
refundDTO
);
return res.status(200).send({
id: refundVendorCredit.id,
message: 'The vendor credit refund has been created successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Deletes refund vendor credit transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private deleteRefundVendorCredit = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { refundId: vendorCreditId } = req.params;
const { tenantId } = req;
try {
await this.deleteRefundCredit.deleteRefundVendorCreditRefund(
tenantId,
vendorCreditId
);
return res.status(200).send({
id: vendorCreditId,
message: 'The vendor credit refund has been deleted successfully.',
});
} catch (error) {
next(error);
}
};
/**
* Retrieve refunds transactions associated to vendor credit transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private vendorCreditRefundTransactions = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { id: vendorCreditId } = req.params;
const { tenantId } = req;
try {
const transactions = await this.listRefundCredit.getVendorCreditRefunds(
tenantId,
vendorCreditId
);
return res.status(200).send({ data: transactions });
} catch (error) {
next(error);
}
};
/**
* Open vendor credit transaction.
* @param {Error} error
* @param {Request} req
* @param {Response} res
*/
private openVendorCreditTransaction = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { id: vendorCreditId } = req.params;
const { tenantId } = req;
try {
await this.openVendorCreditService.openVendorCredit(
tenantId,
vendorCreditId
);
return res.status(200).send({
id: vendorCreditId,
message: 'The vendor credit has been opened successfully.',
});
} catch (error) {
next(error);
}
};
/**
*
* @param req
* @param res
* @param next
* @returns
*/
private getRefundCreditTransaction = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { refundId } = req.params;
const { tenantId } = req;
try {
const refundCredit =
await this.getRefundCredit.getRefundCreditTransaction(
tenantId,
refundId
);
return res.status(200).send({ refundCredit });
} catch (error) {
next(error);
}
};
/**
* Handles service errors.
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private handleServiceError(
error: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
if (error.errorType === 'ENTRIES_ITEMS_IDS_NOT_EXISTS') {
return res.boom.badRequest(null, {
errors: [{ type: 'ENTRIES_ITEMS_IDS_NOT_EXISTS', code: 100 }],
});
}
if (error.errorType === 'ENTRIES_IDS_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'ENTRIES_IDS_NOT_FOUND', code: 200 }],
});
}
if (error.errorType === 'contact_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'VENDOR_NOT_FOUND', code: 300 }],
});
}
if (error.errorType === 'ITEMS_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'ITEMS_NOT_FOUND', code: 400 }],
});
}
if (error.errorType === 'VENDOR_CREDIT_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'VENDOR_CREDIT_NOT_FOUND', code: 500 }],
});
}
if (error.errorType === 'DEPOSIT_ACCOUNT_INVALID_TYPE') {
return res.boom.badRequest(null, {
errors: [{ type: 'DEPOSIT_ACCOUNT_INVALID_TYPE', code: 600 }],
});
}
if (error.errorType === 'REFUND_VENDOR_CREDIT_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'REFUND_VENDOR_CREDIT_NOT_FOUND', code: 700 }],
});
}
if (error.errorType === 'VENDOR_CREDIT_HAS_NO_CREDITS_REMAINING') {
return res.boom.badRequest(null, {
errors: [
{ type: 'VENDOR_CREDIT_HAS_NO_CREDITS_REMAINING', code: 800 },
],
});
}
if (error.errorType === 'VENDOR_CREDIT_ALREADY_OPENED') {
return res.boom.badRequest(null, {
errors: [{ type: 'VENDOR_CREDIT_ALREADY_OPENED', code: 900 }],
});
}
if (error.errorType === 'VENDOR_CREDIT_HAS_APPLIED_BILLS') {
return res.boom.badRequest(null, {
errors: [{ type: 'VENDOR_CREDIT_HAS_APPLIED_BILLS', code: 1000 }],
});
}
if (error.errorType === 'VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS') {
return res.boom.badRequest(null, {
errors: [
{ type: 'VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS', code: 1200 },
],
});
}
if (error.errorType === 'TRANSACTIONS_DATE_LOCKED') {
return res.boom.badRequest(null, {
errors: [
{
type: 'TRANSACTIONS_DATE_LOCKED',
code: 4000,
data: { ...error.payload },
},
],
});
}
}
next(error);
}
}

View File

@@ -0,0 +1,226 @@
import { Service, Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { param, check } from 'express-validator';
import BaseController from '../BaseController';
import ApplyVendorCreditToBills from '@/services/Purchases/VendorCredits/ApplyVendorCreditToBills/ApplyVendorCreditToBills';
import DeleteApplyVendorCreditToBill from '@/services/Purchases/VendorCredits/ApplyVendorCreditToBills/DeleteApplyVendorCreditToBill';
import { ServiceError } from '@/exceptions';
import GetAppliedBillsToVendorCredit from '@/services/Purchases/VendorCredits/ApplyVendorCreditToBills/GetAppliedBillsToVendorCredit';
import GetVendorCreditToApplyBills from '@/services/Purchases/VendorCredits/ApplyVendorCreditToBills/GetVendorCreditToApplyBills';
@Service()
export default class VendorCreditApplyToBills extends BaseController {
@Inject()
applyVendorCreditToBillsService: ApplyVendorCreditToBills;
@Inject()
deleteAppliedCreditToBillsService: DeleteApplyVendorCreditToBill;
@Inject()
getAppliedBillsToCreditService: GetAppliedBillsToVendorCredit;
@Inject()
getCreditToApplyBillsService: GetVendorCreditToApplyBills;
/**
*
* @returns
*/
router() {
const router = Router();
router.post(
'/:id/apply-to-bills',
[
param('id').exists().isNumeric().toInt(),
check('entries').isArray({ min: 1 }),
check('entries.*.bill_id').exists().isInt().toInt(),
check('entries.*.amount').exists().isNumeric().toFloat(),
],
this.validationResult,
this.asyncMiddleware(this.applyVendorCreditToBills),
this.handleServiceErrors
);
router.delete(
'/applied-to-bills/:applyId',
[param('applyId').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.deleteApplyCreditToBill),
this.handleServiceErrors
);
router.get(
'/:id/apply-to-bills',
[param('id').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.getVendorCreditAssociatedBillsToApply),
this.handleServiceErrors
);
router.get(
'/:id/applied-bills',
[param('id').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.getVendorCreditAppliedBills),
this.handleServiceErrors
);
return router;
}
/**
* Apply vendor credit to the given bills.
* @param {Request}
* @param {Response}
* @param {NextFunction}
*/
public applyVendorCreditToBills = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: vendorCreditId } = req.params;
const applyCreditToBillsDTO = this.matchedBodyData(req);
try {
await this.applyVendorCreditToBillsService.applyVendorCreditToBills(
tenantId,
vendorCreditId,
applyCreditToBillsDTO
);
return res.status(200).send({
id: vendorCreditId,
message:
'The vendor credit has been applied to the given bills successfully',
});
} catch (error) {
next(error);
}
};
/**
* Deletes vendor credit applied to bill transaction.
* @param {Request}
* @param {Response}
* @param {NextFunction}
*/
public deleteApplyCreditToBill = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { applyId } = req.params;
try {
await this.deleteAppliedCreditToBillsService.deleteApplyVendorCreditToBills(
tenantId,
applyId
);
return res.status(200).send({
id: applyId,
message:
'The applied vendor credit to bill has been deleted successfully',
});
} catch (error) {
next(error);
}
};
/**
*
*/
public getVendorCreditAssociatedBillsToApply = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: vendorCreditId } = req.params;
try {
const bills =
await this.getCreditToApplyBillsService.getCreditToApplyBills(
tenantId,
vendorCreditId
);
return res.status(200).send({ data: bills });
} catch (error) {
next(error);
}
};
/**
*
*/
public getVendorCreditAppliedBills = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: vendorCreditId } = req.params;
try {
const appliedBills =
await this.getAppliedBillsToCreditService.getAppliedBills(
tenantId,
vendorCreditId
);
return res.status(200).send({ data: appliedBills });
} catch (error) {
next(error);
}
};
/**
* Handles service errors.
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param next
*/
handleServiceErrors(
error: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
if (error.errorType === 'VENDOR_CREDIT_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'VENDOR_CREDIT_NOT_FOUND', code: 100 }],
});
}
if (error.errorType === 'BILL_ENTRIES_IDS_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'BILL_ENTRIES_IDS_NOT_FOUND', code: 200 }],
});
}
if (error.errorType === 'BILLS_NOT_OPENED_YET') {
return res.boom.badRequest(null, {
errors: [{ type: 'BILLS_NOT_OPENED_YET', code: 300 }],
});
}
if (error.errorType === 'BILLS_HAS_NO_REMAINING_AMOUNT') {
return res.boom.badRequest(null, {
errors: [{ type: 'BILLS_HAS_NO_REMAINING_AMOUNT', code: 400 }],
});
}
if (error.errorType === 'VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT') {
return res.boom.badRequest(null, {
errors: [
{ type: 'VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT', code: 500 },
],
});
}
if (error.errorType === 'VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [
{ type: 'VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND', code: 600 },
],
});
}
}
next(error);
}
}

View File

@@ -0,0 +1,25 @@
import { Router } from 'express';
import { Container, Service } from 'typedi';
import Bills from '@/api/controllers/Purchases/Bills';
import BillPayments from '@/api/controllers/Purchases/BillsPayments';
import BillAllocateLandedCost from './LandedCost';
import VendorCredit from './VendorCredit';
import VendorCreditApplyToBills from './VendorCreditApplyToBills';
@Service()
export default class PurchasesController {
router() {
const router = Router();
router.use('/bills', Container.get(Bills).router());
router.use('/bill_payments', Container.get(BillPayments).router());
router.use('/landed-cost', Container.get(BillAllocateLandedCost).router());
router.use('/vendor-credit', Container.get(VendorCredit).router());
router.use(
'/vendor-credit',
Container.get(VendorCreditApplyToBills).router()
);
return router;
}
}