WIP: Allocate landed cost.

This commit is contained in:
a.bouhuolia
2021-07-22 18:11:17 +02:00
parent 1eacc254d8
commit 76c6cb3699
33 changed files with 1577 additions and 163 deletions

View File

@@ -111,6 +111,7 @@ export default class ExpensesController extends BaseController {
.trim()
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('categories.*.landed_cost').optional().isBoolean().toBoolean(),
];
}
@@ -251,11 +252,8 @@ export default class ExpensesController extends BaseController {
}
try {
const {
expenses,
pagination,
filterMeta,
} = await this.expensesService.getExpensesList(tenantId, filter);
const { expenses, pagination, filterMeta } =
await this.expensesService.getExpensesList(tenantId, filter);
return res.status(200).send({
expenses,
@@ -345,6 +343,11 @@ export default class ExpensesController extends BaseController {
errors: [{ type: 'CONTACT_NOT_FOUND', code: 800 }],
});
}
if (error.errorType === 'EXPENSE_HAS_ASSOCIATED_LANDED_COST') {
return res.status(400).send({
errors: [{ type: 'EXPENSE_HAS_ASSOCIATED_LANDED_COST', code: 900 }],
});
}
}
next(error);
}

View File

@@ -110,6 +110,10 @@ export default class BillsController extends BaseController {
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.landed_cost')
.optional({ nullable: true })
.isBoolean()
.toBoolean(),
];
}
@@ -141,6 +145,10 @@ export default class BillsController extends BaseController {
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.landedCost')
.optional({ nullable: true })
.isBoolean()
.toBoolean(),
];
}
@@ -301,11 +309,8 @@ export default class BillsController extends BaseController {
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
}
try {
const {
bills,
pagination,
filterMeta,
} = await this.billsService.getBills(tenantId, filter);
const { bills, pagination, filterMeta } =
await this.billsService.getBills(tenantId, filter);
return res.status(200).send({
bills,
@@ -397,17 +402,24 @@ export default class BillsController extends BaseController {
if (error.errorType === 'contact_not_found') {
return res.boom.badRequest(null, {
errors: [
{ type: 'VENDOR_NOT_FOUND', message: 'Vendor not found.', code: 1200 },
{
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
}],
errors: [
{
type: 'BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES',
message:
'Cannot delete bill that has associated payment transactions.',
code: 1200,
},
],
});
}
}

View File

@@ -0,0 +1,289 @@
import { Router, Request, Response, NextFunction } from 'express';
import { check, param, query } from 'express-validator';
import { Service, Inject } from 'typedi';
import { ServiceError } from 'exceptions';
import AllocateLandedCostService from 'services/Purchases/LandedCost';
import LandedCostListing from 'services/Purchases/LandedCost/LandedCostListing';
import BaseController from '../BaseController';
import { ResultSetDependencies } from 'mathjs';
@Service()
export default class BillAllocateLandedCost extends BaseController {
@Inject()
allocateLandedCost: AllocateLandedCostService;
@Inject()
landedCostListing: LandedCostListing;
/**
* 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.bind(this),
this.handleServiceErrors
);
router.delete(
'/:allocatedLandedCostId',
[param('allocatedLandedCostId').exists().isInt()],
this.validationResult,
this.deleteAllocatedLandedCost.bind(this),
this.handleServiceErrors
);
router.get(
'/transactions',
[query('transaction_type').exists().isIn(['Expense', 'Bill'])],
this.validationResult,
this.getLandedCostTransactions.bind(this),
this.handleServiceErrors
);
router.get(
'/bills/:billId/transactions',
[param('billId').exists()],
this.validationResult,
this.getBillLandedCostTransactions.bind(this),
this.handleServiceErrors
);
return router;
}
/**
* Retrieve the landed cost transactions of the given query.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async getLandedCostTransactions(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const query = this.matchedQueryData(req);
try {
const transactions =
await this.landedCostListing.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 async calculateLandedCost(
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 async deleteAllocatedLandedCost(
req: Request,
res: Response,
next: NextFunction
): Promise<Response> {
const { tenantId } = req;
const { allocatedLandedCostId } = req.params;
try {
await this.allocateLandedCost.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 async listLandedCosts(
req: Request,
res: Response,
next: NextFunction
) {
const query = this.matchedQueryData(req);
const { tenantId } = req;
try {
const transactions =
await this.landedCostListing.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 async getBillLandedCostTransactions(
req: Request,
res: Response,
next: NextFunction
): Promise<Response> {
const { tenantId } = req;
const { billId } = req.params;
try {
const transactions =
await this.landedCostListing.getBillLandedCostTransactions(
tenantId,
billId
);
return res.status(200).send({
billId,
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',
code: 400,
message: 'The give bill id not found.',
},
],
});
}
if (error.errorType === 'LANDED_COST_TRANSACTION_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'LANDED_COST_TRANSACTION_NOT_FOUND',
code: 200,
message: 'The given landed cost transaction id not found.',
},
],
});
}
if (error.errorType === 'LANDED_COST_ENTRY_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'LANDED_COST_ENTRY_NOT_FOUND',
code: 300,
message: 'The given landed cost tranasction entry id not found.',
},
],
});
}
if (error.errorType === 'COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT') {
return res.status(400).send({
errors: [
{ type: 'COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT', code: 300 },
],
});
}
if (error.errorType === 'LANDED_COST_ITEMS_IDS_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'LANDED_COST_ITEMS_IDS_NOT_FOUND',
code: 200,
message: 'The given entries ids of purchase invoice not found.',
},
],
});
}
if (error.errorType === 'BILL_LANDED_COST_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'BILL_LANDED_COST_NOT_FOUND',
code: 200,
message: 'The given bill located landed cost not found.',
},
],
});
}
if (error.errorType === 'COST_TRASNACTION_NOT_FOUND') {
return res.status(400).send({
errors: [{ type: 'COST_TRASNACTION_NOT_FOUND', code: 500 }],
});
}
}
next(error);
}
}

View File

@@ -2,6 +2,7 @@ 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';
@Service()
export default class PurchasesController {
@@ -11,6 +12,7 @@ export default class PurchasesController {
router.use('/bills', Container.get(Bills).router());
router.use('/bill_payments', Container.get(BillPayments).router());
router.use('/landed-cost', Container.get(BillAllocateLandedCost).router());
return router;
}

View File

@@ -347,7 +347,7 @@ export default class SaleInvoicesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
handleServiceErrors(
private handleServiceErrors(
error: Error,
req: Request,
res: Response,