mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
add server to monorepo.
This commit is contained in:
547
packages/server/src/api/controllers/Purchases/Bills.ts
Normal file
547
packages/server/src/api/controllers/Purchases/Bills.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
455
packages/server/src/api/controllers/Purchases/BillsPayments.ts
Normal file
455
packages/server/src/api/controllers/Purchases/BillsPayments.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
305
packages/server/src/api/controllers/Purchases/LandedCost.ts
Normal file
305
packages/server/src/api/controllers/Purchases/LandedCost.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
660
packages/server/src/api/controllers/Purchases/VendorCredit.ts
Normal file
660
packages/server/src/api/controllers/Purchases/VendorCredit.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
25
packages/server/src/api/controllers/Purchases/index.ts
Normal file
25
packages/server/src/api/controllers/Purchases/index.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user