feat: Item validate cost, income and inventory account type.

feat: Style sales and purchases forms - 80% progress.
feat: Validate purchase-able and sell-able items in invoices and bills.
feat: Fix bugs in inventory FIFO/LIFO cost methods.
This commit is contained in:
Ahmed Bouhuolia
2020-08-22 11:58:08 +02:00
parent b46570dc01
commit 45088b2d3b
34 changed files with 841 additions and 636 deletions

View File

@@ -1,5 +1,5 @@
import { Router, Request, Response } from 'express';
import { check, param, query, oneOf, ValidationChain } from 'express-validator';
import { check, param, query, ValidationChain } from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import validateMiddleware from '@/http/middleware/validateMiddleware';
import ItemsService from '@/services/Items/ItemsService';
@@ -124,9 +124,6 @@ export default class ItemsController {
/**
* Validate specific item params schema.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
static get validateSpecificItemSchema(): ValidationChain[] {
return [
@@ -135,6 +132,9 @@ export default class ItemsController {
}
/**
* Validate list query schema
*/
static get validateListQuerySchema() {
return [
query('column_sort_order').optional().isIn(['created_at', 'name', 'amount', 'sku']),
@@ -221,16 +221,21 @@ export default class ItemsController {
* @param {Function} next
*/
static async validateCostAccountExistance(req: Request, res: Response, next: Function) {
const { Account } = req.models;
const { Account, AccountType } = req.models;
const item = req.body;
if (item.cost_account_id) {
const foundAccount = await Account.query().findById(item.cost_account_id);
const COGSType = await AccountType.query().findOne('key', 'cost_of_goods_sold');
const foundAccount = await Account.query().findById(item.cost_account_id)
if (!foundAccount) {
return res.status(400).send({
errors: [{ type: 'COST.ACCOUNT.NOT.FOUND', code: 120 }],
});
} else if (foundAccount.accountTypeId !== COGSType.id) {
return res.status(400).send({
errors: [{ type: 'COST.ACCOUNT.NOT.COGS.TYPE', code: 220 }],
});
}
}
next();
@@ -243,16 +248,21 @@ export default class ItemsController {
* @param {NextFunction} next
*/
static async validateSellAccountExistance(req: Request, res: Response, next: Function) {
const { Account } = req.models;
const { Account, AccountType } = req.models;
const item = req.body;
if (item.sell_account_id) {
const incomeType = await AccountType.query().findOne('key', 'income');
const foundAccount = await Account.query().findById(item.sell_account_id);
if (!foundAccount) {
return res.status(400).send({
errors: [{ type: 'SELL.ACCOUNT.NOT.FOUND', code: 130 }],
});
} else if (foundAccount.accountTypeId !== incomeType.id) {
return res.status(400).send({
errors: [{ type: 'SELL.ACCOUNT.NOT.INCOME.TYPE', code: 230 }],
})
}
}
next();
@@ -265,16 +275,21 @@ export default class ItemsController {
* @param {NextFunction} next
*/
static async validateInventoryAccountExistance(req: Request, res: Response, next: Function) {
const { Account } = req.models;
const { Account, AccountType } = req.models;
const item = req.body;
if (item.inventory_account_id) {
const otherAsset = await AccountType.query().findOne('key', 'other_asset');
const foundAccount = await Account.query().findById(item.inventory_account_id);
if (!foundAccount) {
return res.status(400).send({
errors: [{ type: 'INVENTORY.ACCOUNT.NOT.FOUND', code: 200}],
});
} else if (otherAsset.id !== foundAccount.accountTypeId) {
return res.status(400).send({
errors: [{ type: 'INVENTORY.ACCOUNT.NOT.CURRENT.ASSET', code: 300 }],
});
}
}
next();

View File

@@ -25,6 +25,7 @@ export default class BillsController extends BaseController {
asyncMiddleware(this.validateVendorExistance),
asyncMiddleware(this.validateItemsIds),
asyncMiddleware(this.validateBillNumberExists),
asyncMiddleware(this.validateNonPurchasableEntriesItems),
asyncMiddleware(this.newBill)
);
router.post(
@@ -35,6 +36,7 @@ export default class BillsController extends BaseController {
asyncMiddleware(this.validateVendorExistance),
asyncMiddleware(this.validateItemsIds),
asyncMiddleware(this.validateEntriesIdsExistance),
asyncMiddleware(this.validateNonPurchasableEntriesItems),
asyncMiddleware(this.editBill)
);
router.get(
@@ -201,6 +203,32 @@ export default class BillsController extends BaseController {
next();
}
/**
* Validate the entries items that not purchase-able.
* @param {Request} req
* @param {Response} res
* @param {Function} next
*/
static async validateNonPurchasableEntriesItems(req, res, next) {
const { Item } = req.models;
const bill = { ...req.body };
const itemsIds = bill.entries.map(e => e.item_id);
const purchasbleItems = await Item.query()
.where('purchasable', true)
.whereIn('id', itemsIds);
const purchasbleItemsIds = purchasbleItems.map((item) => item.id);
const notPurchasableItems = difference(itemsIds, purchasbleItemsIds);
if (notPurchasableItems.length > 0) {
return res.status(400).send({
errors: [{ type: 'NOT.PURCHASE.ABLE.ITEMS', code: 600 }],
});
}
next();
}
/**
* Creates a new bill and records journal transactions.
* @param {Request} req

View File

@@ -11,7 +11,7 @@ import CustomersService from '@/services/Customers/CustomersService';
import DynamicListing from '@/services/DynamicListing/DynamicListing';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing';
import { Customer } from '../../../models';
import { Customer, Item } from '../../../models';
export default class SaleInvoicesController {
/**
@@ -27,6 +27,7 @@ export default class SaleInvoicesController {
asyncMiddleware(this.validateInvoiceCustomerExistance),
asyncMiddleware(this.validateInvoiceNumberUnique),
asyncMiddleware(this.validateInvoiceItemsIdsExistance),
asyncMiddleware(this.validateNonSellableEntriesItems),
asyncMiddleware(this.newSaleInvoice)
);
router.post(
@@ -42,6 +43,7 @@ export default class SaleInvoicesController {
asyncMiddleware(this.validateInvoiceItemsIdsExistance),
asyncMiddleware(this.valdiateInvoiceEntriesIdsExistance),
asyncMiddleware(this.validateEntriesIdsExistance),
asyncMiddleware(this.validateNonSellableEntriesItems),
asyncMiddleware(this.editSaleInvoice)
);
router.delete(
@@ -257,6 +259,32 @@ export default class SaleInvoicesController {
next();
}
/**
* Validate the entries items that not sellable.
* @param {Request} req
* @param {Response} res
* @param {Function} next
*/
static async validateNonSellableEntriesItems(req, res, next) {
const { Item } = req.models;
const saleInvoice = { ...req.body };
const itemsIds = saleInvoice.entries.map(e => e.item_id);
const sellableItems = await Item.query()
.where('sellable', true)
.whereIn('id', itemsIds);
const sellableItemsIds = sellableItems.map((item) => item.id);
const notSellableItems = difference(itemsIds, sellableItemsIds);
if (notSellableItems.length > 0) {
return res.status(400).send({
errors: [{ type: 'NOT.SELLABLE.ITEMS', code: 600 }],
});
}
next();
}
/**
* Creates a new sale invoice.
* @param {Request} req