- feat: Optimize tenancy software architecture.

This commit is contained in:
Ahmed Bouhuolia
2020-08-30 22:11:14 +02:00
parent 74321a2886
commit ca251a2d28
53 changed files with 1581 additions and 1055 deletions

View File

@@ -28,13 +28,11 @@ export default {
this.getManualJournal.validation, this.getManualJournal.validation,
asyncMiddleware(this.getManualJournal.handler) asyncMiddleware(this.getManualJournal.handler)
); );
router.get( router.get(
'/manual-journals', '/manual-journals',
this.manualJournals.validation, this.manualJournals.validation,
asyncMiddleware(this.manualJournals.handler) asyncMiddleware(this.manualJournals.handler)
); );
router.post( router.post(
'/make-journal-entries', '/make-journal-entries',
this.validateMediaIds, this.validateMediaIds,
@@ -42,13 +40,11 @@ export default {
this.makeJournalEntries.validation, this.makeJournalEntries.validation,
asyncMiddleware(this.makeJournalEntries.handler) asyncMiddleware(this.makeJournalEntries.handler)
); );
router.post( router.post(
'/manual-journals/:id/publish', '/manual-journals/:id/publish',
this.publishManualJournal.validation, this.publishManualJournal.validation,
asyncMiddleware(this.publishManualJournal.handler) asyncMiddleware(this.publishManualJournal.handler)
); );
router.post( router.post(
'/manual-journals/:id', '/manual-journals/:id',
this.validateMediaIds, this.validateMediaIds,
@@ -56,31 +52,26 @@ export default {
this.editManualJournal.validation, this.editManualJournal.validation,
asyncMiddleware(this.editManualJournal.handler) asyncMiddleware(this.editManualJournal.handler)
); );
router.delete( router.delete(
'/manual-journals/:id', '/manual-journals/:id',
this.deleteManualJournal.validation, this.deleteManualJournal.validation,
asyncMiddleware(this.deleteManualJournal.handler) asyncMiddleware(this.deleteManualJournal.handler)
); );
router.delete( router.delete(
'/manual-journals', '/manual-journals',
this.deleteBulkManualJournals.validation, this.deleteBulkManualJournals.validation,
asyncMiddleware(this.deleteBulkManualJournals.handler) asyncMiddleware(this.deleteBulkManualJournals.handler)
); );
router.post( router.post(
'/recurring-journal-entries', '/recurring-journal-entries',
this.recurringJournalEntries.validation, this.recurringJournalEntries.validation,
asyncMiddleware(this.recurringJournalEntries.handler) asyncMiddleware(this.recurringJournalEntries.handler)
); );
router.post( router.post(
'quick-journal-entries', 'quick-journal-entries',
this.quickJournalEntries.validation, this.quickJournalEntries.validation,
asyncMiddleware(this.quickJournalEntries.handler) asyncMiddleware(this.quickJournalEntries.handler)
); );
return router; return router;
}, },

View File

@@ -1,6 +1,3 @@
export default class BaseController { export default class BaseController {
} }

View File

@@ -1,5 +1,6 @@
import { Inject, Service } from 'typedi';
import { Router, Request, Response } from 'express'; import { Router, Request, Response } from 'express';
import { check, param, query, ValidationChain } from 'express-validator'; import { check, param, query, ValidationChain, matchedData } from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from '@/http/middleware/validateMiddleware';
import ItemsService from '@/services/Items/ItemsService'; import ItemsService from '@/services/Items/ItemsService';
@@ -7,23 +8,27 @@ import DynamicListing from '@/services/DynamicListing/DynamicListing';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing'; import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing';
@Service()
export default class ItemsController { export default class ItemsController {
@Inject()
itemsService: ItemsService;
/** /**
* Router constructor. * Router constructor.
*/ */
static router() { router() {
const router = Router(); const router = Router();
router.post( router.post(
'/', '/',
this.validateItemSchema, this.validateItemSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateCategoryExistance), asyncMiddleware(this.validateCategoryExistance.bind(this)),
asyncMiddleware(this.validateCostAccountExistance), asyncMiddleware(this.validateCostAccountExistance.bind(this)),
asyncMiddleware(this.validateSellAccountExistance), asyncMiddleware(this.validateSellAccountExistance.bind(this)),
asyncMiddleware(this.validateInventoryAccountExistance), asyncMiddleware(this.validateInventoryAccountExistance.bind(this)),
asyncMiddleware(this.validateItemNameExistance), asyncMiddleware(this.validateItemNameExistance.bind(this)),
asyncMiddleware(this.newItem), asyncMiddleware(this.newItem.bind(this)),
); );
router.post( router.post(
'/:id', [ '/:id', [
@@ -31,49 +36,41 @@ export default class ItemsController {
...this.validateSpecificItemSchema, ...this.validateSpecificItemSchema,
], ],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateItemExistance), asyncMiddleware(this.validateItemExistance.bind(this)),
asyncMiddleware(this.validateCategoryExistance), asyncMiddleware(this.validateCategoryExistance.bind(this)),
asyncMiddleware(this.validateCostAccountExistance), asyncMiddleware(this.validateCostAccountExistance.bind(this)),
asyncMiddleware(this.validateSellAccountExistance), asyncMiddleware(this.validateSellAccountExistance.bind(this)),
asyncMiddleware(this.validateInventoryAccountExistance), asyncMiddleware(this.validateInventoryAccountExistance.bind(this)),
asyncMiddleware(this.validateItemNameExistance), asyncMiddleware(this.validateItemNameExistance.bind(this)),
asyncMiddleware(this.editItem), asyncMiddleware(this.editItem.bind(this)),
); );
router.delete( router.delete(
'/:id', '/:id',
this.validateSpecificItemSchema, this.validateSpecificItemSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateItemExistance), asyncMiddleware(this.validateItemExistance.bind(this)),
asyncMiddleware(this.deleteItem), asyncMiddleware(this.deleteItem.bind(this)),
); );
router.get( router.get(
'/:id', '/:id',
this.validateSpecificItemSchema, this.validateSpecificItemSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateItemExistance), asyncMiddleware(this.validateItemExistance.bind(this)),
asyncMiddleware(this.getItem), asyncMiddleware(this.getItem.bind(this)),
); );
router.get( router.get(
'/', '/',
this.validateListQuerySchema, this.validateListQuerySchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.listItems), asyncMiddleware(this.listItems.bind(this)),
); );
return router; return router;
} }
/** /**
* Validate item schema. * Validate item schema.
*
* @param {Request} req -
* @param {Response} res -
* @return {ValidationChain[]} - validation chain.
*/ */
static get validateItemSchema( get validateItemSchema(): ValidationChain[] {
req: Request,
res: Response,
next: Function,
): ValidationChain[] {
return [ return [
check('name').exists(), check('name').exists(),
check('type').exists().trim().escape() check('type').exists().trim().escape()
@@ -122,7 +119,7 @@ export default class ItemsController {
/** /**
* Validate specific item params schema. * Validate specific item params schema.
*/ */
static get validateSpecificItemSchema(): ValidationChain[] { get validateSpecificItemSchema(): ValidationChain[] {
return [ return [
param('id').exists().isNumeric().toInt(), param('id').exists().isNumeric().toInt(),
]; ];
@@ -132,7 +129,7 @@ export default class ItemsController {
/** /**
* Validate list query schema * Validate list query schema
*/ */
static get validateListQuerySchema() { get validateListQuerySchema() {
return [ return [
query('column_sort_order').optional().isIn(['created_at', 'name', 'amount', 'sku']), query('column_sort_order').optional().isIn(['created_at', 'name', 'amount', 'sku']),
query('sort_order').optional().isIn(['desc', 'asc']), query('sort_order').optional().isIn(['desc', 'asc']),
@@ -149,7 +146,7 @@ export default class ItemsController {
* @param {Response} res - * @param {Response} res -
* @param {NextFunction} next - * @param {NextFunction} next -
*/ */
static async validateItemExistance(req: Request, res: Response, next: Function) { async validateItemExistance(req: Request, res: Response, next: Function) {
const { Item } = req.models; const { Item } = req.models;
const itemId: number = req.params.id; const itemId: number = req.params.id;
@@ -169,7 +166,7 @@ export default class ItemsController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
static async validateItemNameExistance(req: Request, res: Response, next: Function) { async validateItemNameExistance(req: Request, res: Response, next: Function) {
const { Item } = req.models; const { Item } = req.models;
const item = req.body; const item = req.body;
const itemId: number = req.params.id; const itemId: number = req.params.id;
@@ -195,7 +192,7 @@ export default class ItemsController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateCategoryExistance(req: Request, res: Response, next: Function) { async validateCategoryExistance(req: Request, res: Response, next: Function) {
const { ItemCategory } = req.models; const { ItemCategory } = req.models;
const item = req.body; const item = req.body;
@@ -217,7 +214,7 @@ export default class ItemsController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateCostAccountExistance(req: Request, res: Response, next: Function) { async validateCostAccountExistance(req: Request, res: Response, next: Function) {
const { Account, AccountType } = req.models; const { Account, AccountType } = req.models;
const item = req.body; const item = req.body;
@@ -244,7 +241,7 @@ export default class ItemsController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
static async validateSellAccountExistance(req: Request, res: Response, next: Function) { async validateSellAccountExistance(req: Request, res: Response, next: Function) {
const { Account, AccountType } = req.models; const { Account, AccountType } = req.models;
const item = req.body; const item = req.body;
@@ -271,7 +268,7 @@ export default class ItemsController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
static async validateInventoryAccountExistance(req: Request, res: Response, next: Function) { async validateInventoryAccountExistance(req: Request, res: Response, next: Function) {
const { Account, AccountType } = req.models; const { Account, AccountType } = req.models;
const item = req.body; const item = req.body;
@@ -297,9 +294,14 @@ export default class ItemsController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async newItem(req: Request, res: Response,) { async newItem(req: Request, res: Response,) {
const item = req.body; const { tenantId } = req;
const storedItem = await ItemsService.newItem(item);
const item = matchedData(req, {
locations: ['body'],
includeOptionals: true
});
const storedItem = await this.itemsService.newItem(tenantId, item);
return res.status(200).send({ id: storedItem.id }); return res.status(200).send({ id: storedItem.id });
} }
@@ -309,10 +311,15 @@ export default class ItemsController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async editItem(req: Request, res: Response) { async editItem(req: Request, res: Response) {
const item = req.body; const { tenantId } = req;
const itemId: number = req.params.id; const itemId: number = req.params.id;
const updatedItem = await ItemsService.editItem(item, itemId); const item = matchedData(req, {
locations: ['body'],
includeOptionals: true
});
const updatedItem = await this.itemsService.editItem(tenantId, item, itemId);
return res.status(200).send({ id: itemId }); return res.status(200).send({ id: itemId });
} }
@@ -322,9 +329,11 @@ export default class ItemsController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async deleteItem(req: Request, res: Response) { async deleteItem(req: Request, res: Response) {
const itemId: number = req.params.id; const itemId: number = req.params.id;
await ItemsService.deleteItem(itemId); const { tenantId } = req;
await this.itemsService.deleteItem(tenantId, itemId);
return res.status(200).send({ id: itemId }); return res.status(200).send({ id: itemId });
} }
@@ -335,9 +344,11 @@ export default class ItemsController {
* @param {Response} res * @param {Response} res
* @return {Response} * @return {Response}
*/ */
static async getItem(req: Request, res: Response) { async getItem(req: Request, res: Response) {
const itemId: number = req.params.id; const itemId: number = req.params.id;
const storedItem = await ItemsService.getItemWithMetadata(itemId); const { tenantId } = req;
const storedItem = await this.itemsService.getItemWithMetadata(tenantId, itemId);
return res.status(200).send({ item: storedItem }); return res.status(200).send({ item: storedItem });
} }
@@ -347,7 +358,7 @@ export default class ItemsController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async listItems(req: Request, res: Response) { async listItems(req: Request, res: Response) {
const filter = { const filter = {
filter_roles: [], filter_roles: [],
sort_order: 'asc', sort_order: 'asc',

View File

@@ -1,63 +1,75 @@
import express from 'express'; import { Router, Request, Response } from 'express';
import { check, param, query } from 'express-validator'; import { check, param, query, matchedData } from 'express-validator';
import { Service, Inject } from 'typedi';
import { difference } from 'lodash';
import { BillOTD } from '@/interfaces';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from '@/http/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import BillsService from '@/services/Purchases/Bills'; import BillsService from '@/services/Purchases/Bills';
import BaseController from '@/http/controllers/BaseController'; import BaseController from '@/http/controllers/BaseController';
import VendorsServices from '@/services/Vendors/VendorsService';
import ItemsService from '@/services/Items/ItemsService'; import ItemsService from '@/services/Items/ItemsService';
import TenancyService from '@/services/Tenancy/TenancyService';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListing from '@/services/DynamicListing/DynamicListing';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/HasDynamicListing'; import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/HasDynamicListing';
import { difference } from 'lodash';
@Service()
export default class BillsController extends BaseController { export default class BillsController extends BaseController {
@Inject()
itemsService: ItemsService;
@Inject()
billsService: BillsService;
@Inject()
tenancy: TenancyService;
/** /**
* Router constructor. * Router constructor.
*/ */
static router() { router() {
const router = express.Router(); const router = Router();
router.post( router.post(
'/', '/',
[...this.billValidationSchema], [...this.billValidationSchema],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateVendorExistance), asyncMiddleware(this.validateVendorExistance.bind(this)),
asyncMiddleware(this.validateItemsIds), asyncMiddleware(this.validateItemsIds.bind(this)),
asyncMiddleware(this.validateBillNumberExists), asyncMiddleware(this.validateBillNumberExists.bind(this)),
asyncMiddleware(this.validateNonPurchasableEntriesItems), asyncMiddleware(this.validateNonPurchasableEntriesItems.bind(this)),
asyncMiddleware(this.newBill) asyncMiddleware(this.newBill.bind(this))
); );
router.post( router.post(
'/:id', '/:id',
[...this.billValidationSchema, ...this.specificBillValidationSchema], [...this.billValidationSchema, ...this.specificBillValidationSchema],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateBillExistance), asyncMiddleware(this.validateBillExistance.bind(this)),
asyncMiddleware(this.validateVendorExistance), asyncMiddleware(this.validateVendorExistance.bind(this)),
asyncMiddleware(this.validateItemsIds), asyncMiddleware(this.validateItemsIds.bind(this)),
asyncMiddleware(this.validateEntriesIdsExistance), asyncMiddleware(this.validateEntriesIdsExistance.bind(this)),
asyncMiddleware(this.validateNonPurchasableEntriesItems), asyncMiddleware(this.validateNonPurchasableEntriesItems.bind(this)),
asyncMiddleware(this.editBill) asyncMiddleware(this.editBill.bind(this))
); );
router.get( router.get(
'/:id', '/:id',
[...this.specificBillValidationSchema], [...this.specificBillValidationSchema],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateBillExistance), asyncMiddleware(this.validateBillExistance.bind(this)),
asyncMiddleware(this.getBill) asyncMiddleware(this.getBill.bind(this))
); );
router.get( router.get(
'/', '/',
[...this.billsListingValidationSchema], [...this.billsListingValidationSchema],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.listingBills) asyncMiddleware(this.listingBills.bind(this))
); );
router.delete( router.delete(
'/:id', '/:id',
[...this.specificBillValidationSchema], [...this.specificBillValidationSchema],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateBillExistance), asyncMiddleware(this.validateBillExistance.bind(this)),
asyncMiddleware(this.deleteBill) asyncMiddleware(this.deleteBill.bind(this))
); );
return router; return router;
} }
@@ -65,7 +77,7 @@ export default class BillsController extends BaseController {
/** /**
* Common validation schema. * Common validation schema.
*/ */
static get billValidationSchema() { get billValidationSchema() {
return [ return [
check('bill_number').exists().trim().escape(), check('bill_number').exists().trim().escape(),
check('bill_date').exists().isISO8601(), check('bill_date').exists().isISO8601(),
@@ -87,14 +99,14 @@ export default class BillsController extends BaseController {
/** /**
* Bill validation schema. * Bill validation schema.
*/ */
static get specificBillValidationSchema() { get specificBillValidationSchema() {
return [param('id').exists().isNumeric().toInt()]; return [param('id').exists().isNumeric().toInt()];
} }
/** /**
* Bills list validation schema. * Bills list validation schema.
*/ */
static get billsListingValidationSchema() { get billsListingValidationSchema() {
return [ return [
query('custom_view_id').optional().isNumeric().toInt(), query('custom_view_id').optional().isNumeric().toInt(),
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),
@@ -107,14 +119,17 @@ export default class BillsController extends BaseController {
/** /**
* Validates whether the vendor is exist. * Validates whether the vendor is exist.
* @async
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateVendorExistance(req, res, next) { async validateVendorExistance(req: Request, res: Response, next: Function) {
const isVendorExists = await VendorsServices.isVendorExists( const { tenantId } = req;
req.body.vendor_id const { Vendor } = req.models;
);
const isVendorExists = await Vendor.query().findById(req.body.vendor_id);
if (!isVendorExists) { if (!isVendorExists) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'VENDOR.ID.NOT.FOUND', code: 300 }], errors: [{ type: 'VENDOR.ID.NOT.FOUND', code: 300 }],
@@ -125,12 +140,17 @@ export default class BillsController extends BaseController {
/** /**
* Validates the given bill existance. * Validates the given bill existance.
* @async
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateBillExistance(req, res, next) { async validateBillExistance(req: Request, res: Response, next: Function) {
const isBillExists = await BillsService.isBillExists(req.params.id); const billId: number = req.params.id;
const { tenantId } = req;
const isBillExists = await this.billsService.isBillExists(tenantId, billId);
if (!isBillExists) { if (!isBillExists) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'BILL.NOT.FOUND', code: 200 }], errors: [{ type: 'BILL.NOT.FOUND', code: 200 }],
@@ -141,13 +161,17 @@ export default class BillsController extends BaseController {
/** /**
* Validates the entries items ids. * Validates the entries items ids.
* @async
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateItemsIds(req, res, next) { async validateItemsIds(req: Request, res: Response, next: Function) {
const { tenantId } = req;
const itemsIds = req.body.entries.map((e) => e.item_id); const itemsIds = req.body.entries.map((e) => e.item_id);
const notFoundItemsIds = await ItemsService.isItemsIdsExists(itemsIds);
const notFoundItemsIds = await this.itemsService.isItemsIdsExists(tenantId, itemsIds);
if (notFoundItemsIds.length > 0) { if (notFoundItemsIds.length > 0) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'ITEMS.IDS.NOT.FOUND', code: 400 }], errors: [{ type: 'ITEMS.IDS.NOT.FOUND', code: 400 }],
@@ -158,15 +182,17 @@ export default class BillsController extends BaseController {
/** /**
* Validates the bill number existance. * Validates the bill number existance.
* @async
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateBillNumberExists(req, res, next) { async validateBillNumberExists(req: Request, res: Response, next: Function) {
const isBillNoExists = await BillsService.isBillNoExists( const { tenantId } = req;
req.body.bill_number
);
const isBillNoExists = await this.billsService.isBillNoExists(
tenantId, req.body.bill_number,
);
if (isBillNoExists) { if (isBillNoExists) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'BILL.NUMBER.EXISTS', code: 500 }], errors: [{ type: 'BILL.NUMBER.EXISTS', code: 500 }],
@@ -181,20 +207,20 @@ export default class BillsController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateEntriesIdsExistance(req, res, next) { async validateEntriesIdsExistance(req: Request, res: Response, next: Function) {
const { id: billId } = req.params; const { id: billId } = req.params;
const bill = { ...req.body }; const bill = { ...req.body };
const { ItemEntry } = req.models; const { ItemEntry } = req.models;
const entriesIds = bill.entries.filter((e) => e.id).map((e) => e.id); const entriesIds = bill.entries.filter((e) => e.id).map((e) => e.id);
const storedEntries = await ItemEntry.tenant() const storedEntries = await ItemEntry.query()
.query()
.whereIn('reference_id', [billId]) .whereIn('reference_id', [billId])
.whereIn('reference_type', ['Bill']); .whereIn('reference_type', ['Bill']);
const storedEntriesIds = storedEntries.map((entry) => entry.id); const storedEntriesIds = storedEntries.map((entry) => entry.id);
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds); const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
if (notFoundEntriesIds.length > 0) { if (notFoundEntriesIds.length > 0) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'BILL.ENTRIES.IDS.NOT.FOUND', code: 600 }], errors: [{ type: 'BILL.ENTRIES.IDS.NOT.FOUND', code: 600 }],
@@ -209,7 +235,7 @@ export default class BillsController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateNonPurchasableEntriesItems(req, res, next) { async validateNonPurchasableEntriesItems(req: Request, res: Response, next: Function) {
const { Item } = req.models; const { Item } = req.models;
const bill = { ...req.body }; const bill = { ...req.body };
const itemsIds = bill.entries.map(e => e.item_id); const itemsIds = bill.entries.map(e => e.item_id);
@@ -235,17 +261,15 @@ export default class BillsController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async newBill(req, res, next) { async newBill(req: Request, res: Response, next: Function) {
const { tenantId } = req;
const { ItemEntry } = req.models; const { ItemEntry } = req.models;
const bill = { const billOTD: BillOTD = matchedData(req, {
...req.body, locations: ['body'],
entries: req.body.entries.map((entry) => ({ includeOptionals: true
...entry, });
amount: ItemEntry.calcAmount(entry), const storedBill = await this.billsService.createBill(tenantId, billOTD);
})),
};
const storedBill = await BillsService.createBill(bill);
return res.status(200).send({ id: storedBill.id }); return res.status(200).send({ id: storedBill.id });
} }
@@ -255,17 +279,16 @@ export default class BillsController extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async editBill(req, res) { async editBill(req: Request, res: Response) {
const { ItemEntry } = req.models;
const { id: billId } = req.params; const { id: billId } = req.params;
const bill = { const { ItemEntry } = req.models;
...req.body, const { tenantId } = req;
entries: req.body.entries.map((entry) => ({
...entry, const billOTD: BillOTD = matchedData(req, {
amount: ItemEntry.calcAmount(entry), locations: ['body'],
})), includeOptionals: true
}; });
const editedBill = await BillsService.editBill(billId, bill); const editedBill = await this.billsService.editBill(tenantId, billId, billOTD);
return res.status(200).send({ id: billId }); return res.status(200).send({ id: billId });
} }
@@ -276,9 +299,11 @@ export default class BillsController extends BaseController {
* @param {Response} res * @param {Response} res
* @return {Response} * @return {Response}
*/ */
static async getBill(req, res) { async getBill(req: Request, res: Response) {
const { tenantId } = req;
const { id: billId } = req.params; const { id: billId } = req.params;
const bill = await BillsService.getBillWithMetadata(billId);
const bill = await this.billsService.getBillWithMetadata(tenantId, billId);
return res.status(200).send({ bill }); return res.status(200).send({ bill });
} }
@@ -289,9 +314,11 @@ export default class BillsController extends BaseController {
* @param {Response} res - * @param {Response} res -
* @return {Response} * @return {Response}
*/ */
static async deleteBill(req, res) { async deleteBill(req: Request, res: Response) {
const billId = req.params.id; const billId = req.params.id;
await BillsService.deleteBill(billId); const { tenantId } = req;
await this.billsService.deleteBill(tenantId, billId);
return res.status(200).send({ id: billId }); return res.status(200).send({ id: billId });
} }
@@ -302,7 +329,7 @@ export default class BillsController extends BaseController {
* @param {Response} res - * @param {Response} res -
* @return {Response} * @return {Response}
*/ */
static async listingBills(req, res) { async listingBills(req: Request, res: Response) {
const filter = { const filter = {
filter_roles: [], filter_roles: [],
sort_order: 'asc', sort_order: 'asc',

View File

@@ -1,6 +1,7 @@
import { Router } from 'express'; import { Router } from 'express';
import { check, param, query, ValidationChain } from 'express-validator'; import { Service, Inject } from 'typedi';
import { check, param, query, ValidationChain, matchedData } from 'express-validator';
import { difference } from 'lodash'; import { difference } from 'lodash';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from '@/http/middleware/validateMiddleware';
@@ -13,55 +14,62 @@ import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDyn
/** /**
* Bills payments controller. * Bills payments controller.
* @controller * @service
*/ */
@Service()
export default class BillsPayments extends BaseController { export default class BillsPayments extends BaseController {
@Inject()
billPaymentService: BillPaymentsService;
@Inject()
accountsService: AccountsService;
/** /**
* Router constructor. * Router constructor.
*/ */
static router() { router() {
const router = Router(); const router = Router();
router.post('/', [ router.post('/', [
...this.billPaymentSchemaValidation, ...this.billPaymentSchemaValidation,
], ],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateBillPaymentVendorExistance), asyncMiddleware(this.validateBillPaymentVendorExistance.bind(this)),
asyncMiddleware(this.validatePaymentAccount), asyncMiddleware(this.validatePaymentAccount.bind(this)),
asyncMiddleware(this.validatePaymentNumber), asyncMiddleware(this.validatePaymentNumber.bind(this)),
asyncMiddleware(this.validateEntriesBillsExistance), asyncMiddleware(this.validateEntriesBillsExistance.bind(this)),
asyncMiddleware(this.validateVendorsDueAmount), asyncMiddleware(this.validateVendorsDueAmount.bind(this)),
asyncMiddleware(this.createBillPayment), asyncMiddleware(this.createBillPayment.bind(this)),
); );
router.post('/:id', [ router.post('/:id', [
...this.billPaymentSchemaValidation, ...this.billPaymentSchemaValidation,
...this.specificBillPaymentValidateSchema, ...this.specificBillPaymentValidateSchema,
], ],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateBillPaymentVendorExistance), asyncMiddleware(this.validateBillPaymentVendorExistance.bind(this)),
asyncMiddleware(this.validatePaymentAccount), asyncMiddleware(this.validatePaymentAccount.bind(this)),
asyncMiddleware(this.validatePaymentNumber), asyncMiddleware(this.validatePaymentNumber.bind(this)),
asyncMiddleware(this.validateEntriesIdsExistance), asyncMiddleware(this.validateEntriesIdsExistance.bind(this)),
asyncMiddleware(this.validateEntriesBillsExistance), asyncMiddleware(this.validateEntriesBillsExistance.bind(this)),
asyncMiddleware(this.validateVendorsDueAmount), asyncMiddleware(this.validateVendorsDueAmount.bind(this)),
asyncMiddleware(this.editBillPayment), asyncMiddleware(this.editBillPayment.bind(this)),
) )
router.delete('/:id', router.delete('/:id',
this.specificBillPaymentValidateSchema, this.specificBillPaymentValidateSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateBillPaymentExistance), asyncMiddleware(this.validateBillPaymentExistance.bind(this)),
asyncMiddleware(this.deleteBillPayment), asyncMiddleware(this.deleteBillPayment.bind(this)),
); );
router.get('/:id', router.get('/:id',
this.specificBillPaymentValidateSchema, this.specificBillPaymentValidateSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateBillPaymentExistance), asyncMiddleware(this.validateBillPaymentExistance.bind(this)),
asyncMiddleware(this.getBillPayment), asyncMiddleware(this.getBillPayment.bind(this)),
); );
router.get('/', router.get('/',
this.listingValidationSchema, this.listingValidationSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.getBillsPayments) asyncMiddleware(this.getBillsPayments.bind(this))
); );
return router; return router;
} }
@@ -69,7 +77,7 @@ export default class BillsPayments extends BaseController {
/** /**
* Bill payments schema validation. * Bill payments schema validation.
*/ */
static get billPaymentSchemaValidation(): ValidationChain[] { get billPaymentSchemaValidation(): ValidationChain[] {
return [ return [
check('vendor_id').exists().isNumeric().toInt(), check('vendor_id').exists().isNumeric().toInt(),
check('payment_account_id').exists().isNumeric().toInt(), check('payment_account_id').exists().isNumeric().toInt(),
@@ -87,19 +95,33 @@ export default class BillsPayments extends BaseController {
/** /**
* Specific bill payment schema validation. * Specific bill payment schema validation.
*/ */
static get specificBillPaymentValidateSchema(): ValidationChain[] { get specificBillPaymentValidateSchema(): ValidationChain[] {
return [ return [
param('id').exists().isNumeric().toInt(), param('id').exists().isNumeric().toInt(),
]; ];
} }
/**
* Bills payment list validation schema.
*/
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(),
];
}
/** /**
* Validate whether the bill payment vendor exists on the storage. * Validate whether the bill payment vendor exists on the storage.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateBillPaymentVendorExistance(req: Request, res: Response, next: any ) { async validateBillPaymentVendorExistance(req: Request, res: Response, next: any ) {
const billPayment = req.body; const billPayment = req.body;
const { Vendor } = req.models; const { Vendor } = req.models;
const isVendorExists = await Vendor.query().findById(billPayment.vendor_id); const isVendorExists = await Vendor.query().findById(billPayment.vendor_id);
@@ -118,7 +140,7 @@ export default class BillsPayments extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateBillPaymentExistance(req: Request, res: Response, next: any ) { async validateBillPaymentExistance(req: Request, res: Response, next: any ) {
const { id: billPaymentId } = req.params; const { id: billPaymentId } = req.params;
const { BillPayment } = req.models; const { BillPayment } = req.models;
const foundBillPayment = await BillPayment.query().findById(billPaymentId); const foundBillPayment = await BillPayment.query().findById(billPaymentId);
@@ -137,11 +159,15 @@ export default class BillsPayments extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validatePaymentAccount(req: Request, res: Response, next: any) { async validatePaymentAccount(req: Request, res: Response, next: any) {
const { tenantId } = req;
const billPayment = { ...req.body }; const billPayment = { ...req.body };
const isAccountExists = await AccountsService.isAccountExists(
const isAccountExists = await this.accountsService.isAccountExists(
tenantId,
billPayment.payment_account_id billPayment.payment_account_id
); );
if (!isAccountExists) { if (!isAccountExists) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'PAYMENT.ACCOUNT.NOT.FOUND', code: 200 }], errors: [{ type: 'PAYMENT.ACCOUNT.NOT.FOUND', code: 200 }],
@@ -156,7 +182,7 @@ export default class BillsPayments extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} res * @param {Function} res
*/ */
static async validatePaymentNumber(req: Request, res: Response, next: any) { async validatePaymentNumber(req: Request, res: Response, next: any) {
const billPayment = { ...req.body }; const billPayment = { ...req.body };
const { id: billPaymentId } = req.params; const { id: billPaymentId } = req.params;
const { BillPayment } = req.models; const { BillPayment } = req.models;
@@ -164,7 +190,6 @@ export default class BillsPayments extends BaseController {
const foundBillPayment = await BillPayment.query() const foundBillPayment = await BillPayment.query()
.onBuild((builder: any) => { .onBuild((builder: any) => {
builder.where('payment_number', billPayment.payment_number) builder.where('payment_number', billPayment.payment_number)
if (billPaymentId) { if (billPaymentId) {
builder.whereNot('id', billPaymentId); builder.whereNot('id', billPaymentId);
} }
@@ -185,7 +210,7 @@ export default class BillsPayments extends BaseController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
static async validateEntriesBillsExistance(req: Request, res: Response, next: any) { async validateEntriesBillsExistance(req: Request, res: Response, next: any) {
const { Bill } = req.models; const { Bill } = req.models;
const billPayment = { ...req.body }; const billPayment = { ...req.body };
const entriesBillsIds = billPayment.entries.map((e: any) => e.bill_id); const entriesBillsIds = billPayment.entries.map((e: any) => e.bill_id);
@@ -210,7 +235,7 @@ export default class BillsPayments extends BaseController {
* @param {NextFunction} next * @param {NextFunction} next
* @return {void} * @return {void}
*/ */
static async validateVendorsDueAmount(req: Request, res: Response, next: Function) { async validateVendorsDueAmount(req: Request, res: Response, next: Function) {
const { Bill } = req.models; const { Bill } = req.models;
const billsIds = req.body.entries.map((entry: any) => entry.bill_id); const billsIds = req.body.entries.map((entry: any) => entry.bill_id);
const storedBills = await Bill.query() const storedBills = await Bill.query()
@@ -248,7 +273,7 @@ export default class BillsPayments extends BaseController {
* @param {Response} res * @param {Response} res
* @return {Response} * @return {Response}
*/ */
static async validateEntriesIdsExistance(req: Request, res: Response, next: Function) { async validateEntriesIdsExistance(req: Request, res: Response, next: Function) {
const { BillPaymentEntry } = req.models; const { BillPaymentEntry } = req.models;
const billPayment = { id: req.params.id, ...req.body }; const billPayment = { id: req.params.id, ...req.body };
@@ -256,7 +281,7 @@ export default class BillsPayments extends BaseController {
.filter((entry: any) => entry.id) .filter((entry: any) => entry.id)
.map((entry: any) => entry.id); .map((entry: any) => entry.id);
const storedEntries = await BillPaymentEntry.tenant().query() const storedEntries = await BillPaymentEntry.query()
.where('bill_payment_id', billPayment.id); .where('bill_payment_id', billPayment.id);
const storedEntriesIds = storedEntries.map((entry: any) => entry.id); const storedEntriesIds = storedEntries.map((entry: any) => entry.id);
@@ -277,9 +302,15 @@ export default class BillsPayments extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Response} res * @param {Response} res
*/ */
static async createBillPayment(req: Request, res: Response) { async createBillPayment(req: Request, res: Response) {
const billPayment = { ...req.body }; const { tenantId } = req;
const storedPayment = await BillPaymentsService.createBillPayment(billPayment);
const billPayment = matchedData(req, {
locations: ['body'],
includeOptionals: true,
});
const storedPayment = await this.billPaymentService
.createBillPayment(tenantId, billPayment);
return res.status(200).send({ id: storedPayment.id }); return res.status(200).send({ id: storedPayment.id });
} }
@@ -289,10 +320,14 @@ export default class BillsPayments extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async editBillPayment(req: Request, res: Response) { async editBillPayment(req: Request, res: Response) {
const billPayment = { ...req.body }; const { tenantId } = req;
const { id: billPaymentId } = req.params;
const billPayment = matchedData(req, {
locations: ['body'],
includeOptionals: true,
});
const { id: billPaymentId } = req.params;
const { BillPayment } = req.models; const { BillPayment } = req.models;
const oldBillPayment = await BillPayment.query() const oldBillPayment = await BillPayment.query()
@@ -300,7 +335,8 @@ export default class BillsPayments extends BaseController {
.withGraphFetched('entries') .withGraphFetched('entries')
.first(); .first();
await BillPaymentsService.editBillPayment( await this.billPaymentService.editBillPayment(
tenantId,
billPaymentId, billPaymentId,
billPayment, billPayment,
oldBillPayment, oldBillPayment,
@@ -315,11 +351,13 @@ export default class BillsPayments extends BaseController {
* @param {Response} res - * @param {Response} res -
* @return {Response} res - * @return {Response} res -
*/ */
static async deleteBillPayment(req: Request, res: Response) { async deleteBillPayment(req: Request, res: Response) {
const { tenantId } = req;
const { id: billPaymentId } = req.params; const { id: billPaymentId } = req.params;
const billPayment = req.body; const billPayment = req.body;
await BillPaymentsService.deleteBillPayment(billPaymentId); await this.billPaymentService.deleteBillPayment(tenantId, billPaymentId);
return res.status(200).send({ id: billPaymentId }); return res.status(200).send({ id: billPaymentId });
} }
@@ -329,34 +367,23 @@ export default class BillsPayments extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async getBillPayment(req: Request, res: Response) { async getBillPayment(req: Request, res: Response) {
const { tenantId } = req;
const { id: billPaymentId } = req.params; const { id: billPaymentId } = req.params;
const billPayment = await BillPaymentsService.getBillPaymentWithMetadata(billPaymentId);
const billPayment = await this.billPaymentService
.getBillPaymentWithMetadata(tenantId, billPaymentId);
return res.status(200).send({ bill_payment: { ...billPayment } }); return res.status(200).send({ bill_payment: { ...billPayment } });
} }
/**
* Bills payment list validation schema.
*/
static 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(),
];
}
/** /**
* Retrieve bills payments listing with pagination metadata. * Retrieve bills payments listing with pagination metadata.
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
* @return {Response} * @return {Response}
*/ */
static async getBillsPayments(req: Request, res: Response) { async getBillsPayments(req: Request, res: Response) {
const filter = { const filter = {
filter_roles: [], filter_roles: [],
sort_order: 'asc', sort_order: 'asc',

View File

@@ -1,4 +1,5 @@
import express from 'express'; import express from 'express';
import { Container } from 'typedi';
import Bills from '@/http/controllers/Purchases/Bills' import Bills from '@/http/controllers/Purchases/Bills'
import BillPayments from '@/http/controllers/Purchases/BillsPayments'; import BillPayments from '@/http/controllers/Purchases/BillsPayments';
@@ -7,8 +8,8 @@ export default {
router() { router() {
const router = express.Router(); const router = express.Router();
router.use('/bills', Bills.router()); router.use('/bills', Container.get(Bills).router());
router.use('/bill_payments', BillPayments.router()); router.use('/bill_payments', Container.get(BillPayments).router());
return router; return router;
} }

View File

@@ -1,13 +1,14 @@
import express from 'express'; import { Router, Request, Response } from 'express';
import { check, param, query } from 'express-validator'; import { check, param, query, ValidationChain, matchedData } from 'express-validator';
import { difference } from 'lodash'; import { difference } from 'lodash';
import { PaymentReceiveEntry } from '@/models'; import { Inject, Service } from 'typedi';
import { IPaymentReceive, IPaymentReceiveOTD } from '@/interfaces';
import BaseController from '@/http/controllers/BaseController'; import BaseController from '@/http/controllers/BaseController';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from '@/http/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import PaymentReceiveService from '@/services/Sales/PaymentsReceives'; import PaymentReceiveService from '@/services/Sales/PaymentsReceives';
import CustomersService from '@/services/Customers/CustomersService'; import CustomersService from '@/services/Customers/CustomersService';
import SaleInvoicesService from '@/services/Sales/SalesInvoices'; import SaleInvoiceService from '@/services/Sales/SalesInvoices';
import AccountsService from '@/services/Accounts/AccountsService'; import AccountsService from '@/services/Accounts/AccountsService';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListing from '@/services/DynamicListing/DynamicListing';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder';
@@ -15,70 +16,134 @@ import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDyn
/** /**
* Payments receives controller. * Payments receives controller.
* @controller * @service
*/ */
@Service()
export default class PaymentReceivesController extends BaseController { export default class PaymentReceivesController extends BaseController {
@Inject()
paymentReceiveService: PaymentReceiveService;
@Inject()
customersService: CustomersService;
@Inject()
accountsService: AccountsService;
@Inject()
saleInvoiceService: SaleInvoiceService;
/** /**
* Router constructor. * Router constructor.
*/ */
static router() { router() {
const router = express.Router(); const router = Router();
router.post( router.post(
'/:id', '/:id',
this.editPaymentReceiveValidation, this.editPaymentReceiveValidation,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validatePaymentReceiveExistance), asyncMiddleware(this.validatePaymentReceiveExistance.bind(this)),
asyncMiddleware(this.validatePaymentReceiveNoExistance), asyncMiddleware(this.validatePaymentReceiveNoExistance.bind(this)),
asyncMiddleware(this.validateCustomerExistance), asyncMiddleware(this.validateCustomerExistance.bind(this)),
asyncMiddleware(this.validateDepositAccount), asyncMiddleware(this.validateDepositAccount.bind(this)),
asyncMiddleware(this.validateInvoicesIDs), asyncMiddleware(this.validateInvoicesIDs.bind(this)),
asyncMiddleware(this.validateEntriesIdsExistance), asyncMiddleware(this.validateEntriesIdsExistance.bind(this)),
asyncMiddleware(this.validateInvoicesPaymentsAmount), asyncMiddleware(this.validateInvoicesPaymentsAmount.bind(this)),
asyncMiddleware(this.editPaymentReceive), asyncMiddleware(this.editPaymentReceive.bind(this)),
); );
router.post( router.post(
'/', '/',
this.newPaymentReceiveValidation, this.newPaymentReceiveValidation,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validatePaymentReceiveNoExistance), asyncMiddleware(this.validatePaymentReceiveNoExistance.bind(this)),
asyncMiddleware(this.validateCustomerExistance), asyncMiddleware(this.validateCustomerExistance.bind(this)),
asyncMiddleware(this.validateDepositAccount), asyncMiddleware(this.validateDepositAccount.bind(this)),
asyncMiddleware(this.validateInvoicesIDs), asyncMiddleware(this.validateInvoicesIDs.bind(this)),
asyncMiddleware(this.validateInvoicesPaymentsAmount), asyncMiddleware(this.validateInvoicesPaymentsAmount.bind(this)),
asyncMiddleware(this.newPaymentReceive), asyncMiddleware(this.newPaymentReceive.bind(this)),
); );
router.get( router.get(
'/:id', '/:id',
this.paymentReceiveValidation, this.paymentReceiveValidation,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validatePaymentReceiveExistance), asyncMiddleware(this.validatePaymentReceiveExistance.bind(this)),
asyncMiddleware(this.getPaymentReceive) asyncMiddleware(this.getPaymentReceive.bind(this))
); );
router.get( router.get(
'/', '/',
this.validatePaymentReceiveList, this.validatePaymentReceiveList,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.getPaymentReceiveList), asyncMiddleware(this.getPaymentReceiveList.bind(this)),
); );
router.delete( router.delete(
'/:id', '/:id',
this.paymentReceiveValidation, this.paymentReceiveValidation,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validatePaymentReceiveExistance), asyncMiddleware(this.validatePaymentReceiveExistance.bind(this)),
asyncMiddleware(this.deletePaymentReceive), asyncMiddleware(this.deletePaymentReceive.bind(this)),
); );
return router; return router;
} }
/**
* Payment receive schema.
* @return {Array}
*/
get paymentReceiveSchema(): ValidationChain[] {
return [
check('customer_id').exists().isNumeric().toInt(),
check('payment_date').exists(),
check('reference_no').optional(),
check('deposit_account_id').exists().isNumeric().toInt(),
check('payment_receive_no').exists().trim().escape(),
check('statement').optional().trim().escape(),
check('entries').isArray({ min: 1 }),
check('entries.*.invoice_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toInt(),
];
}
/**
* Payment receive list validation schema.
*/
get validatePaymentReceiveList(): 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(),
]
}
/**
* Validate payment receive parameters.
*/
get paymentReceiveValidation() {
return [param('id').exists().isNumeric().toInt()];
}
/**
* New payment receive validation schema.
* @return {Array}
*/
get newPaymentReceiveValidation() {
return [...this.paymentReceiveSchema];
}
/** /**
* Validates the payment receive number existance. * Validates the payment receive number existance.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validatePaymentReceiveNoExistance(req, res, next) { async validatePaymentReceiveNoExistance(req: Request, res: Response, next: Function) {
const isPaymentNoExists = await PaymentReceiveService.isPaymentReceiveNoExists( const tenantId = req.tenantId;
const isPaymentNoExists = await this.paymentReceiveService.isPaymentReceiveNoExists(
tenantId,
req.body.payment_receive_no, req.body.payment_receive_no,
req.params.id, req.params.id,
); );
@@ -96,13 +161,16 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validatePaymentReceiveExistance(req, res, next) { async validatePaymentReceiveExistance(req: Request, res: Response, next: Function) {
const isPaymentNoExists = await PaymentReceiveService.isPaymentReceiveExists( const tenantId = req.tenantId;
req.params.id const isPaymentNoExists = await this.paymentReceiveService
); .isPaymentReceiveExists(
tenantId,
req.params.id
);
if (!isPaymentNoExists) { if (!isPaymentNoExists) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'PAYMENT.RECEIVE.NO.EXISTS', code: 600 }], errors: [{ type: 'PAYMENT.RECEIVE.NOT.EXISTS', code: 600 }],
}); });
} }
next(); next();
@@ -114,8 +182,10 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateDepositAccount(req, res, next) { async validateDepositAccount(req: Request, res: Response, next: Function) {
const isDepositAccExists = await AccountsService.isAccountExists( const tenantId = req.tenantId;
const isDepositAccExists = await this.accountsService.isAccountExists(
tenantId,
req.body.deposit_account_id req.body.deposit_account_id
); );
if (!isDepositAccExists) { if (!isDepositAccExists) {
@@ -132,10 +202,11 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateCustomerExistance(req, res, next) { async validateCustomerExistance(req: Request, res: Response, next: Function) {
const isCustomerExists = await CustomersService.isCustomerExists( const { Customer } = req.models;
req.body.customer_id
); const isCustomerExists = await Customer.query().findById(req.body.customer_id);
if (!isCustomerExists) { if (!isCustomerExists) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'CUSTOMER.ID.NOT.EXISTS', code: 200 }], errors: [{ type: 'CUSTOMER.ID.NOT.EXISTS', code: 200 }],
@@ -150,10 +221,14 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res - * @param {Response} res -
* @param {Function} next - * @param {Function} next -
*/ */
static async validateInvoicesIDs(req, res, next) { async validateInvoicesIDs(req: Request, res: Response, next: Function) {
const paymentReceive = { ...req.body }; const paymentReceive = { ...req.body };
const invoicesIds = paymentReceive.entries.map((e) => e.invoice_id); const { tenantId } = req;
const notFoundInvoicesIDs = await SaleInvoicesService.isInvoicesExist( const invoicesIds = paymentReceive.entries
.map((e) => e.invoice_id);
const notFoundInvoicesIDs = await this.saleInvoiceService.isInvoicesExist(
tenantId,
invoicesIds, invoicesIds,
paymentReceive.customer_id, paymentReceive.customer_id,
); );
@@ -171,19 +246,19 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res - * @param {Response} res -
* @param {Function} next - * @param {Function} next -
*/ */
static async validateInvoicesPaymentsAmount(req, res, next) { async validateInvoicesPaymentsAmount(req: Request, res: Response, next: Function) {
const { SaleInvoice } = req.models; const { SaleInvoice } = req.models;
const invoicesIds = req.body.entries.map((e) => e.invoice_id); const invoicesIds = req.body.entries.map((e) => e.invoice_id);
const storedInvoices = await SaleInvoice.tenant()
.query() const storedInvoices = await SaleInvoice.query()
.whereIn('id', invoicesIds); .whereIn('id', invoicesIds);
const storedInvoicesMap = new Map( const storedInvoicesMap = new Map(
storedInvoices.map((invoice) => [invoice.id, invoice]) storedInvoices.map((invoice) => [invoice.id, invoice])
); );
const hasWrongPaymentAmount = []; const hasWrongPaymentAmount: any[] = [];
req.body.entries.forEach((entry, index) => { req.body.entries.forEach((entry, index: number) => {
const entryInvoice = storedInvoicesMap.get(entry.invoice_id); const entryInvoice = storedInvoicesMap.get(entry.invoice_id);
const { dueAmount } = entryInvoice; const { dueAmount } = entryInvoice;
@@ -211,13 +286,15 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res * @param {Response} res
* @return {Response} * @return {Response}
*/ */
static async validateEntriesIdsExistance(req, res, next) { async validateEntriesIdsExistance(req: Request, res: Response, next: Function) {
const paymentReceive = { id: req.params.id, ...req.body }; const paymentReceive = { id: req.params.id, ...req.body };
const entriesIds = paymentReceive.entries const entriesIds = paymentReceive.entries
.filter(entry => entry.id) .filter(entry => entry.id)
.map(entry => entry.id); .map(entry => entry.id);
const storedEntries = await PaymentReceiveEntry.tenant().query() const { PaymentReceiveEntry } = req.models;
const storedEntries = await PaymentReceiveEntry.query()
.where('payment_receive_id', paymentReceive.id); .where('payment_receive_id', paymentReceive.id);
const storedEntriesIds = storedEntries.map((entry) => entry.id); const storedEntriesIds = storedEntries.map((entry) => entry.id);
@@ -231,49 +308,28 @@ export default class PaymentReceivesController extends BaseController {
next(); next();
} }
/**
* Payment receive schema.
* @return {Array}
*/
static get paymentReceiveSchema() {
return [
check('customer_id').exists().isNumeric().toInt(),
check('payment_date').exists(),
check('reference_no').optional(),
check('deposit_account_id').exists().isNumeric().toInt(),
check('payment_receive_no').exists().trim().escape(),
check('statement').optional().trim().escape(),
check('entries').isArray({ min: 1 }),
check('entries.*.invoice_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toInt(),
];
}
/**
* New payment receive validation schema.
* @return {Array}
*/
static get newPaymentReceiveValidation() {
return [...this.paymentReceiveSchema];
}
/** /**
* Records payment receive to the given customer with associated invoices. * Records payment receive to the given customer with associated invoices.
*/ */
static async newPaymentReceive(req, res) { async newPaymentReceive(req: Request, res: Response) {
const paymentReceive = { ...req.body }; const { tenantId } = req;
const storedPaymentReceive = await PaymentReceiveService.createPaymentReceive( const paymentReceive: IPaymentReceiveOTD = matchedData(req, {
paymentReceive locations: ['body'],
); includeOptionals: true,
});
const storedPaymentReceive = await this.paymentReceiveService
.createPaymentReceive(
tenantId,
paymentReceive,
);
return res.status(200).send({ id: storedPaymentReceive.id }); return res.status(200).send({ id: storedPaymentReceive.id });
} }
/** /**
* Edit payment receive validation. * Edit payment receive validation.
*/ */
static get editPaymentReceiveValidation() { get editPaymentReceiveValidation() {
return [ return [
param('id').exists().isNumeric().toInt(), param('id').exists().isNumeric().toInt(),
...this.paymentReceiveSchema, ...this.paymentReceiveSchema,
@@ -286,18 +342,23 @@ export default class PaymentReceivesController extends BaseController {
* @param {Response} res * @param {Response} res
* @return {Response} * @return {Response}
*/ */
static async editPaymentReceive(req, res) { async editPaymentReceive(req: Request, res: Response) {
const paymentReceive = { ...req.body }; const { tenantId } = req;
const { id: paymentReceiveId } = req.params; const { id: paymentReceiveId } = req.params;
const { PaymentReceive } = req.models; const { PaymentReceive } = req.models;
const paymentReceive: IPaymentReceiveOTD = matchedData(req, {
locations: ['body'],
});
// Retrieve the payment receive before updating. // Retrieve the payment receive before updating.
const oldPaymentReceive = await PaymentReceive.query() const oldPaymentReceive: IPaymentReceive = await PaymentReceive.query()
.where('id', paymentReceiveId) .where('id', paymentReceiveId)
.withGraphFetched('entries') .withGraphFetched('entries')
.first(); .first();
await PaymentReceiveService.editPaymentReceive( await this.paymentReceiveService.editPaymentReceive(
tenantId,
paymentReceiveId, paymentReceiveId,
paymentReceive, paymentReceive,
oldPaymentReceive, oldPaymentReceive,
@@ -305,28 +366,23 @@ export default class PaymentReceivesController extends BaseController {
return res.status(200).send({ id: paymentReceiveId }); return res.status(200).send({ id: paymentReceiveId });
} }
/**
* Validate payment receive parameters.
*/
static get paymentReceiveValidation() {
return [param('id').exists().isNumeric().toInt()];
}
/** /**
* Delets the given payment receive id. * Delets the given payment receive id.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async deletePaymentReceive(req, res) { async deletePaymentReceive(req: Request, res: Response) {
const { tenantId } = req;
const { id: paymentReceiveId } = req.params; const { id: paymentReceiveId } = req.params;
const { PaymentReceive } = req.models; const { PaymentReceive } = req.models;
const storedPaymentReceive = await PaymentReceive.query() const storedPaymentReceive = await PaymentReceive.query()
.where('id', paymentReceiveId) .where('id', paymentReceiveId)
.withGraphFetched('entries') .withGraphFetched('entries')
.first(); .first();
await PaymentReceiveService.deletePaymentReceive( await this.paymentReceiveService.deletePaymentReceive(
tenantId,
paymentReceiveId, paymentReceiveId,
storedPaymentReceive storedPaymentReceive
); );
@@ -339,7 +395,7 @@ export default class PaymentReceivesController extends BaseController {
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
static async getPaymentReceive(req, res) { async getPaymentReceive(req: Request, res: Response) {
const { id: paymentReceiveId } = req.params; const { id: paymentReceiveId } = req.params;
const paymentReceive = await PaymentReceiveService.getPaymentReceive( const paymentReceive = await PaymentReceiveService.getPaymentReceive(
paymentReceiveId paymentReceiveId
@@ -347,27 +403,13 @@ export default class PaymentReceivesController extends BaseController {
return res.status(200).send({ paymentReceive }); return res.status(200).send({ paymentReceive });
} }
/**
* Payment receive list validation schema.
*/
static get validatePaymentReceiveList() {
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(),
]
}
/** /**
* Retrieve payment receive list with pagination metadata. * Retrieve payment receive list with pagination metadata.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @return {Response} * @return {Response}
*/ */
static async getPaymentReceiveList(req, res) { async getPaymentReceiveList(req: Request, res: Response) {
const filter = { const filter = {
filter_roles: [], filter_roles: [],
sort_order: 'asc', sort_order: 'asc',

View File

@@ -1,6 +1,7 @@
import express from 'express'; import { Router, Request, Response } from 'express';
import { check, param, query } from 'express-validator'; import { check, param, query, matchedData } from 'express-validator';
import { ItemEntry } from '@/models'; import { Inject, Service } from 'typedi';
import { ISaleEstimate, ISaleEstimateOTD } from '@/interfaces';
import BaseController from '@/http/controllers/BaseController' import BaseController from '@/http/controllers/BaseController'
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from '@/http/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from '@/http/middleware/asyncMiddleware';
@@ -10,21 +11,31 @@ import ItemsService from '@/services/Items/ItemsService';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListing from '@/services/DynamicListing/DynamicListing';
@Service()
export default class SalesEstimatesController extends BaseController { export default class SalesEstimatesController extends BaseController {
@Inject()
saleEstimateService: SaleEstimateService;
@Inject()
itemsService: ItemsService;
@Inject()
customersService: CustomersService;
/** /**
* Router constructor. * Router constructor.
*/ */
static router() { router() {
const router = express.Router(); const router = Router();
router.post( router.post(
'/', '/',
this.estimateValidationSchema, this.estimateValidationSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateEstimateCustomerExistance), asyncMiddleware(this.validateEstimateCustomerExistance.bind(this)),
asyncMiddleware(this.validateEstimateNumberExistance), asyncMiddleware(this.validateEstimateNumberExistance.bind(this)),
asyncMiddleware(this.validateEstimateEntriesItemsExistance), asyncMiddleware(this.validateEstimateEntriesItemsExistance.bind(this)),
asyncMiddleware(this.newEstimate) asyncMiddleware(this.newEstimate.bind(this))
); );
router.post( router.post(
'/:id', [ '/:id', [
@@ -32,33 +43,33 @@ export default class SalesEstimatesController extends BaseController {
...this.estimateValidationSchema, ...this.estimateValidationSchema,
], ],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateEstimateIdExistance), asyncMiddleware(this.validateEstimateIdExistance.bind(this)),
asyncMiddleware(this.validateEstimateCustomerExistance), asyncMiddleware(this.validateEstimateCustomerExistance.bind(this)),
asyncMiddleware(this.validateEstimateNumberExistance), asyncMiddleware(this.validateEstimateNumberExistance.bind(this)),
asyncMiddleware(this.validateEstimateEntriesItemsExistance), asyncMiddleware(this.validateEstimateEntriesItemsExistance.bind(this)),
asyncMiddleware(this.valdiateInvoiceEntriesIdsExistance), asyncMiddleware(this.valdiateInvoiceEntriesIdsExistance.bind(this)),
asyncMiddleware(this.editEstimate) asyncMiddleware(this.editEstimate.bind(this))
); );
router.delete( router.delete(
'/:id', [ '/:id', [
this.validateSpecificEstimateSchema, this.validateSpecificEstimateSchema,
], ],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateEstimateIdExistance), asyncMiddleware(this.validateEstimateIdExistance.bind(this)),
asyncMiddleware(this.deleteEstimate) asyncMiddleware(this.deleteEstimate.bind(this))
); );
router.get( router.get(
'/:id', '/:id',
this.validateSpecificEstimateSchema, this.validateSpecificEstimateSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateEstimateIdExistance), asyncMiddleware(this.validateEstimateIdExistance.bind(this)),
asyncMiddleware(this.getEstimate) asyncMiddleware(this.getEstimate.bind(this))
); );
router.get( router.get(
'/', '/',
this.validateEstimateListSchema, this.validateEstimateListSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.getEstimates) asyncMiddleware(this.getEstimates.bind(this))
); );
return router; return router;
} }
@@ -66,7 +77,7 @@ export default class SalesEstimatesController extends BaseController {
/** /**
* Estimate validation schema. * Estimate validation schema.
*/ */
static get estimateValidationSchema() { get estimateValidationSchema() {
return [ return [
check('customer_id').exists().isNumeric().toInt(), check('customer_id').exists().isNumeric().toInt(),
check('estimate_date').exists().isISO8601(), check('estimate_date').exists().isISO8601(),
@@ -90,7 +101,7 @@ export default class SalesEstimatesController extends BaseController {
/** /**
* Specific sale estimate validation schema. * Specific sale estimate validation schema.
*/ */
static get validateSpecificEstimateSchema() { get validateSpecificEstimateSchema() {
return [ return [
param('id').exists().isNumeric().toInt(), param('id').exists().isNumeric().toInt(),
]; ];
@@ -99,7 +110,7 @@ export default class SalesEstimatesController extends BaseController {
/** /**
* Sales estimates list validation schema. * Sales estimates list validation schema.
*/ */
static get validateEstimateListSchema() { get validateEstimateListSchema() {
return [ return [
query('custom_view_id').optional().isNumeric().toInt(), query('custom_view_id').optional().isNumeric().toInt(),
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),
@@ -116,12 +127,13 @@ export default class SalesEstimatesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateEstimateCustomerExistance(req, res, next) { async validateEstimateCustomerExistance(req: Request, res: Response, next: Function) {
const estimate = { ...req.body }; const estimate = { ...req.body };
const isCustomerExists = await CustomersService.isCustomerExists( const { Customer } = req.models
estimate.customer_id
); const foundCustomer = await Customer.query().findById(estimate.customer_id);
if (!isCustomerExists) {
if (!foundCustomer) {
return res.status(404).send({ return res.status(404).send({
errors: [{ type: 'CUSTOMER.ID.NOT.FOUND', code: 200 }], errors: [{ type: 'CUSTOMER.ID.NOT.FOUND', code: 200 }],
}); });
@@ -135,10 +147,12 @@ export default class SalesEstimatesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateEstimateNumberExistance(req, res, next) { async validateEstimateNumberExistance(req: Request, res: Response, next: Function) {
const estimate = { ...req.body }; const estimate = { ...req.body };
const { tenantId } = req;
const isEstNumberUnqiue = await SaleEstimateService.isEstimateNumberUnique( const isEstNumberUnqiue = await this.saleEstimateService.isEstimateNumberUnique(
tenantId,
estimate.estimate_number, estimate.estimate_number,
req.params.id, req.params.id,
); );
@@ -156,12 +170,13 @@ export default class SalesEstimatesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateEstimateEntriesItemsExistance(req, res, next) { async validateEstimateEntriesItemsExistance(req: Request, res: Response, next: Function) {
const tenantId = req.tenantId;
const estimate = { ...req.body }; const estimate = { ...req.body };
const estimateItemsIds = estimate.entries.map(e => e.item_id); const estimateItemsIds = estimate.entries.map(e => e.item_id);
// Validate items ids in estimate entries exists. // Validate items ids in estimate entries exists.
const notFoundItemsIds = await ItemsService.isItemsIdsExists(estimateItemsIds); const notFoundItemsIds = await this.itemsService.isItemsIdsExists(tenantId, estimateItemsIds);
if (notFoundItemsIds.length > 0) { if (notFoundItemsIds.length > 0) {
return res.boom.badRequest(null, { return res.boom.badRequest(null, {
@@ -177,9 +192,12 @@ export default class SalesEstimatesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateEstimateIdExistance(req, res, next) { async validateEstimateIdExistance(req: Request, res: Response, next: Function) {
const { id: estimateId } = req.params; const { id: estimateId } = req.params;
const storedEstimate = await SaleEstimateService.getEstimate(estimateId); const { tenantId } = req;
const storedEstimate = await this.saleEstimateService
.getEstimate(tenantId, estimateId);
if (!storedEstimate) { if (!storedEstimate) {
return res.status(404).send({ return res.status(404).send({
@@ -195,14 +213,16 @@ export default class SalesEstimatesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async valdiateInvoiceEntriesIdsExistance(req, res, next) { async valdiateInvoiceEntriesIdsExistance(req: Request, res: Response, next: Function) {
const { ItemEntry } = req.models;
const { id: saleInvoiceId } = req.params; const { id: saleInvoiceId } = req.params;
const saleInvoice = { ...req.body }; const saleInvoice = { ...req.body };
const entriesIds = saleInvoice.entries const entriesIds = saleInvoice.entries
.filter(e => e.id) .filter(e => e.id)
.map((e) => e.id); .map((e) => e.id);
const foundEntries = await ItemEntry.tenant().query() const foundEntries = await ItemEntry.query()
.whereIn('id', entriesIds) .whereIn('id', entriesIds)
.where('reference_type', 'SaleInvoice') .where('reference_type', 'SaleInvoice')
.where('reference_id', saleInvoiceId); .where('reference_id', saleInvoiceId);
@@ -221,15 +241,14 @@ export default class SalesEstimatesController extends BaseController {
* @param {Response} res - * @param {Response} res -
* @return {Response} res - * @return {Response} res -
*/ */
static async newEstimate(req, res) { async newEstimate(req: Request, res: Response) {
const estimate = { const { tenantId } = req;
...req.body, const estimateOTD: ISaleEstimateOTD = matchedData(req, {
entries: req.body.entries.map((entry) => ({ locations: ['body'],
...entry, includeOptionals: true,
amount: ItemEntry.calcAmount(entry), });
})), const storedEstimate = await this.saleEstimateService
}; .createEstimate(tenantId, estimateOTD);
const storedEstimate = await SaleEstimateService.createEstimate(estimate);
return res.status(200).send({ id: storedEstimate.id }); return res.status(200).send({ id: storedEstimate.id });
} }
@@ -239,12 +258,16 @@ export default class SalesEstimatesController extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async editEstimate(req, res) { async editEstimate(req: Request, res: Response) {
const { id: estimateId } = req.params; const { id: estimateId } = req.params;
const estimate = { ...req.body }; const { tenantId } = req;
const estimateOTD: ISaleEstimateOTD = matchedData(req, {
locations: ['body'],
includeOptionals: true,
});
// Update estimate with associated estimate entries. // Update estimate with associated estimate entries.
await SaleEstimateService.editEstimate(estimateId, estimate); await this.saleEstimateService.editEstimate(tenantId, estimateId, estimateOTD);
return res.status(200).send({ id: estimateId }); return res.status(200).send({ id: estimateId });
} }
@@ -254,9 +277,11 @@ export default class SalesEstimatesController extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async deleteEstimate(req, res) { async deleteEstimate(req: Request, res: Response) {
const { id: estimateId } = req.params; const { id: estimateId } = req.params;
await SaleEstimateService.deleteEstimate(estimateId); const { tenantId } = req;
await this.saleEstimateService.deleteEstimate(tenantId, estimateId);
return res.status(200).send({ id: estimateId }); return res.status(200).send({ id: estimateId });
} }
@@ -264,9 +289,12 @@ export default class SalesEstimatesController extends BaseController {
/** /**
* Retrieve the given estimate with associated entries. * Retrieve the given estimate with associated entries.
*/ */
static async getEstimate(req, res) { async getEstimate(req: Request, res: Response) {
const { id: estimateId } = req.params; const { id: estimateId } = req.params;
const estimate = await SaleEstimateService.getEstimateWithEntries(estimateId); const { tenantId } = req;
const estimate = await this.saleEstimateService
.getEstimateWithEntries(tenantId, estimateId);
return res.status(200).send({ estimate }); return res.status(200).send({ estimate });
} }
@@ -276,7 +304,7 @@ export default class SalesEstimatesController extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async getEstimates(req, res) { async getEstimates(req: Request, res: Response) {
const filter = { const filter = {
filter_roles: [], filter_roles: [],
sort_order: 'asc', sort_order: 'asc',
@@ -288,7 +316,7 @@ export default class SalesEstimatesController extends BaseController {
filter.filter_roles = JSON.parse(filter.stringified_filter_roles); filter.filter_roles = JSON.parse(filter.stringified_filter_roles);
} }
const { SaleEstimate, Resource, View } = req.models; const { SaleEstimate, Resource, View } = req.models;
const resource = await Resource.tenant().query() const resource = await Resource.query()
.remember() .remember()
.where('name', 'sales_estimates') .where('name', 'sales_estimates')
.withGraphFetched('fields') .withGraphFetched('fields')

View File

@@ -1,34 +1,40 @@
import express from 'express'; import express from 'express';
import { check, param, query } from 'express-validator'; import { check, param, query, matchedData } from 'express-validator';
import { difference } from 'lodash'; import { difference } from 'lodash';
import { raw } from 'objection'; import { raw } from 'objection';
import { ItemEntry } from '@/models'; import { Service, Inject } from 'typedi';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from '@/http/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import SaleInvoiceService from '@/services/Sales/SalesInvoices'; import SaleInvoiceService from '@/services/Sales/SalesInvoices';
import ItemsService from '@/services/Items/ItemsService'; import ItemsService from '@/services/Items/ItemsService';
import CustomersService from '@/services/Customers/CustomersService';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListing from '@/services/DynamicListing/DynamicListing';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing'; import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing';
import { Customer, Item } from '../../../models'; import { ISaleInvoiceOTD } from '@/interfaces';
@Service()
export default class SaleInvoicesController { export default class SaleInvoicesController {
@Inject()
itemsService: ItemsService;
@Inject()
saleInvoiceService: SaleInvoiceService;
/** /**
* Router constructor. * Router constructor.
*/ */
static router() { router() {
const router = express.Router(); const router = express.Router();
router.post( router.post(
'/', '/',
this.saleInvoiceValidationSchema, this.saleInvoiceValidationSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateInvoiceCustomerExistance), asyncMiddleware(this.validateInvoiceCustomerExistance.bind(this)),
asyncMiddleware(this.validateInvoiceNumberUnique), asyncMiddleware(this.validateInvoiceNumberUnique.bind(this)),
asyncMiddleware(this.validateInvoiceItemsIdsExistance), asyncMiddleware(this.validateInvoiceItemsIdsExistance.bind(this)),
asyncMiddleware(this.validateNonSellableEntriesItems), asyncMiddleware(this.validateNonSellableEntriesItems.bind(this)),
asyncMiddleware(this.newSaleInvoice) asyncMiddleware(this.newSaleInvoice.bind(this))
); );
router.post( router.post(
'/:id', '/:id',
@@ -37,38 +43,38 @@ export default class SaleInvoicesController {
...this.specificSaleInvoiceValidation, ...this.specificSaleInvoiceValidation,
], ],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateInvoiceExistance), asyncMiddleware(this.validateInvoiceExistance.bind(this)),
asyncMiddleware(this.validateInvoiceCustomerExistance), asyncMiddleware(this.validateInvoiceCustomerExistance.bind(this)),
asyncMiddleware(this.validateInvoiceNumberUnique), asyncMiddleware(this.validateInvoiceNumberUnique.bind(this)),
asyncMiddleware(this.validateInvoiceItemsIdsExistance), asyncMiddleware(this.validateInvoiceItemsIdsExistance.bind(this)),
asyncMiddleware(this.valdiateInvoiceEntriesIdsExistance), asyncMiddleware(this.valdiateInvoiceEntriesIdsExistance.bind(this)),
asyncMiddleware(this.validateEntriesIdsExistance), asyncMiddleware(this.validateEntriesIdsExistance.bind(this)),
asyncMiddleware(this.validateNonSellableEntriesItems), asyncMiddleware(this.validateNonSellableEntriesItems.bind(this)),
asyncMiddleware(this.editSaleInvoice) asyncMiddleware(this.editSaleInvoice.bind(this))
); );
router.delete( router.delete(
'/:id', '/:id',
this.specificSaleInvoiceValidation, this.specificSaleInvoiceValidation,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateInvoiceExistance), asyncMiddleware(this.validateInvoiceExistance.bind(this)),
asyncMiddleware(this.deleteSaleInvoice) asyncMiddleware(this.deleteSaleInvoice.bind(this))
); );
router.get( router.get(
'/due_invoices', '/due_invoices',
this.dueSalesInvoicesListValidationSchema, this.dueSalesInvoicesListValidationSchema,
asyncMiddleware(this.getDueSalesInvoice), asyncMiddleware(this.getDueSalesInvoice.bind(this)),
); );
router.get( router.get(
'/:id', '/:id',
this.specificSaleInvoiceValidation, this.specificSaleInvoiceValidation,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateInvoiceExistance), asyncMiddleware(this.validateInvoiceExistance.bind(this)),
asyncMiddleware(this.getSaleInvoice) asyncMiddleware(this.getSaleInvoice.bind(this))
); );
router.get( router.get(
'/', '/',
this.saleInvoiceListValidationSchema, this.saleInvoiceListValidationSchema,
asyncMiddleware(this.getSalesInvoices) asyncMiddleware(this.getSalesInvoices.bind(this))
) )
return router; return router;
} }
@@ -76,7 +82,7 @@ export default class SaleInvoicesController {
/** /**
* Sale invoice validation schema. * Sale invoice validation schema.
*/ */
static get saleInvoiceValidationSchema() { get saleInvoiceValidationSchema() {
return [ return [
check('customer_id').exists().isNumeric().toInt(), check('customer_id').exists().isNumeric().toInt(),
check('invoice_date').exists().isISO8601(), check('invoice_date').exists().isISO8601(),
@@ -102,14 +108,14 @@ export default class SaleInvoicesController {
/** /**
* Specific sale invoice validation schema. * Specific sale invoice validation schema.
*/ */
static get specificSaleInvoiceValidation() { get specificSaleInvoiceValidation() {
return [param('id').exists().isNumeric().toInt()]; return [param('id').exists().isNumeric().toInt()];
} }
/** /**
* Sales invoices list validation schema. * Sales invoices list validation schema.
*/ */
static get saleInvoiceListValidationSchema() { get saleInvoiceListValidationSchema() {
return [ return [
query('custom_view_id').optional().isNumeric().toInt(), query('custom_view_id').optional().isNumeric().toInt(),
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),
@@ -120,8 +126,10 @@ export default class SaleInvoicesController {
]; ];
} }
/**
static get dueSalesInvoicesListValidationSchema() { * Due sale invoice list validation schema.
*/
get dueSalesInvoicesListValidationSchema() {
return [ return [
query('customer_id').optional().isNumeric().toInt(), query('customer_id').optional().isNumeric().toInt(),
] ]
@@ -133,11 +141,12 @@ export default class SaleInvoicesController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateInvoiceCustomerExistance(req, res, next) { async validateInvoiceCustomerExistance(req: Request, res: Response, next: Function) {
const saleInvoice = { ...req.body }; const saleInvoice = { ...req.body };
const isCustomerIDExists = await CustomersService.isCustomerExists( const { Customer } = req.models;
saleInvoice.customer_id
); const isCustomerIDExists = await Customer.query().findById(saleInvoice.customer_id);
if (!isCustomerIDExists) { if (!isCustomerIDExists) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'CUSTOMER.ID.NOT.EXISTS', code: 200 }], errors: [{ type: 'CUSTOMER.ID.NOT.EXISTS', code: 200 }],
@@ -148,15 +157,17 @@ export default class SaleInvoicesController {
/** /**
* Validate whether sale invoice items ids esits on the storage. * Validate whether sale invoice items ids esits on the storage.
* @param {Request} req * @param {Request} req -
* @param {Response} res * @param {Response} res -
* @param {Function} next * @param {Function} next -
*/ */
static async validateInvoiceItemsIdsExistance(req, res, next) { async validateInvoiceItemsIdsExistance(req: Request, res: Response, next: Function) {
const { tenantId } = req;
const saleInvoice = { ...req.body }; const saleInvoice = { ...req.body };
const entriesItemsIds = saleInvoice.entries.map((e) => e.item_id); const entriesItemsIds = saleInvoice.entries.map((e) => e.item_id);
const isItemsIdsExists = await ItemsService.isItemsIdsExists(
entriesItemsIds const isItemsIdsExists = await this.itemsService.isItemsIdsExists(
tenantId, entriesItemsIds,
); );
if (isItemsIdsExists.length > 0) { if (isItemsIdsExists.length > 0) {
return res.status(400).send({ return res.status(400).send({
@@ -173,9 +184,12 @@ export default class SaleInvoicesController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateInvoiceNumberUnique(req, res, next) { async validateInvoiceNumberUnique(req: Request, res: Response, next: Function) {
const { tenantId } = req;
const saleInvoice = { ...req.body }; const saleInvoice = { ...req.body };
const isInvoiceNoExists = await SaleInvoiceService.isSaleInvoiceNumberExists(
const isInvoiceNoExists = await this.saleInvoiceService.isSaleInvoiceNumberExists(
tenantId,
saleInvoice.invoice_no, saleInvoice.invoice_no,
req.params.id req.params.id
); );
@@ -195,10 +209,12 @@ export default class SaleInvoicesController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateInvoiceExistance(req, res, next) { async validateInvoiceExistance(req: Request, res: Response, next: Function) {
const { id: saleInvoiceId } = req.params; const { id: saleInvoiceId } = req.params;
const isSaleInvoiceExists = await SaleInvoiceService.isSaleInvoiceExists( const { tenantId } = req;
saleInvoiceId
const isSaleInvoiceExists = await this.saleInvoiceService.isSaleInvoiceExists(
tenantId, saleInvoiceId,
); );
if (!isSaleInvoiceExists) { if (!isSaleInvoiceExists) {
return res return res
@@ -214,12 +230,13 @@ export default class SaleInvoicesController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async valdiateInvoiceEntriesIdsExistance(req, res, next) { async valdiateInvoiceEntriesIdsExistance(req: Request, res: Response, next: Function) {
const { tenantId } = req;
const saleInvoice = { ...req.body }; const saleInvoice = { ...req.body };
const entriesItemsIds = saleInvoice.entries.map((e) => e.item_id); const entriesItemsIds = saleInvoice.entries.map((e) => e.item_id);
const isItemsIdsExists = await ItemsService.isItemsIdsExists( const isItemsIdsExists = await this.itemsService.isItemsIdsExists(
entriesItemsIds tenantId, entriesItemsIds,
); );
if (isItemsIdsExists.length > 0) { if (isItemsIdsExists.length > 0) {
return res.status(400).send({ return res.status(400).send({
@@ -235,14 +252,16 @@ export default class SaleInvoicesController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateEntriesIdsExistance(req, res, next) { async validateEntriesIdsExistance(req: Request, res: Response, next: Function) {
const { ItemEntry } = req.models;
const { id: saleInvoiceId } = req.params; const { id: saleInvoiceId } = req.params;
const saleInvoice = { ...req.body }; const saleInvoice = { ...req.body };
const entriesIds = saleInvoice.entries const entriesIds = saleInvoice.entries
.filter(e => e.id) .filter(e => e.id)
.map(e => e.id); .map(e => e.id);
const storedEntries = await ItemEntry.tenant().query() const storedEntries = await ItemEntry.query()
.whereIn('reference_id', [saleInvoiceId]) .whereIn('reference_id', [saleInvoiceId])
.whereIn('reference_type', ['SaleInvoice']); .whereIn('reference_type', ['SaleInvoice']);
@@ -265,7 +284,7 @@ export default class SaleInvoicesController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateNonSellableEntriesItems(req, res, next) { async validateNonSellableEntriesItems(req: Request, res: Response, next: Function) {
const { Item } = req.models; const { Item } = req.models;
const saleInvoice = { ...req.body }; const saleInvoice = { ...req.body };
const itemsIds = saleInvoice.entries.map(e => e.item_id); const itemsIds = saleInvoice.entries.map(e => e.item_id);
@@ -291,22 +310,17 @@ export default class SaleInvoicesController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async newSaleInvoice(req, res) { async newSaleInvoice(req: Request, res: Response) {
const errorReasons = []; const { tenantId } = req;
const saleInvoice = { const saleInvoiceOTD: ISaleInvoiceOTD = matchedData(req, {
...req.body, locations: ['body'],
entries: req.body.entries.map((entry) => ({ includeOptionals: true
...entry, });
amount: ItemEntry.calcAmount(entry),
})),
};
// Creates a new sale invoice with associated entries. // Creates a new sale invoice with associated entries.
const storedSaleInvoice = await SaleInvoiceService.createSaleInvoice( const storedSaleInvoice = await this.saleInvoiceService.createSaleInvoice(
saleInvoice tenantId, saleInvoiceOTD,
); );
// InventoryService.trackingInventoryLotsCost();
return res.status(200).send({ id: storedSaleInvoice.id }); return res.status(200).send({ id: storedSaleInvoice.id });
} }
@@ -316,19 +330,18 @@ export default class SaleInvoicesController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async editSaleInvoice(req, res) { async editSaleInvoice(req: Request, res: Response) {
const { tenantId } = req;
const { id: saleInvoiceId } = req.params; const { id: saleInvoiceId } = req.params;
const saleInvoice = {
...req.body,
entries: req.body.entries.map((entry) => ({
...entry,
amount: ItemEntry.calcAmount(entry),
})),
};
// Update the given sale invoice details.
await SaleInvoiceService.editSaleInvoice(saleInvoiceId, saleInvoice);
return res.status(200).send({ id: saleInvoice.id }); const saleInvoiceOTD: ISaleInvoiceOTD = matchedData(req, {
locations: ['body'],
includeOptionals: true
});
// Update the given sale invoice details.
await this.saleInvoiceService.editSaleInvoice(tenantId, saleInvoiceId, saleInvoiceOTD);
return res.status(200).send({ id: saleInvoiceId });
} }
/** /**
@@ -337,10 +350,12 @@ export default class SaleInvoicesController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async deleteSaleInvoice(req, res) { async deleteSaleInvoice(req: Request, res: Response) {
const { id: saleInvoiceId } = req.params; const { id: saleInvoiceId } = req.params;
const { tenantId } = req;
// Deletes the sale invoice with associated entries and journal transaction. // Deletes the sale invoice with associated entries and journal transaction.
await SaleInvoiceService.deleteSaleInvoice(saleInvoiceId); await this.saleInvoiceService.deleteSaleInvoice(tenantId, saleInvoiceId);
return res.status(200).send({ id: saleInvoiceId }); return res.status(200).send({ id: saleInvoiceId });
} }
@@ -350,10 +365,12 @@ export default class SaleInvoicesController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async getSaleInvoice(req, res) { async getSaleInvoice(req: Request, res: Response) {
const { id: saleInvoiceId } = req.params; const { id: saleInvoiceId } = req.params;
const saleInvoice = await SaleInvoiceService.getSaleInvoiceWithEntries( const { tenantId } = req;
saleInvoiceId
const saleInvoice = await this.saleInvoiceService.getSaleInvoiceWithEntries(
tenantId, saleInvoiceId,
); );
return res.status(200).send({ sale_invoice: saleInvoice }); return res.status(200).send({ sale_invoice: saleInvoice });
} }
@@ -363,13 +380,14 @@ export default class SaleInvoicesController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async getDueSalesInvoice(req, res) { async getDueSalesInvoice(req: Request, res: Response) {
const { Customer, SaleInvoice } = req.models;
const { tenantId } = req;
const filter = { const filter = {
customer_id: null, customer_id: null,
...req.query, ...req.query,
}; };
const { Customer, SaleInvoice } = req.models;
if (filter.customer_id) { if (filter.customer_id) {
const foundCustomer = await Customer.query().findById(filter.customer_id); const foundCustomer = await Customer.query().findById(filter.customer_id);
@@ -381,7 +399,6 @@ export default class SaleInvoicesController {
} }
const dueSalesInvoices = await SaleInvoice.query().onBuild((query) => { const dueSalesInvoices = await SaleInvoice.query().onBuild((query) => {
query.where(raw('BALANCE - PAYMENT_AMOUNT > 0')); query.where(raw('BALANCE - PAYMENT_AMOUNT > 0'));
if (filter.customer_id) { if (filter.customer_id) {
query.where('customer_id', filter.customer_id); query.where('customer_id', filter.customer_id);
} }
@@ -397,7 +414,7 @@ export default class SaleInvoicesController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async getSalesInvoices(req, res) { async getSalesInvoices(req, res) {
const filter = { const filter = {
filter_roles: [], filter_roles: [],
sort_order: 'asc', sort_order: 'asc',

View File

@@ -1,9 +1,8 @@
import express from 'express'; import { Router, Request, Response } from 'express';
import { check, param, query } from 'express-validator'; import { check, param, query, matchedData } from 'express-validator';
import { ItemEntry } from '@/models'; import { Inject, Service } from 'typedi';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from '@/http/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import CustomersService from '@/services/Customers/CustomersService';
import AccountsService from '@/services/Accounts/AccountsService'; import AccountsService from '@/services/Accounts/AccountsService';
import ItemsService from '@/services/Items/ItemsService'; import ItemsService from '@/services/Items/ItemsService';
import SaleReceiptService from '@/services/Sales/SalesReceipts'; import SaleReceiptService from '@/services/Sales/SalesReceipts';
@@ -13,12 +12,22 @@ import {
dynamicListingErrorsToResponse dynamicListingErrorsToResponse
} from '@/services/DynamicListing/HasDynamicListing'; } from '@/services/DynamicListing/HasDynamicListing';
@Service()
export default class SalesReceiptsController { export default class SalesReceiptsController {
@Inject()
saleReceiptService: SaleReceiptService;
@Inject()
accountsService: AccountsService;
@Inject()
itemsService: ItemsService;
/** /**
* Router constructor. * Router constructor.
*/ */
static router() { router() {
const router = express.Router(); const router = Router();
router.post( router.post(
'/:id', [ '/:id', [
@@ -26,34 +35,34 @@ export default class SalesReceiptsController {
...this.salesReceiptsValidationSchema, ...this.salesReceiptsValidationSchema,
], ],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateSaleReceiptExistance), asyncMiddleware(this.validateSaleReceiptExistance.bind(this)),
asyncMiddleware(this.validateReceiptCustomerExistance), asyncMiddleware(this.validateReceiptCustomerExistance.bind(this)),
asyncMiddleware(this.validateReceiptDepositAccountExistance), asyncMiddleware(this.validateReceiptDepositAccountExistance.bind(this)),
asyncMiddleware(this.validateReceiptItemsIdsExistance), asyncMiddleware(this.validateReceiptItemsIdsExistance.bind(this)),
asyncMiddleware(this.validateReceiptEntriesIds), asyncMiddleware(this.validateReceiptEntriesIds.bind(this)),
asyncMiddleware(this.editSaleReceipt) asyncMiddleware(this.editSaleReceipt.bind(this))
); );
router.post( router.post(
'/', '/',
this.salesReceiptsValidationSchema, this.salesReceiptsValidationSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateReceiptCustomerExistance), asyncMiddleware(this.validateReceiptCustomerExistance.bind(this)),
asyncMiddleware(this.validateReceiptDepositAccountExistance), asyncMiddleware(this.validateReceiptDepositAccountExistance.bind(this)),
asyncMiddleware(this.validateReceiptItemsIdsExistance), asyncMiddleware(this.validateReceiptItemsIdsExistance.bind(this)),
asyncMiddleware(this.newSaleReceipt) asyncMiddleware(this.newSaleReceipt.bind(this))
); );
router.delete( router.delete(
'/:id', '/:id',
this.specificReceiptValidationSchema, this.specificReceiptValidationSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateSaleReceiptExistance), asyncMiddleware(this.validateSaleReceiptExistance.bind(this)),
asyncMiddleware(this.deleteSaleReceipt) asyncMiddleware(this.deleteSaleReceipt.bind(this))
); );
router.get( router.get(
'/', '/',
this.listSalesReceiptsValidationSchema, this.listSalesReceiptsValidationSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.listingSalesReceipts) asyncMiddleware(this.listingSalesReceipts.bind(this))
); );
return router; return router;
} }
@@ -62,7 +71,7 @@ export default class SalesReceiptsController {
* Sales receipt validation schema. * Sales receipt validation schema.
* @return {Array} * @return {Array}
*/ */
static get salesReceiptsValidationSchema() { get salesReceiptsValidationSchema() {
return [ return [
check('customer_id').exists().isNumeric().toInt(), check('customer_id').exists().isNumeric().toInt(),
check('deposit_account_id').exists().isNumeric().toInt(), check('deposit_account_id').exists().isNumeric().toInt(),
@@ -88,7 +97,7 @@ export default class SalesReceiptsController {
/** /**
* Specific sale receipt validation schema. * Specific sale receipt validation schema.
*/ */
static get specificReceiptValidationSchema() { get specificReceiptValidationSchema() {
return [ return [
param('id').exists().isNumeric().toInt() param('id').exists().isNumeric().toInt()
]; ];
@@ -97,7 +106,7 @@ export default class SalesReceiptsController {
/** /**
* List sales receipts validation schema. * List sales receipts validation schema.
*/ */
static get listSalesReceiptsValidationSchema() { get listSalesReceiptsValidationSchema() {
return [ return [
query('custom_view_id').optional().isNumeric().toInt(), query('custom_view_id').optional().isNumeric().toInt(),
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),
@@ -113,11 +122,15 @@ export default class SalesReceiptsController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async validateSaleReceiptExistance(req, res, next) { async validateSaleReceiptExistance(req: Request, res: Response, next: Function) {
const { tenantId } = req;
const { id: saleReceiptId } = req.params; const { id: saleReceiptId } = req.params;
const isSaleReceiptExists = await SaleReceiptService.isSaleReceiptExists(
saleReceiptId const isSaleReceiptExists = await this.saleReceiptService
); .isSaleReceiptExists(
tenantId,
saleReceiptId,
);
if (!isSaleReceiptExists) { if (!isSaleReceiptExists) {
return res.status(404).send({ return res.status(404).send({
errors: [{ type: 'SALE.RECEIPT.NOT.FOUND', code: 200 }], errors: [{ type: 'SALE.RECEIPT.NOT.FOUND', code: 200 }],
@@ -132,12 +145,13 @@ export default class SalesReceiptsController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateReceiptCustomerExistance(req, res, next) { async validateReceiptCustomerExistance(req: Request, res: Response, next: Function) {
const saleReceipt = { ...req.body }; const saleReceipt = { ...req.body };
const isCustomerExists = await CustomersService.isCustomerExists( const { Customer } = req.models;
saleReceipt.customer_id
); const foundCustomer = await Customer.query().findById(saleReceipt.customer_id);
if (!isCustomerExists) {
if (!foundCustomer) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'CUSTOMER.ID.NOT.EXISTS', code: 200 }], errors: [{ type: 'CUSTOMER.ID.NOT.EXISTS', code: 200 }],
}); });
@@ -151,9 +165,12 @@ export default class SalesReceiptsController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateReceiptDepositAccountExistance(req, res, next) { async validateReceiptDepositAccountExistance(req: Request, res: Response, next: Function) {
const { tenantId } = req;
const saleReceipt = { ...req.body }; const saleReceipt = { ...req.body };
const isDepositAccountExists = await AccountsService.isAccountExists( const isDepositAccountExists = await this.accountsService.isAccountExists(
tenantId,
saleReceipt.deposit_account_id saleReceipt.deposit_account_id
); );
if (!isDepositAccountExists) { if (!isDepositAccountExists) {
@@ -170,10 +187,14 @@ export default class SalesReceiptsController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateReceiptItemsIdsExistance(req, res, next) { async validateReceiptItemsIdsExistance(req: Request, res: Response, next: Function) {
const { tenantId } = req;
const saleReceipt = { ...req.body }; const saleReceipt = { ...req.body };
const estimateItemsIds = saleReceipt.entries.map((e) => e.item_id); const estimateItemsIds = saleReceipt.entries.map((e) => e.item_id);
const notFoundItemsIds = await ItemsService.isItemsIdsExists(
const notFoundItemsIds = await this.itemsService.isItemsIdsExists(
tenantId,
estimateItemsIds estimateItemsIds
); );
if (notFoundItemsIds.length > 0) { if (notFoundItemsIds.length > 0) {
@@ -188,15 +209,19 @@ export default class SalesReceiptsController {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
static async validateReceiptEntriesIds(req, res, next) { async validateReceiptEntriesIds(req: Request, res: Response, next: Function) {
const { tenantId } = req;
const saleReceipt = { ...req.body }; const saleReceipt = { ...req.body };
const { id: saleReceiptId } = req.params; const { id: saleReceiptId } = req.params;
// Validate the entries IDs that not stored or associated to the sale receipt. // Validate the entries IDs that not stored or associated to the sale receipt.
const notExistsEntriesIds = await SaleReceiptService.isSaleReceiptEntriesIDsExists( const notExistsEntriesIds = await this.saleReceiptService
saleReceiptId, .isSaleReceiptEntriesIDsExists(
saleReceipt tenantId,
); saleReceiptId,
saleReceipt,
);
if (notExistsEntriesIds.length > 0) { if (notExistsEntriesIds.length > 0) {
return res.status(400).send({ errors: [{ return res.status(400).send({ errors: [{
type: 'ENTRIES.IDS.NOT.FOUND', type: 'ENTRIES.IDS.NOT.FOUND',
@@ -212,19 +237,19 @@ export default class SalesReceiptsController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async newSaleReceipt(req, res) { async newSaleReceipt(req: Request, res: Response) {
const saleReceipt = { const { tenantId } = req;
...req.body,
entries: req.body.entries.map((entry) => ({
...entry,
amount: ItemEntry.calcAmount(entry),
})),
};
const saleReceipt = matchedData(req, {
locations: ['body'],
includeOptionals: true,
});
// Store the given sale receipt details with associated entries. // Store the given sale receipt details with associated entries.
const storedSaleReceipt = await SaleReceiptService.createSaleReceipt( const storedSaleReceipt = await this.saleReceiptService
saleReceipt .createSaleReceipt(
); tenantId,
saleReceipt,
);
return res.status(200).send({ id: storedSaleReceipt.id }); return res.status(200).send({ id: storedSaleReceipt.id });
} }
@@ -233,11 +258,12 @@ export default class SalesReceiptsController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async deleteSaleReceipt(req, res) { async deleteSaleReceipt(req: Request, res: Response) {
const { tenantId } = req;
const { id: saleReceiptId } = req.params; const { id: saleReceiptId } = req.params;
// Deletes the sale receipt. // Deletes the sale receipt.
await SaleReceiptService.deleteSaleReceipt(saleReceiptId); await this.saleReceiptService.deleteSaleReceipt(tenantId, saleReceiptId);
return res.status(200).send({ id: saleReceiptId }); return res.status(200).send({ id: saleReceiptId });
} }
@@ -248,9 +274,12 @@ export default class SalesReceiptsController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async editSaleReceipt(req, res) { async editSaleReceipt(req: Request, res: Response) {
const { tenantId } = req;
const { id: saleReceiptId } = req.params; const { id: saleReceiptId } = req.params;
const saleReceipt = { ...req.body }; const saleReceipt = { ...req.body };
const errorReasons = []; const errorReasons = [];
// Handle all errors with reasons messages. // Handle all errors with reasons messages.
@@ -258,7 +287,11 @@ export default class SalesReceiptsController {
return res.boom.badRequest(null, { errors: errorReasons }); return res.boom.badRequest(null, { errors: errorReasons });
} }
// Update the given sale receipt details. // Update the given sale receipt details.
await SaleReceiptService.editSaleReceipt(saleReceiptId, saleReceipt); await this.saleReceiptService.editSaleReceipt(
tenantId,
saleReceiptId,
saleReceipt,
);
return res.status(200).send(); return res.status(200).send();
} }
@@ -268,7 +301,7 @@ export default class SalesReceiptsController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
static async listingSalesReceipts(req, res) { async listingSalesReceipts(req: Request, res: Response) {
const filter = { const filter = {
filter_roles: [], filter_roles: [],
sort_order: 'asc', sort_order: 'asc',
@@ -280,7 +313,7 @@ export default class SalesReceiptsController {
filter.filter_roles = JSON.parse(filter.stringified_filter_roles); filter.filter_roles = JSON.parse(filter.stringified_filter_roles);
} }
const { SaleReceipt, Resource, View } = req.models; const { SaleReceipt, Resource, View } = req.models;
const resource = await Resource.tenant().query() const resource = await Resource.query()
.remember() .remember()
.where('name', 'sales_receipts') .where('name', 'sales_receipts')
.withGraphFetched('fields') .withGraphFetched('fields')

View File

@@ -1,4 +1,5 @@
import express from 'express'; import express from 'express';
import { Container } from 'typedi';
import SalesEstimates from './SalesEstimates'; import SalesEstimates from './SalesEstimates';
import SalesReceipts from './SalesReceipts'; import SalesReceipts from './SalesReceipts';
import SalesInvoices from './SalesInvoices' import SalesInvoices from './SalesInvoices'
@@ -11,10 +12,10 @@ export default {
router() { router() {
const router = express.Router(); const router = express.Router();
router.use('/invoices', SalesInvoices.router()); router.use('/invoices', Container.get(SalesInvoices).router());
router.use('/estimates', SalesEstimates.router()); router.use('/estimates', Container.get(SalesEstimates).router());
router.use('/receipts', SalesReceipts.router()); router.use('/receipts', Container.get(SalesReceipts).router());
router.use('/payment_receives', PaymentReceives.router()); router.use('/payment_receives', Container.get(PaymentReceives).router());
return router; return router;
} }

View File

@@ -25,14 +25,15 @@ export default class VouchersController {
this.generateVoucherSchema, this.generateVoucherSchema,
validateMiddleware, validateMiddleware,
PrettierMiddleware, PrettierMiddleware,
asyncMiddleware(this.validatePlanExistance), asyncMiddleware(this.validatePlanExistance.bind(this)),
asyncMiddleware(this.generateVoucher.bind(this)), asyncMiddleware(this.generateVoucher.bind(this)),
); );
router.post( router.post(
'/disable/:voucherId', '/disable/:voucherId',
validateMiddleware,
PrettierMiddleware, PrettierMiddleware,
asyncMiddleware(this.validateVoucherExistance), asyncMiddleware(this.validateVoucherExistance.bind(this)),
asyncMiddleware(this.validateNotDisabledVoucher), asyncMiddleware(this.validateNotDisabledVoucher.bind(this)),
asyncMiddleware(this.disableVoucher.bind(this)), asyncMiddleware(this.disableVoucher.bind(this)),
); );
router.post( router.post(
@@ -45,7 +46,7 @@ export default class VouchersController {
router.delete( router.delete(
'/:voucherId', '/:voucherId',
PrettierMiddleware, PrettierMiddleware,
asyncMiddleware(this.validateVoucherExistance), asyncMiddleware(this.validateVoucherExistance.bind(this)),
asyncMiddleware(this.deleteVoucher.bind(this)), asyncMiddleware(this.deleteVoucher.bind(this)),
); );
router.get( router.get(
@@ -158,23 +159,21 @@ export default class VouchersController {
* @param {Response} res * @param {Response} res
* @return {Response} * @return {Response}
*/ */
async generateVoucher(req: Request, res: Response) { async generateVoucher(req: Request, res: Response, next: Function) {
const { loop = 10, period, periodInterval, planId } = req.body; const { loop = 10, period, periodInterval, planId } = req.body;
const generatedVouchers: string[] = [];
const asyncOpers = [];
times(loop, () => { try {
const generateOper = this.voucherService await this.voucherService.generateVouchers(
.generateVoucher(period, periodInterval, planId) loop, period, periodInterval, planId,
.then((generatedVoucher: any) => { );
generatedVouchers.push(generatedVoucher) return res.status(200).send({
}); code: 100,
asyncOpers.push(generateOper); message: 'The vouchers have been generated successfully.'
}); });
} catch (error) {
return res.status(200).send({ console.log(error);
vouchers: generatedVouchers, next(error);
}); }
} }
/** /**

View File

@@ -49,7 +49,7 @@ export default (app) => {
dashboard.use('/api/account_types', AccountTypes.router()); dashboard.use('/api/account_types', AccountTypes.router());
dashboard.use('/api/accounting', Accounting.router()); dashboard.use('/api/accounting', Accounting.router());
dashboard.use('/api/views', Views.router()); dashboard.use('/api/views', Views.router());
dashboard.use('/api/items', Items.router()); dashboard.use('/api/items', Container.get(Items).router());
dashboard.use('/api/item_categories', Container.get(ItemCategories)); dashboard.use('/api/item_categories', Container.get(ItemCategories));
dashboard.use('/api/expenses', Expenses.router()); dashboard.use('/api/expenses', Expenses.router());
dashboard.use('/api/financial_statements', FinancialStatements.router()); dashboard.use('/api/financial_statements', FinancialStatements.router());

View File

@@ -2,6 +2,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import TenantsManager from '@/system/TenantsManager'; import TenantsManager from '@/system/TenantsManager';
import TenantModel from '@/models/TenantModel'; import TenantModel from '@/models/TenantModel';
import { Container } from 'typedi';
function loadModelsFromDirectory() { function loadModelsFromDirectory() {
const models = {}; const models = {};
@@ -45,6 +46,7 @@ export default async (req, res, next) => {
req.knex = knex; req.knex = knex;
req.organizationId = organizationId; req.organizationId = organizationId;
req.tenant = tenant; req.tenant = tenant;
req.tenantId = tenant.id;
req.models = { req.models = {
...Object.values(models).reduce((acc, model) => { ...Object.values(models).reduce((acc, model) => {
if (typeof model.resource.default !== 'undefined' && if (typeof model.resource.default !== 'undefined' &&
@@ -56,5 +58,8 @@ export default async (req, res, next) => {
return acc; return acc;
}, {}), }, {}),
}; };
Container.of(`tenant-${tenant.id}`).set('models', {
...req.models,
});
next(); next();
}; };

View File

@@ -0,0 +1,3 @@
export interface IBillOTD {};
export interface IBill {};

View File

@@ -11,3 +11,5 @@ export interface IBillPayment {
billNo: string, billNo: string,
entries: IBillPaymentEntry[], entries: IBillPaymentEntry[],
} }
export interface IBillPaymentOTD {};

View File

@@ -0,0 +1,4 @@
export interface IPaymentReceive { };
export interface IPaymentReceiveOTD { };

View File

@@ -0,0 +1,4 @@
export interface ISaleEstimate {};
export interface ISaleEstimateOTD {};

View File

@@ -1,5 +1,9 @@
import { IInventoryTransaction, IInventoryLotCost } from './InventoryTransaction'; import { IInventoryTransaction, IInventoryLotCost } from './InventoryTransaction';
import { IBillPaymentEntry, IBillPayment } from './BillPayment'; import {
IBillPaymentEntry,
IBillPayment,
IBillPaymentOTD,
} from './BillPayment';
import { IInventoryCostMethod } from './InventoryCostMethod'; import { IInventoryCostMethod } from './InventoryCostMethod';
import { IItemEntry } from './ItemEntry'; import { IItemEntry } from './ItemEntry';
import { IItem } from './Item'; import { IItem } from './Item';
@@ -16,10 +20,20 @@ import {
ISaleInvoice, ISaleInvoice,
ISaleInvoiceOTD, ISaleInvoiceOTD,
} from './SaleInvoice'; } from './SaleInvoice';
import {
IPaymentReceive,
IPaymentReceiveOTD,
} from './PaymentReceive';
import {
ISaleEstimate,
ISaleEstimateOTD,
} from './SaleEstimate';
export { export {
IBillPaymentEntry, IBillPaymentEntry,
IBillPayment, IBillPayment,
IBillPaymentOTD,
IInventoryTransaction, IInventoryTransaction,
IInventoryLotCost, IInventoryLotCost,
IInventoryCostMethod, IInventoryCostMethod,
@@ -38,4 +52,10 @@ export {
ISaleInvoice, ISaleInvoice,
ISaleInvoiceOTD, ISaleInvoiceOTD,
ISaleEstimate,
ISaleEstimateOTD,
IPaymentReceive,
IPaymentReceiveOTD,
}; };

View File

@@ -0,0 +1,8 @@
export default class MailNotificationSubscribeEnd {
handler(job) {
}
}

View File

@@ -0,0 +1,6 @@
export default class MailNotificationSubscribeEnd {
}

View File

@@ -0,0 +1,13 @@
export default class SMSNotificationSubscribeEnd {
handler(job) {
const { tenantId, subscriptionSlug } = job.attrs.data;
}
}

View File

@@ -0,0 +1,8 @@
export default class MailNotificationSubscribeEnd {
handler(job) {
}
}

View File

@@ -30,6 +30,22 @@ export default ({ agenda }: { agenda: Agenda }) => {
'send-voucher-via-email', 'send-voucher-via-email',
{ priority: 'high', concurrency: 1, }, { priority: 'high', concurrency: 1, },
new SendVoucherViaEmailJob().handler, new SendVoucherViaEmailJob().handler,
) );
// agenda.define(
// 'send-sms-notification-subscribe-end',
// { priority: 'high', concurrency: 1, },
// );
// agenda.define(
// 'send-mail-notification-subscribe-end',
// { priority: 'high', concurrency: 1, },
// );
// agenda.define(
// 'send-sms-notification-trial-end',
// { priority: 'high', concurrency: 1, },
// );
// agenda.define(
// 'send-mail-notification-trial-end',
// { priority: 'high', concurrency: 1, },
// );
agenda.start(); agenda.start();
}; };

View File

@@ -74,7 +74,7 @@ export default class Bill extends mixin(TenantModel, [CachableModel]) {
* @return {Array} * @return {Array}
*/ */
static async getNotFoundBills(billsIds, vendorId) { static async getNotFoundBills(billsIds, vendorId) {
const storedBills = await this.tenant().query() const storedBills = await this.query()
.onBuild((builder) => { .onBuild((builder) => {
builder.whereIn('id', billsIds); builder.whereIn('id', billsIds);
@@ -94,8 +94,7 @@ export default class Bill extends mixin(TenantModel, [CachableModel]) {
static changePaymentAmount(billId, amount) { static changePaymentAmount(billId, amount) {
const changeMethod = amount > 0 ? 'increment' : 'decrement'; const changeMethod = amount > 0 ? 'increment' : 'decrement';
return this.tenant() return this.query()
.query()
.where('id', billId) .where('id', billId)
[changeMethod]('payment_amount', Math.abs(amount)); [changeMethod]('payment_amount', Math.abs(amount));
} }

View File

@@ -33,10 +33,9 @@ export default class Customer extends TenantModel {
* @param {Numeric} amount * @param {Numeric} amount
*/ */
static async changeBalance(customerId, amount) { static async changeBalance(customerId, amount) {
const changeMethod = amount > 0 ? 'increment' : 'decrement'; const changeMethod = (amount > 0) ? 'increment' : 'decrement';
await this.tenant() return this.query()
.query()
.where('id', customerId) .where('id', customerId)
[changeMethod]('balance', Math.abs(amount)); [changeMethod]('balance', Math.abs(amount));
} }
@@ -47,8 +46,7 @@ export default class Customer extends TenantModel {
* @param {Integer} amount * @param {Integer} amount
*/ */
static async incrementBalance(customerId, amount) { static async incrementBalance(customerId, amount) {
await this.tenant() return this.query()
.query()
.where('id', customerId) .where('id', customerId)
.increment('balance', amount); .increment('balance', amount);
} }
@@ -59,9 +57,25 @@ export default class Customer extends TenantModel {
* @param {integer} amount - * @param {integer} amount -
*/ */
static async decrementBalance(customerId, amount) { static async decrementBalance(customerId, amount) {
await this.tenant() await this.query()
.query()
.where('id', customerId) .where('id', customerId)
.decrement('balance', amount); .decrement('balance', amount);
} }
static changeDiffBalance(customerId, oldCustomerId, amount, oldAmount) {
const diffAmount = amount - oldAmount;
const asyncOpers = [];
if (customerId != oldCustomerId) {
const oldCustomerOper = this.changeBalance(oldCustomerId, (oldAmount * -1));
const customerOper = this.changeBalance(customerId, amount);
asyncOpers.push(customerOper);
asyncOpers.push(oldCustomerOper);
} else {
const balanceChangeOper = this.changeBalance(customerId, diffAmount);
asyncOpers.push(balanceChangeOper);
}
return Promise.all(asyncOpers);
}
} }

View File

@@ -17,13 +17,12 @@ export default class ItemEntry extends TenantModel {
return ['created_at', 'updated_at']; return ['created_at', 'updated_at'];
} }
/** static get virtualAttributes() {
* Relationship mapping. return ['amount'];
*/ }
static get relationMappings() {
return {
}; static amount() {
return this.calcAmount(this);
} }
static calcAmount(itemEntry) { static calcAmount(itemEntry) {

View File

@@ -119,8 +119,7 @@ export default class SaleInvoice extends mixin(TenantModel, [CachableModel]) {
static async changePaymentAmount(invoiceId, amount) { static async changePaymentAmount(invoiceId, amount) {
const changeMethod = amount > 0 ? 'increment' : 'decrement'; const changeMethod = amount > 0 ? 'increment' : 'decrement';
await this.tenant() await this.query()
.query()
.where('id', invoiceId) .where('id', invoiceId)
[changeMethod]('payment_amount', Math.abs(amount)); [changeMethod]('payment_amount', Math.abs(amount));
} }

View File

@@ -25,8 +25,7 @@ export default class Vendor extends TenantModel {
static async changeBalance(vendorId, amount) { static async changeBalance(vendorId, amount) {
const changeMethod = amount > 0 ? 'increment' : 'decrement'; const changeMethod = amount > 0 ? 'increment' : 'decrement';
return this.tenant() return this.query()
.query()
.where('id', vendorId) .where('id', vendorId)
[changeMethod]('balance', Math.abs(amount)); [changeMethod]('balance', Math.abs(amount));
} }

View File

@@ -1,22 +0,0 @@
import { Account, AccountType } from '@/models';
export default class AccountsService {
static async isAccountExists(accountId) {
const foundAccounts = await Account.tenant().query().where('id', accountId);
return foundAccounts.length > 0;
}
static async getAccountByType(accountTypeKey) {
const accountType = await AccountType.tenant()
.query()
.where('key', accountTypeKey)
.first();
const account = await Account.tenant()
.query()
.where('account_type_id', accountType.id)
.first();
return account;
}
}

View File

@@ -0,0 +1,29 @@
import { Inject, Service } from 'typedi';
import TenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class AccountsService {
@Inject()
tenancy: TenancyService;
async isAccountExists(tenantId: number, accountId: number) {
const { Account } = this.tenancy.models(tenantId);
const foundAccounts = await Account.query()
.where('id', accountId);
return foundAccounts.length > 0;
}
async getAccountByType(tenantId: number, accountTypeKey: string) {
const { AccountType, Account } = this.tenancy.models(tenantId);
const accountType = await AccountType.query()
.where('key', accountTypeKey)
.first();
const account = await Account.query()
.where('account_type_id', accountType.id)
.first();
return account;
}
}

View File

@@ -4,7 +4,7 @@ import Customer from "../../models/Customer";
export default class CustomersService { export default class CustomersService {
static async isCustomerExists(customerId) { static async isCustomerExists(customerId) {
const foundCustomeres = await Customer.tenant().query().where('id', customerId); const foundCustomeres = await Customer.query().where('id', customerId);
return foundCustomeres.length > 0; return foundCustomeres.length > 0;
} }
} }

View File

@@ -1,23 +1,26 @@
import { Container } from 'typedi'; import { Container, Service, Inject } from 'typedi';
import {
InventoryTransaction,
Item,
Option,
} from '@/models';
import InventoryAverageCost from '@/services/Inventory/InventoryAverageCost'; import InventoryAverageCost from '@/services/Inventory/InventoryAverageCost';
import InventoryCostLotTracker from '@/services/Inventory/InventoryCostLotTracker'; import InventoryCostLotTracker from '@/services/Inventory/InventoryCostLotTracker';
import TenancyService from '@/services/Tenancy/TenancyService';
type TCostMethod = 'FIFO' | 'LIFO' | 'AVG'; type TCostMethod = 'FIFO' | 'LIFO' | 'AVG';
@Service()
export default class InventoryService { export default class InventoryService {
@Inject()
tenancy: TenancyService;
/** /**
* Computes the given item cost and records the inventory lots transactions * Computes the given item cost and records the inventory lots transactions
* and journal entries based on the cost method FIFO, LIFO or average cost rate. * and journal entries based on the cost method FIFO, LIFO or average cost rate.
* @param {number} tenantId -
* @param {Date} fromDate - * @param {Date} fromDate -
* @param {number} itemId - * @param {number} itemId -
*/ */
static async computeItemCost(fromDate: Date, itemId: number) { async computeItemCost(tenantId: number, fromDate: Date, itemId: number) {
const item = await Item.tenant().query() const { Item } = this.tenancy.models(tenantId);
const item = await Item.query()
.findById(itemId) .findById(itemId)
.withGraphFetched('category'); .withGraphFetched('category');
@@ -42,30 +45,34 @@ export default class InventoryService {
} }
/** /**
* SChedule item cost compute job. * Schedule item cost compute job.
* @param {number} tenantId
* @param {number} itemId * @param {number} itemId
* @param {Date} startingDate * @param {Date} startingDate
*/ */
static async scheduleComputeItemCost(itemId: number, startingDate: Date|string) { async scheduleComputeItemCost(tenantId: number, itemId: number, startingDate: Date|string) {
const agenda = Container.get('agenda'); const agenda = Container.get('agenda');
return agenda.schedule('in 3 seconds', 'compute-item-cost', { return agenda.schedule('in 3 seconds', 'compute-item-cost', {
startingDate, itemId, startingDate, itemId, tenantId,
}); });
} }
/** /**
* Records the inventory transactions. * Records the inventory transactions.
* @param {number} tenantId - Tenant id.
* @param {Bill} bill * @param {Bill} bill
* @param {number} billId * @param {number} billId
*/ */
static async recordInventoryTransactions( async recordInventoryTransactions(
tenantId: number,
entries: [], entries: [],
deleteOld: boolean, deleteOld: boolean,
) { ) {
const { InventoryTransaction, Item } = this.tenancy.models(tenantId);
const entriesItemsIds = entries.map((e: any) => e.item_id); const entriesItemsIds = entries.map((e: any) => e.item_id);
const inventoryItems = await Item.tenant() const inventoryItems = await Item.query()
.query()
.whereIn('id', entriesItemsIds) .whereIn('id', entriesItemsIds)
.where('type', 'inventory'); .where('type', 'inventory');
@@ -78,11 +85,12 @@ export default class InventoryService {
inventoryEntries.forEach(async (entry: any) => { inventoryEntries.forEach(async (entry: any) => {
if (deleteOld) { if (deleteOld) {
await this.deleteInventoryTransactions( await this.deleteInventoryTransactions(
tenantId,
entry.transactionId, entry.transactionId,
entry.transactionType, entry.transactionType,
); );
} }
await InventoryTransaction.tenant().query().insert({ await InventoryTransaction.query().insert({
...entry, ...entry,
lotNumber: entry.lotNumber, lotNumber: entry.lotNumber,
}); });
@@ -91,15 +99,19 @@ export default class InventoryService {
/** /**
* Deletes the given inventory transactions. * Deletes the given inventory transactions.
* @param {number} tenantId - Tenant id.
* @param {string} transactionType * @param {string} transactionType
* @param {number} transactionId * @param {number} transactionId
* @return {Promise} * @return {Promise}
*/ */
static deleteInventoryTransactions( deleteInventoryTransactions(
tenantId: number,
transactionId: number, transactionId: number,
transactionType: string, transactionType: string,
) { ) {
return InventoryTransaction.tenant().query() const { InventoryTransaction } = this.tenancy.models(tenantId);
return InventoryTransaction.query()
.where('transaction_type', transactionType) .where('transaction_type', transactionType)
.where('transaction_id', transactionId) .where('transaction_id', transactionId)
.delete(); .delete();
@@ -107,21 +119,24 @@ export default class InventoryService {
/** /**
* Retrieve the lot number after the increment. * Retrieve the lot number after the increment.
* @param {number} tenantId - Tenant id.
*/ */
static async nextLotNumber() { async nextLotNumber(tenantId: number) {
const { Option } = this.tenancy.models(tenantId);
const LOT_NUMBER_KEY = 'lot_number_increment'; const LOT_NUMBER_KEY = 'lot_number_increment';
const effectRows = await Option.tenant().query() const effectRows = await Option.query()
.where('key', LOT_NUMBER_KEY) .where('key', LOT_NUMBER_KEY)
.increment('value', 1); .increment('value', 1);
if (effectRows === 0) { if (effectRows === 0) {
await Option.tenant().query() await Option.query()
.insert({ .insert({
key: LOT_NUMBER_KEY, key: LOT_NUMBER_KEY,
value: 1, value: 1,
}); });
} }
const options = await Option.tenant().query(); const options = await Option.query();
return options.getMeta(LOT_NUMBER_KEY, 1); return options.getMeta(LOT_NUMBER_KEY, 1);
} }
} }

View File

@@ -1,5 +1,4 @@
import { pick } from 'lodash'; import { pick } from 'lodash';
import { InventoryTransaction } from '@/models';
import { IInventoryTransaction } from '@/interfaces'; import { IInventoryTransaction } from '@/interfaces';
import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod'; import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod';
@@ -10,10 +9,12 @@ export default class InventoryAverageCostMethod extends InventoryCostMethod impl
/** /**
* Constructor method. * Constructor method.
* @param {number} tenantId - The given tenant id.
* @param {Date} startingDate - * @param {Date} startingDate -
* @param {number} itemId - * @param {number} itemId - The given inventory item id.
*/ */
constructor( constructor(
tenantId: number,
startingDate: Date, startingDate: Date,
itemId: number, itemId: number,
) { ) {
@@ -39,11 +40,10 @@ export default class InventoryAverageCostMethod extends InventoryCostMethod impl
* @param {string} referenceType * @param {string} referenceType
*/ */
public async computeItemCost() { public async computeItemCost() {
const { InventoryTransaction } = this.tenantModels;
const openingAvgCost = await this.getOpeningAvaregeCost(this.startingDate, this.itemId); const openingAvgCost = await this.getOpeningAvaregeCost(this.startingDate, this.itemId);
const afterInvTransactions: IInventoryTransaction[] = await InventoryTransaction const afterInvTransactions: IInventoryTransaction[] = await InventoryTransaction.query()
.tenant()
.query()
.modify('filterDateRange', this.startingDate) .modify('filterDateRange', this.startingDate)
.orderBy('date', 'ASC') .orderBy('date', 'ASC')
.orderByRaw("FIELD(direction, 'IN', 'OUT')") .orderByRaw("FIELD(direction, 'IN', 'OUT')")
@@ -66,6 +66,7 @@ export default class InventoryAverageCostMethod extends InventoryCostMethod impl
* @return {number} * @return {number}
*/ */
public async getOpeningAvaregeCost(startingDate: Date, itemId: number) { public async getOpeningAvaregeCost(startingDate: Date, itemId: number) {
const { InventoryTransaction } = this.tenantModels;
const commonBuilder = (builder: any) => { const commonBuilder = (builder: any) => {
if (startingDate) { if (startingDate) {
builder.where('date', '<', startingDate); builder.where('date', '<', startingDate);
@@ -81,16 +82,14 @@ export default class InventoryAverageCostMethod extends InventoryCostMethod impl
// Calculates the total inventory total quantity and rate `IN` transactions. // Calculates the total inventory total quantity and rate `IN` transactions.
// @todo total `IN` transactions. // @todo total `IN` transactions.
const inInvSumationOper: Promise<any> = InventoryTransaction.tenant() const inInvSumationOper: Promise<any> = InventoryTransaction.query()
.query()
.onBuild(commonBuilder) .onBuild(commonBuilder)
.where('direction', 'IN') .where('direction', 'IN')
.first(); .first();
// Calculates the total inventory total quantity and rate `OUT` transactions. // Calculates the total inventory total quantity and rate `OUT` transactions.
// @todo total `OUT` transactions. // @todo total `OUT` transactions.
const outInvSumationOper: Promise<any> = InventoryTransaction.tenant() const outInvSumationOper: Promise<any> = InventoryTransaction.query()
.query()
.onBuild(commonBuilder) .onBuild(commonBuilder)
.where('direction', 'OUT') .where('direction', 'OUT')
.first(); .first();

View File

@@ -1,10 +1,5 @@
import { omit, pick, chain } from 'lodash'; import { pick, chain } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import {
InventoryTransaction,
InventoryLotCostTracker,
Item,
} from "@/models";
import { IInventoryLotCost, IInventoryTransaction } from "@/interfaces"; import { IInventoryLotCost, IInventoryTransaction } from "@/interfaces";
import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod'; import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod';
@@ -28,7 +23,12 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
* @param {number} itemId - * @param {number} itemId -
* @param {string} costMethod - * @param {string} costMethod -
*/ */
constructor(startingDate: Date, itemId: number, costMethod: TCostMethod = 'FIFO') { constructor(
tenantId: number,
startingDate: Date,
itemId: number,
costMethod: TCostMethod = 'FIFO'
) {
super(); super();
this.startingDate = startingDate; this.startingDate = startingDate;
@@ -83,13 +83,14 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
* @private * @private
*/ */
private async fetchInvINTransactions() { private async fetchInvINTransactions() {
const { InventoryTransaction, InventoryLotCostTracker } = this.tenantModels;
const commonBuilder = (builder: any) => { const commonBuilder = (builder: any) => {
builder.orderBy('date', (this.costMethod === 'LIFO') ? 'DESC': 'ASC'); builder.orderBy('date', (this.costMethod === 'LIFO') ? 'DESC': 'ASC');
builder.where('item_id', this.itemId); builder.where('item_id', this.itemId);
}; };
const afterInvTransactions: IInventoryTransaction[] = const afterInvTransactions: IInventoryTransaction[] =
await InventoryTransaction.tenant() await InventoryTransaction.query()
.query()
.modify('filterDateRange', this.startingDate) .modify('filterDateRange', this.startingDate)
.orderByRaw("FIELD(direction, 'IN', 'OUT')") .orderByRaw("FIELD(direction, 'IN', 'OUT')")
.onBuild(commonBuilder) .onBuild(commonBuilder)
@@ -97,8 +98,7 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
.withGraphFetched('item'); .withGraphFetched('item');
const availiableINLots: IInventoryLotCost[] = const availiableINLots: IInventoryLotCost[] =
await InventoryLotCostTracker.tenant() await InventoryLotCostTracker.query()
.query()
.modify('filterDateRange', null, this.startingDate) .modify('filterDateRange', null, this.startingDate)
.orderBy('date', 'ASC') .orderBy('date', 'ASC')
.where('direction', 'IN') .where('direction', 'IN')
@@ -117,9 +117,10 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
* @private * @private
*/ */
private async fetchInvOUTTransactions() { private async fetchInvOUTTransactions() {
const { InventoryTransaction } = this.tenantModels;
const afterOUTTransactions: IInventoryTransaction[] = const afterOUTTransactions: IInventoryTransaction[] =
await InventoryTransaction.tenant() await InventoryTransaction.query()
.query()
.modify('filterDateRange', this.startingDate) .modify('filterDateRange', this.startingDate)
.orderBy('date', 'ASC') .orderBy('date', 'ASC')
.orderBy('lot_number', 'ASC') .orderBy('lot_number', 'ASC')
@@ -132,8 +133,8 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
private async fetchItemsMapped() { private async fetchItemsMapped() {
const itemsIds = chain(this.inTransactions).map((e) => e.itemId).uniq().value(); const itemsIds = chain(this.inTransactions).map((e) => e.itemId).uniq().value();
const storedItems = await Item.tenant() const { Item } = this.tenantModels;
.query() const storedItems = await Item.query()
.where('type', 'inventory') .where('type', 'inventory')
.whereIn('id', itemsIds); .whereIn('id', itemsIds);
@@ -145,9 +146,9 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
* @private * @private
*/ */
private async fetchRevertInvJReferenceIds() { private async fetchRevertInvJReferenceIds() {
const { InventoryTransaction } = this.tenantModels;
const revertJEntriesTransactions: IInventoryTransaction[] = const revertJEntriesTransactions: IInventoryTransaction[] =
await InventoryTransaction.tenant() await InventoryTransaction.query()
.query()
.select(['transactionId', 'transactionType']) .select(['transactionId', 'transactionType'])
.modify('filterDateRange', this.startingDate) .modify('filterDateRange', this.startingDate)
.where('direction', 'OUT') .where('direction', 'OUT')
@@ -164,16 +165,15 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
* @return {Promise} * @return {Promise}
*/ */
public async revertInventoryLots(startingDate: Date) { public async revertInventoryLots(startingDate: Date) {
const { InventoryLotCostTracker } = this.tenantModels;
const asyncOpers: any[] = []; const asyncOpers: any[] = [];
const inventoryLotsTrans = await InventoryLotCostTracker.tenant() const inventoryLotsTrans = await InventoryLotCostTracker.query()
.query()
.modify('filterDateRange', this.startingDate) .modify('filterDateRange', this.startingDate)
.orderBy('date', 'DESC') .orderBy('date', 'DESC')
.where('item_id', this.itemId) .where('item_id', this.itemId)
.where('direction', 'OUT'); .where('direction', 'OUT');
const deleteInvLotsTrans = InventoryLotCostTracker.tenant() const deleteInvLotsTrans = InventoryLotCostTracker.query()
.query()
.modify('filterDateRange', this.startingDate) .modify('filterDateRange', this.startingDate)
.where('item_id', this.itemId) .where('item_id', this.itemId)
.delete(); .delete();
@@ -181,8 +181,7 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
inventoryLotsTrans.forEach((inventoryLot: IInventoryLotCost) => { inventoryLotsTrans.forEach((inventoryLot: IInventoryLotCost) => {
if (!inventoryLot.lotNumber) { return; } if (!inventoryLot.lotNumber) { return; }
const incrementOper = InventoryLotCostTracker.tenant() const incrementOper = InventoryLotCostTracker.query()
.query()
.where('lot_number', inventoryLot.lotNumber) .where('lot_number', inventoryLot.lotNumber)
.where('direction', 'IN') .where('direction', 'IN')
.increment('remaining', inventoryLot.quantity); .increment('remaining', inventoryLot.quantity);

View File

@@ -1,28 +1,42 @@
import { omit } from 'lodash'; import { omit } from 'lodash';
import { Inject } from 'typedi';
import TenancyService from '@/services/Tenancy/TenancyService';
import { IInventoryLotCost } from '@/interfaces'; import { IInventoryLotCost } from '@/interfaces';
import { InventoryLotCostTracker } from '@/models';
export default class InventoryCostMethod { export default class InventoryCostMethod {
@Inject()
tenancy: TenancyService;
tenantModels: any;
/**
* Constructor method.
* @param {number} tenantId - The given tenant id.
*/
constructor(tenantId: number, startingDate: Date, itemId: number) {
this.tenantModels = this.tenantModels.models(tenantId);
}
/** /**
* Stores the inventory lots costs transactions in bulk. * Stores the inventory lots costs transactions in bulk.
* @param {IInventoryLotCost[]} costLotsTransactions * @param {IInventoryLotCost[]} costLotsTransactions
* @return {Promise[]} * @return {Promise[]}
*/ */
public storeInventoryLotsCost(costLotsTransactions: IInventoryLotCost[]): Promise<object> { public storeInventoryLotsCost(costLotsTransactions: IInventoryLotCost[]): Promise<object> {
const { InventoryLotCostTracker } = this.tenantModels;
const opers: any = []; const opers: any = [];
costLotsTransactions.forEach((transaction: IInventoryLotCost) => { costLotsTransactions.forEach((transaction: IInventoryLotCost) => {
if (transaction.lotTransId && transaction.decrement) { if (transaction.lotTransId && transaction.decrement) {
const decrementOper = InventoryLotCostTracker.tenant() const decrementOper = InventoryLotCostTracker.query()
.query()
.where('id', transaction.lotTransId) .where('id', transaction.lotTransId)
.decrement('remaining', transaction.decrement); .decrement('remaining', transaction.decrement);
opers.push(decrementOper); opers.push(decrementOper);
} else if(!transaction.lotTransId) { } else if(!transaction.lotTransId) {
const operation = InventoryLotCostTracker.tenant().query() const operation = InventoryLotCostTracker.query()
.insert({ .insert({
...omit(transaction, ['decrement', 'invTransId', 'lotTransId']), ...omit(transaction, ['decrement', 'invTransId', 'lotTransId']),
}); });
opers.push(operation); opers.push(operation);
} }
}); });

View File

@@ -1,59 +0,0 @@
import { difference } from "lodash";
import { Item, ItemTransaction } from '@/models';
export default class ItemsService {
static async newItem(item) {
const storedItem = await Item.tenant()
.query()
.insertAndFetch({
...item,
});
return storedItem;
}
static async editItem(item, itemId) {
const updateItem = await Item.tenant()
.query()
.findById(itemId)
.patch({
...item,
});
return updateItem;
}
static async deleteItem(itemId) {
return Item.tenant()
.query()
.findById(itemId)
.delete();
}
static async getItemWithMetadata(itemId) {
return Item.tenant()
.query()
.findById(itemId)
.withGraphFetched(
'costAccount',
'sellAccount',
'inventoryAccount',
'category'
);
}
/**
* Validates the given items IDs exists or not returns the not found ones.
* @param {Array} itemsIDs
* @return {Array}
*/
static async isItemsIdsExists(itemsIDs) {
const storedItems = await Item.tenant().query().whereIn('id', itemsIDs);
const storedItemsIds = storedItems.map((t) => t.id);
const notFoundItemsIds = difference(
itemsIDs,
storedItemsIds,
);
return notFoundItemsIds;
}
}

View File

@@ -0,0 +1,70 @@
import { difference } from "lodash";
import { Service, Inject } from "typedi";
import TenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class ItemsService {
@Inject()
tenancy: TenancyService;
async newItem(tenantId: number, item: any) {
const { Item } = this.tenancy.models(tenantId);
const storedItem = await Item.query()
.insertAndFetch({
...item,
});
return storedItem;
}
async editItem(tenantId: number, item: any, itemId: number) {
const { Item } = this.tenancy.models(tenantId);
const updateItem = await Item.query()
.findById(itemId)
.patch({
...item,
});
return updateItem;
}
async deleteItem(tenantId: number, itemId: number) {
const { Item } = this.tenancy.models(tenantId);
return Item.query()
.findById(itemId)
.delete();
}
/**
* Retrieve the item details of the given id with associated details.
* @param {number} tenantId
* @param {number} itemId
*/
async getItemWithMetadata(tenantId: number, itemId: number) {
const { Item } = this.tenancy.models(tenantId);
return Item.query()
.findById(itemId)
.withGraphFetched(
'costAccount',
'sellAccount',
'inventoryAccount',
'category'
);
}
/**
* Validates the given items IDs exists or not returns the not found ones.
* @param {Array} itemsIDs
* @return {Array}
*/
async isItemsIdsExists(tenantId: number, itemsIDs: number[]) {
const { Item } = this.tenancy.models(tenantId);
const storedItems = await Item.query().whereIn('id', itemsIDs);
const storedItemsIds = storedItems.map((t) => t.id);
const notFoundItemsIds = difference(
itemsIDs,
storedItemsIds,
);
return notFoundItemsIds;
}
}

View File

@@ -1,5 +1,6 @@
import { Service, Container, Inject } from 'typedi'; import { Service, Container, Inject } from 'typedi';
import cryptoRandomString from 'crypto-random-string'; import cryptoRandomString from 'crypto-random-string';
import { times } from 'lodash';
import { Voucher } from "@/system/models"; import { Voucher } from "@/system/models";
import { IVoucher } from '@/interfaces'; import { IVoucher } from '@/interfaces';
import VoucherMailMessages from '@/services/Payment/VoucherMailMessages'; import VoucherMailMessages from '@/services/Payment/VoucherMailMessages';
@@ -26,6 +27,8 @@ export default class VoucherService {
let voucherCode: string; let voucherCode: string;
let repeat: boolean = true; let repeat: boolean = true;
console.log(Voucher);
while(repeat) { while(repeat) {
voucherCode = cryptoRandomString({ length: 10, type: 'numeric' }); voucherCode = cryptoRandomString({ length: 10, type: 'numeric' });
const foundVouchers = await Voucher.query().where('voucher_code', voucherCode); const foundVouchers = await Voucher.query().where('voucher_code', voucherCode);
@@ -39,6 +42,29 @@ export default class VoucherService {
}); });
} }
/**
*
* @param {number} loop
* @param {number} voucherPeriod
* @param {string} periodInterval
* @param {number} planId
*/
async generateVouchers(
loop = 1,
voucherPeriod: numner,
periodInterval: string = 'days',
planId: number,
) {
const asyncOpers: Promise<any>[] = [];
times(loop, () => {
const generateOper = this.generateVoucher(voucherPeriod, periodInterval, planId);
asyncOpers.push(generateOper);
});
return Promise.all(asyncOpers);
}
/** /**
* Disables the given voucher id on the storage. * Disables the given voucher id on the storage.
* @param {number} voucherId * @param {number} voucherId

View File

@@ -1,26 +1,30 @@
import express from 'express'; import { Inject, Service } from 'typedi';
import { omit, sumBy } from 'lodash'; import { omit, sumBy } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { import { IBillPaymentOTD, IBillPayment } from '@/interfaces';
BillPayment,
BillPaymentEntry,
Vendor,
Bill,
Account,
AccountTransaction,
} from '@/models';
import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries'; import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries';
import AccountsService from '@/services/Accounts/AccountsService'; import AccountsService from '@/services/Accounts/AccountsService';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from '@/services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry'; import JournalEntry from '@/services/Accounting/JournalEntry';
import JournalPosterService from '@/services/Sales/JournalPosterService'; import JournalPosterService from '@/services/Sales/JournalPosterService';
import TenancyService from '@/services/Tenancy/TenancyService';
import { formatDateFields } from '@/utils'; import { formatDateFields } from '@/utils';
/** /**
* Bill payments service. * Bill payments service.
* @service * @service
*/ */
@Service()
export default class BillPaymentsService { export default class BillPaymentsService {
@Inject()
accountsService: AccountsService;
@Inject()
tenancy: TenancyService;
@Inject()
journalService: JournalPosterService;
/** /**
* Creates a new bill payment transcations and store it to the storage * Creates a new bill payment transcations and store it to the storage
* with associated bills entries and journal transactions. * with associated bills entries and journal transactions.
@@ -32,24 +36,24 @@ export default class BillPaymentsService {
* - Increment the payment amount of the given vendor bills. * - Increment the payment amount of the given vendor bills.
* - Decrement the vendor balance. * - Decrement the vendor balance.
* - Records payment journal entries. * - Records payment journal entries.
* * @param {number} tenantId - Tenant id.
* @param {BillPaymentDTO} billPayment * @param {BillPaymentDTO} billPayment - Bill payment object.
*/ */
static async createBillPayment(billPaymentDTO) { async createBillPayment(tenantId: number, billPaymentDTO: IBillPaymentOTD) {
const { Bill, BillPayment, BillPaymentEntry, Vendor } = this.tenancy.models(tenantId);
const billPayment = { const billPayment = {
amount: sumBy(billPaymentDTO.entries, 'payment_amount'), amount: sumBy(billPaymentDTO.entries, 'payment_amount'),
...formatDateFields(billPaymentDTO, ['payment_date']), ...formatDateFields(billPaymentDTO, ['payment_date']),
} }
const storedBillPayment = await BillPayment.tenant() const storedBillPayment = await BillPayment.query()
.query()
.insert({ .insert({
...omit(billPayment, ['entries']), ...omit(billPayment, ['entries']),
}); });
const storeOpers = []; const storeOpers: Promise<any>[] = [];
billPayment.entries.forEach((entry) => { billPayment.entries.forEach((entry) => {
const oper = BillPaymentEntry.tenant() const oper = BillPaymentEntry.query()
.query()
.insert({ .insert({
bill_payment_id: storedBillPayment.id, bill_payment_id: storedBillPayment.id,
...entry, ...entry,
@@ -69,7 +73,7 @@ export default class BillPaymentsService {
); );
// Records the journal transactions after bills payment // Records the journal transactions after bills payment
// and change diff acoount balance. // and change diff acoount balance.
const recordJournalTransaction = this.recordPaymentReceiveJournalEntries({ const recordJournalTransaction = this.recordPaymentReceiveJournalEntries(tenantId, {
id: storedBillPayment.id, id: storedBillPayment.id,
...billPayment, ...billPayment,
}); });
@@ -93,18 +97,23 @@ export default class BillPaymentsService {
* - Re-insert the journal transactions and update the diff accounts balance. * - Re-insert the journal transactions and update the diff accounts balance.
* - Update the diff vendor balance. * - Update the diff vendor balance.
* - Update the diff bill payment amount. * - Update the diff bill payment amount.
* * @param {number} tenantId - Tenant id
* @param {Integer} billPaymentId * @param {Integer} billPaymentId
* @param {BillPaymentDTO} billPayment * @param {BillPaymentDTO} billPayment
* @param {IBillPayment} oldBillPayment * @param {IBillPayment} oldBillPayment
*/ */
static async editBillPayment(billPaymentId, billPaymentDTO, oldBillPayment) { async editBillPayment(
tenantId: number,
billPaymentId: number,
billPaymentDTO,
oldBillPayment,
) {
const { BillPayment, BillPaymentEntry, Vendor } = this.tenancy.models(tenantId);
const billPayment = { const billPayment = {
amount: sumBy(billPaymentDTO.entries, 'payment_amount'), amount: sumBy(billPaymentDTO.entries, 'payment_amount'),
...formatDateFields(billPaymentDTO, ['payment_date']), ...formatDateFields(billPaymentDTO, ['payment_date']),
}; };
const updateBillPayment = await BillPayment.tenant() const updateBillPayment = await BillPayment.query()
.query()
.where('id', billPaymentId) .where('id', billPaymentId)
.update({ .update({
...omit(billPayment, ['entries']), ...omit(billPayment, ['entries']),
@@ -118,22 +127,19 @@ export default class BillPaymentsService {
entriesHasIds entriesHasIds
); );
if (entriesIdsShouldDelete.length > 0) { if (entriesIdsShouldDelete.length > 0) {
const deleteOper = BillPaymentEntry.tenant() const deleteOper = BillPaymentEntry.query()
.query()
.bulkDelete(entriesIdsShouldDelete); .bulkDelete(entriesIdsShouldDelete);
opers.push(deleteOper); opers.push(deleteOper);
} }
// Entries that should be update to the storage. // Entries that should be update to the storage.
if (entriesHasIds.length > 0) { if (entriesHasIds.length > 0) {
const updateOper = BillPaymentEntry.tenant() const updateOper = BillPaymentEntry.query()
.query()
.bulkUpdate(entriesHasIds, { where: 'id' }); .bulkUpdate(entriesHasIds, { where: 'id' });
opers.push(updateOper); opers.push(updateOper);
} }
// Entries that should be inserted to the storage. // Entries that should be inserted to the storage.
if (entriesHasNoIds.length > 0) { if (entriesHasNoIds.length > 0) {
const insertOper = BillPaymentEntry.tenant() const insertOper = BillPaymentEntry.query()
.query()
.bulkInsert( .bulkInsert(
entriesHasNoIds.map((e) => ({ ...e, bill_payment_id: billPaymentId })) entriesHasNoIds.map((e) => ({ ...e, bill_payment_id: billPaymentId }))
); );
@@ -141,7 +147,7 @@ export default class BillPaymentsService {
} }
// Records the journal transactions after bills payment and change // Records the journal transactions after bills payment and change
// different acoount balance. // different acoount balance.
const recordJournalTransaction = this.recordPaymentReceiveJournalEntries({ const recordJournalTransaction = this.recordPaymentReceiveJournalEntries(tenantId, {
id: storedBillPayment.id, id: storedBillPayment.id,
...billPayment, ...billPayment,
}); });
@@ -161,18 +167,19 @@ export default class BillPaymentsService {
/** /**
* Deletes the bill payment and associated transactions. * Deletes the bill payment and associated transactions.
* @param {number} tenantId - Tenant id.
* @param {Integer} billPaymentId - The given bill payment id. * @param {Integer} billPaymentId - The given bill payment id.
* @return {Promise} * @return {Promise}
*/ */
static async deleteBillPayment(billPaymentId) { async deleteBillPayment(tenantId: number, billPaymentId: number) {
const billPayment = await BillPayment.tenant().query().where('id', billPaymentId).first(); const { BillPayment, BillPaymentEntry, Vendor } = this.tenancy.models(tenantId);
const billPayment = await BillPayment.query().where('id', billPaymentId).first();
await BillPayment.tenant().query() await BillPayment.query()
.where('id', billPaymentId) .where('id', billPaymentId)
.delete(); .delete();
await BillPaymentEntry.tenant() await BillPaymentEntry.query()
.query()
.where('bill_payment_id', billPaymentId) .where('bill_payment_id', billPaymentId)
.delete(); .delete();
@@ -192,17 +199,21 @@ export default class BillPaymentsService {
/** /**
* Records bill payment receive journal transactions. * Records bill payment receive journal transactions.
* @param {number} tenantId -
* @param {BillPayment} billPayment * @param {BillPayment} billPayment
* @param {Integer} billPaymentId * @param {Integer} billPaymentId
*/ */
static async recordPaymentReceiveJournalEntries(billPayment) { async recordPaymentReceiveJournalEntries(tenantId: number, billPayment) {
const { AccountTransaction, Account } = this.tenancy.models(tenantId);
const paymentAmount = sumBy(billPayment.entries, 'payment_amount'); const paymentAmount = sumBy(billPayment.entries, 'payment_amount');
const formattedDate = moment(billPayment.payment_date).format('YYYY-MM-DD'); const formattedDate = moment(billPayment.payment_date).format('YYYY-MM-DD');
const payableAccount = await AccountsService.getAccountByType( const payableAccount = await this.accountsService.getAccountByType(
tenantId,
'accounts_payable' 'accounts_payable'
); );
const accountsDepGraph = await Account.tenant().depGraph().query(); const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph); const journal = new JournalPoster(accountsDepGraph);
const commonJournal = { const commonJournal = {
@@ -213,8 +224,7 @@ export default class BillPaymentsService {
date: formattedDate, date: formattedDate,
}; };
if (billPayment.id) { if (billPayment.id) {
const transactions = await AccountTransaction.tenant() const transactions = await AccountTransaction.query()
.query()
.whereIn('reference_type', ['BillPayment']) .whereIn('reference_type', ['BillPayment'])
.where('reference_id', billPayment.id) .where('reference_id', billPayment.id)
.withGraphFetched('account.type'); .withGraphFetched('account.type');
@@ -246,11 +256,12 @@ export default class BillPaymentsService {
/** /**
* Retrieve bill payment with associated metadata. * Retrieve bill payment with associated metadata.
* @param {number} billPaymentId * @param {number} billPaymentId - The bill payment id.
* @return {object} * @return {object}
*/ */
static async getBillPaymentWithMetadata(billPaymentId) { async getBillPaymentWithMetadata(tenantId: number, billPaymentId: number) {
const billPayment = await BillPayment.tenant().query() const { BillPayment } = this.tenancy.models(tenantId);
const billPayment = await BillPayment.query()
.where('id', billPaymentId) .where('id', billPaymentId)
.withGraphFetched('entries') .withGraphFetched('entries')
.withGraphFetched('vendor') .withGraphFetched('vendor')
@@ -265,8 +276,9 @@ export default class BillPaymentsService {
* @param {Integer} billPaymentId * @param {Integer} billPaymentId
* @return {boolean} * @return {boolean}
*/ */
static async isBillPaymentExists(billPaymentId) { async isBillPaymentExists(tenantId: number, billPaymentId: number) {
const billPayment = await BillPayment.tenant().query() const { BillPayment } = this.tenancy.models(tenantId);
const billPayment = await BillPayment.query()
.where('id', billPaymentId) .where('id', billPaymentId)
.first(); .first();

View File

@@ -1,13 +1,6 @@
import { omit, sumBy, pick } from 'lodash'; import { omit, sumBy, pick } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { import { Inject, Service } from 'typedi';
Account,
Bill,
Vendor,
ItemEntry,
Item,
AccountTransaction,
} from '@/models';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from '@/services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry'; import JournalEntry from '@/services/Accounting/JournalEntry';
import AccountsService from '@/services/Accounts/AccountsService'; import AccountsService from '@/services/Accounts/AccountsService';
@@ -15,13 +8,25 @@ import JournalPosterService from '@/services/Sales/JournalPosterService';
import InventoryService from '@/services/Inventory/Inventory'; import InventoryService from '@/services/Inventory/Inventory';
import HasItemsEntries from '@/services/Sales/HasItemsEntries'; import HasItemsEntries from '@/services/Sales/HasItemsEntries';
import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost'; import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost';
import TenancyService from '@/services/Tenancy/TenancyService';
import { formatDateFields } from '@/utils'; import { formatDateFields } from '@/utils';
import{ IBillOTD } from '@/interfaces';
/** /**
* Vendor bills services. * Vendor bills services.
* @service * @service
*/ */
@Service()
export default class BillsService extends SalesInvoicesCost { export default class BillsService extends SalesInvoicesCost {
@Inject()
inventoryService: InventoryService;
@Inject()
accountsService: AccountsService;
@Inject()
tenancy: TenancyService;
/** /**
* Creates a new bill and stored it to the storage. * Creates a new bill and stored it to the storage.
* *
@@ -33,26 +38,29 @@ export default class BillsService extends SalesInvoicesCost {
* - Record bill journal transactions on the given accounts. * - Record bill journal transactions on the given accounts.
* - Record bill items inventory transactions. * - Record bill items inventory transactions.
* ---- * ----
* @param {IBill} bill - * @param {number} tenantId - The given tenant id.
* @param {IBillOTD} billDTO -
* @return {void} * @return {void}
*/ */
static async createBill(billDTO) { async createBill(tenantId: number, billDTO: IBillOTD) {
const invLotNumber = await InventoryService.nextLotNumber(); const { Vendor, Bill, ItemEntry } = this.tenancy.models(tenantId);
const invLotNumber = await this.inventoryService.nextLotNumber(tenantId);
const amount = sumBy(billDTO.entries, e => ItemEntry.calcAmount(e));
const bill = { const bill = {
...formatDateFields(billDTO, ['bill_date', 'due_date']), ...formatDateFields(billDTO, ['bill_date', 'due_date']),
amount: sumBy(billDTO.entries, 'amount'), amount,
invLotNumber: billDTO.invLotNumber || invLotNumber invLotNumber: billDTO.invLotNumber || invLotNumber
}; };
const saveEntriesOpers = []; const saveEntriesOpers = [];
const storedBill = await Bill.tenant() const storedBill = await Bill.query()
.query()
.insert({ .insert({
...omit(bill, ['entries']), ...omit(bill, ['entries']),
}); });
bill.entries.forEach((entry) => { bill.entries.forEach((entry) => {
const oper = ItemEntry.tenant() const oper = ItemEntry.query()
.query()
.insertAndFetch({ .insertAndFetch({
reference_type: 'Bill', reference_type: 'Bill',
reference_id: storedBill.id, reference_id: storedBill.id,
@@ -70,10 +78,10 @@ export default class BillsService extends SalesInvoicesCost {
// Rewrite the inventory transactions for inventory items. // Rewrite the inventory transactions for inventory items.
const writeInvTransactionsOper = this.recordInventoryTransactions( const writeInvTransactionsOper = this.recordInventoryTransactions(
bill, storedBill.id tenantId, bill, storedBill.id
); );
// Writes the journal entries for the given bill transaction. // Writes the journal entries for the given bill transaction.
const writeJEntriesOper = this.recordJournalTransactions({ const writeJEntriesOper = this.recordJournalTransactions(tenantId, {
id: storedBill.id, ...bill, id: storedBill.id, ...bill,
}); });
await Promise.all([ await Promise.all([
@@ -83,7 +91,7 @@ export default class BillsService extends SalesInvoicesCost {
]); ]);
// Schedule bill re-compute based on the item cost // Schedule bill re-compute based on the item cost
// method and starting date. // method and starting date.
await this.scheduleComputeBillItemsCost(bill); await this.scheduleComputeBillItemsCost(tenantId, bill);
return storedBill; return storedBill;
} }
@@ -100,26 +108,29 @@ export default class BillsService extends SalesInvoicesCost {
* - Re-write the inventory transactions. * - Re-write the inventory transactions.
* - Re-write the bill journal transactions. * - Re-write the bill journal transactions.
* *
* @param {number} tenantId - The given tenant id.
* @param {Integer} billId - The given bill id. * @param {Integer} billId - The given bill id.
* @param {IBill} bill - The given new bill details. * @param {billDTO} billDTO - The given new bill details.
*/ */
static async editBill(billId, billDTO) { async editBill(tenantId: number, billId: number, billDTO: billDTO) {
const oldBill = await Bill.tenant().query().findById(billId); const { Bill, ItemEntry, Vendor } = this.tenancy.models(tenantId);
const oldBill = await Bill.query().findById(billId);
const amount = sumBy(billDTO.entries, e => ItemEntry.calcAmount(e));
const bill = { const bill = {
...formatDateFields(billDTO, ['bill_date', 'due_date']), ...formatDateFields(billDTO, ['bill_date', 'due_date']),
amount: sumBy(billDTO.entries, 'amount'), amount,
invLotNumber: oldBill.invLotNumber, invLotNumber: oldBill.invLotNumber,
}; };
// Update the bill transaction. // Update the bill transaction.
const updatedBill = await Bill.tenant() const updatedBill = await Bill.query()
.query()
.where('id', billId) .where('id', billId)
.update({ .update({
...omit(bill, ['entries', 'invLotNumber']) ...omit(bill, ['entries', 'invLotNumber'])
}); });
// Old stored entries. // Old stored entries.
const storedEntries = await ItemEntry.tenant() const storedEntries = await ItemEntry.query()
.query()
.where('reference_id', billId) .where('reference_id', billId)
.where('reference_type', 'Bill'); .where('reference_type', 'Bill');
@@ -135,10 +146,11 @@ export default class BillsService extends SalesInvoicesCost {
oldBill.amount, oldBill.amount,
); );
// Re-write the inventory transactions for inventory items. // Re-write the inventory transactions for inventory items.
const writeInvTransactionsOper = this.recordInventoryTransactions(bill, billId, true); const writeInvTransactionsOper = this.recordInventoryTransactions(
tenantId, bill, billId, true
);
// Writes the journal entries for the given bill transaction. // Writes the journal entries for the given bill transaction.
const writeJEntriesOper = this.recordJournalTransactions({ const writeJEntriesOper = this.recordJournalTransactions(tenantId, {
id: billId, id: billId,
...bill, ...bill,
}, billId); }, billId);
@@ -151,7 +163,7 @@ export default class BillsService extends SalesInvoicesCost {
]); ]);
// Schedule sale invoice re-compute based on the item cost // Schedule sale invoice re-compute based on the item cost
// method and starting date. // method and starting date.
await this.scheduleComputeBillItemsCost(bill); await this.scheduleComputeBillItemsCost(tenantId, bill);
} }
/** /**
@@ -159,21 +171,22 @@ export default class BillsService extends SalesInvoicesCost {
* @param {Integer} billId * @param {Integer} billId
* @return {void} * @return {void}
*/ */
static async deleteBill(billId) { async deleteBill(tenantId: number, billId: number) {
const bill = await Bill.tenant().query() const { Bill, ItemEntry, Vendor } = this.tenancy.models(tenantId);
const bill = await Bill.query()
.where('id', billId) .where('id', billId)
.withGraphFetched('entries') .withGraphFetched('entries')
.first(); .first();
// Delete all associated bill entries. // Delete all associated bill entries.
const deleteBillEntriesOper = ItemEntry.tenant() const deleteBillEntriesOper = ItemEntry.query()
.query()
.where('reference_type', 'Bill') .where('reference_type', 'Bill')
.where('reference_id', billId) .where('reference_id', billId)
.delete(); .delete();
// Delete the bill transaction. // Delete the bill transaction.
const deleteBillOper = Bill.tenant().query().where('id', billId).delete(); const deleteBillOper = Bill.query().where('id', billId).delete();
// Delete associated bill journal transactions. // Delete associated bill journal transactions.
const deleteTransactionsOper = JournalPosterService.deleteJournalTransactions( const deleteTransactionsOper = JournalPosterService.deleteJournalTransactions(
@@ -181,8 +194,8 @@ export default class BillsService extends SalesInvoicesCost {
'Bill' 'Bill'
); );
// Delete bill associated inventory transactions. // Delete bill associated inventory transactions.
const deleteInventoryTransOper = InventoryService.deleteInventoryTransactions( const deleteInventoryTransOper = this.inventoryService.deleteInventoryTransactions(
billId, 'Bill' tenantId, billId, 'Bill'
); );
// Revert vendor balance. // Revert vendor balance.
const revertVendorBalance = Vendor.changeBalance(bill.vendorId, bill.amount * -1); const revertVendorBalance = Vendor.changeBalance(bill.vendorId, bill.amount * -1);
@@ -196,7 +209,7 @@ export default class BillsService extends SalesInvoicesCost {
]); ]);
// Schedule sale invoice re-compute based on the item cost // Schedule sale invoice re-compute based on the item cost
// method and starting date. // method and starting date.
await this.scheduleComputeBillItemsCost(bill); await this.scheduleComputeBillItemsCost(tenantId, bill);
} }
/** /**
@@ -204,7 +217,12 @@ export default class BillsService extends SalesInvoicesCost {
* @param {Bill} bill * @param {Bill} bill
* @param {number} billId * @param {number} billId
*/ */
static recordInventoryTransactions(bill, billId, override) { recordInventoryTransactions(
tenantId: number,
bill: any,
billId: number,
override?: boolean
) {
const inventoryTransactions = bill.entries const inventoryTransactions = bill.entries
.map((entry) => ({ .map((entry) => ({
...pick(entry, ['item_id', 'quantity', 'rate']), ...pick(entry, ['item_id', 'quantity', 'rate']),
@@ -216,8 +234,8 @@ export default class BillsService extends SalesInvoicesCost {
entryId: entry.id, entryId: entry.id,
})); }));
return InventoryService.recordInventoryTransactions( return this.inventoryService.recordInventoryTransactions(
inventoryTransactions, override tenantId, inventoryTransactions, override
); );
} }
@@ -227,19 +245,21 @@ export default class BillsService extends SalesInvoicesCost {
* @param {IBill} bill * @param {IBill} bill
* @param {Integer} billId * @param {Integer} billId
*/ */
static async recordJournalTransactions(bill, billId) { async recordJournalTransactions(tenantId: number, bill: any, billId?: number) {
const { AccountTransaction, Item, Account } = this.tenancy.models(tenantId);
const entriesItemsIds = bill.entries.map((entry) => entry.item_id); const entriesItemsIds = bill.entries.map((entry) => entry.item_id);
const payableTotal = sumBy(bill.entries, 'amount'); const payableTotal = sumBy(bill.entries, 'amount');
const formattedDate = moment(bill.bill_date).format('YYYY-MM-DD'); const formattedDate = moment(bill.bill_date).format('YYYY-MM-DD');
const storedItems = await Item.tenant() const storedItems = await Item.query()
.query()
.whereIn('id', entriesItemsIds); .whereIn('id', entriesItemsIds);
const storedItemsMap = new Map(storedItems.map((item) => [item.id, item])); const storedItemsMap = new Map(storedItems.map((item) => [item.id, item]));
const payableAccount = await AccountsService.getAccountByType('accounts_payable'); const payableAccount = await this.accountsService.getAccountByType(
tenantId, 'accounts_payable'
const accountsDepGraph = await Account.tenant().depGraph().query(); );
const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph); const journal = new JournalPoster(accountsDepGraph);
const commonJournalMeta = { const commonJournalMeta = {
@@ -251,8 +271,7 @@ export default class BillsService extends SalesInvoicesCost {
accural: true, accural: true,
}; };
if (billId) { if (billId) {
const transactions = await AccountTransaction.tenant() const transactions = await AccountTransaction.query()
.query()
.whereIn('reference_type', ['Bill']) .whereIn('reference_type', ['Bill'])
.whereIn('reference_id', [billId]) .whereIn('reference_id', [billId])
.withGraphFetched('account.type'); .withGraphFetched('account.type');
@@ -291,11 +310,14 @@ export default class BillsService extends SalesInvoicesCost {
/** /**
* Detarmines whether the bill exists on the storage. * Detarmines whether the bill exists on the storage.
* @param {Integer} billId * @param {number} tenantId - The given tenant id.
* @param {Integer} billId - The given bill id.
* @return {Boolean} * @return {Boolean}
*/ */
static async isBillExists(billId) { async isBillExists(tenantId: number, billId: number) {
const foundBills = await Bill.tenant().query().where('id', billId); const { Bill } = this.tenancy.models(tenantId);
const foundBills = await Bill.query().where('id', billId);
return foundBills.length > 0; return foundBills.length > 0;
} }
@@ -304,18 +326,23 @@ export default class BillsService extends SalesInvoicesCost {
* @param {Array} billsIds * @param {Array} billsIds
* @return {Boolean} * @return {Boolean}
*/ */
static async isBillsExist(billsIds) { async isBillsExist(tenantId: number, billsIds: number[]) {
const bills = await Bill.tenant().query().whereIn('id', billsIds); const { Bill } = this.tenancy.models(tenantId);
const bills = await Bill.query().whereIn('id', billsIds);
return bills.length > 0; return bills.length > 0;
} }
/** /**
* Detarmines whether the given bill id exists on the storage. * Detarmines whether the given bill id exists on the storage.
* @param {number} tenantId
* @param {Integer} billNumber * @param {Integer} billNumber
* @return {boolean}
*/ */
static async isBillNoExists(billNumber) { async isBillNoExists(tenantId: number, billNumber : string) {
const foundBills = await Bill.tenant() const { Bill } = this.tenancy.models(tenantId);
.query()
const foundBills = await Bill.query()
.where('bill_number', billNumber); .where('bill_number', billNumber);
return foundBills.length > 0; return foundBills.length > 0;
} }
@@ -325,8 +352,10 @@ export default class BillsService extends SalesInvoicesCost {
* @param {Integer} billId - * @param {Integer} billId -
* @returns {Promise} * @returns {Promise}
*/ */
static getBill(billId) { getBill(tenantId: number, billId: number) {
return Bill.tenant().query().where('id', billId).first(); const { Bill } = this.tenancy.models(tenantId);
return Bill.query().where('id', billId).first();
} }
/** /**
@@ -334,9 +363,10 @@ export default class BillsService extends SalesInvoicesCost {
* @param {Integer} billId - * @param {Integer} billId -
* @returns {Promise} * @returns {Promise}
*/ */
static getBillWithMetadata(billId) { getBillWithMetadata(tenantId: number, billId: number) {
return Bill.tenant() const { Bill } = this.tenancy.models(tenantId);
.query()
return Bill.query()
.where('id', billId) .where('id', billId)
.withGraphFetched('vendor') .withGraphFetched('vendor')
.withGraphFetched('entries') .withGraphFetched('entries')
@@ -345,21 +375,27 @@ export default class BillsService extends SalesInvoicesCost {
/** /**
* Schedules compute bill items cost based on each item cost method. * Schedules compute bill items cost based on each item cost method.
* @param {IBill} bill * @param {number} tenantId -
* @param {IBill} bill -
* @return {Promise} * @return {Promise}
*/ */
static async scheduleComputeBillItemsCost(bill) { async scheduleComputeBillItemsCost(tenantId: number, bill) {
const { Item } = this.tenancy.models(tenantId);
const billItemsIds = bill.entries.map((entry) => entry.item_id); const billItemsIds = bill.entries.map((entry) => entry.item_id);
// Retrieves inventory items only. // Retrieves inventory items only.
const inventoryItems = await Item.tenant().query() const inventoryItems = await Item.query()
.whereIn('id', billItemsIds) .whereIn('id', billItemsIds)
.where('type', 'inventory'); .where('type', 'inventory');
const inventoryItemsIds = inventoryItems.map(i => i.id); const inventoryItemsIds = inventoryItems.map(i => i.id);
if (inventoryItemsIds.length > 0) { if (inventoryItemsIds.length > 0) {
await this.scheduleComputeItemsCost(inventoryItemsIds, bill.bill_date); await this.scheduleComputeItemsCost(
tenantId,
inventoryItemsIds,
bill.bill_date
);
} }
} }
} }

View File

@@ -1,7 +1,13 @@
import { difference, omit } from 'lodash'; import { difference, omit } from 'lodash';
import { Service, Inject } from 'typedi';
import TenancyService from '@/services/Tenancy/TenancyService';
import { ItemEntry } from '@/models'; import { ItemEntry } from '@/models';
@Service()
export default class HasItemEntries { export default class HasItemEntries {
@Inject()
tenancy: TenancyService;
/** /**
* Patch items entries to the storage. * Patch items entries to the storage.
* *
@@ -9,15 +15,17 @@ export default class HasItemEntries {
* @param {Array} oldEntries - * @param {Array} oldEntries -
* @param {String} referenceType - * @param {String} referenceType -
* @param {String|Number} referenceId - * @param {String|Number} referenceId -
*
* @return {Promise} * @return {Promise}
*/ */
static async patchItemsEntries( async patchItemsEntries(
tenantId: number,
newEntries: Array<any>, newEntries: Array<any>,
oldEntries: Array<any>, oldEntries: Array<any>,
referenceType: string, referenceType: string,
referenceId: string|number referenceId: string|number
) { ) {
const { ItemEntry } = this.tenancy.models(tenantId);
const entriesHasIds = newEntries.filter((entry) => entry.id); const entriesHasIds = newEntries.filter((entry) => entry.id);
const entriesHasNoIds = newEntries.filter((entry) => !entry.id); const entriesHasNoIds = newEntries.filter((entry) => !entry.id);
const entriesIds = entriesHasIds.map(entry => entry.id); const entriesIds = entriesHasIds.map(entry => entry.id);
@@ -31,15 +39,13 @@ export default class HasItemEntries {
entriesIds, entriesIds,
); );
if (entriesIdsShouldDelete.length > 0) { if (entriesIdsShouldDelete.length > 0) {
const deleteOper = ItemEntry.tenant() const deleteOper = ItemEntry.query()
.query()
.whereIn('id', entriesIdsShouldDelete) .whereIn('id', entriesIdsShouldDelete)
.delete(); .delete();
opers.push(deleteOper); opers.push(deleteOper);
} }
entriesHasIds.forEach((entry) => { entriesHasIds.forEach((entry) => {
const updateOper = ItemEntry.tenant() const updateOper = ItemEntry.query()
.query()
.where('id', entry.id) .where('id', entry.id)
.update({ .update({
...omit(entry, excludeAttrs), ...omit(entry, excludeAttrs),
@@ -47,8 +53,7 @@ export default class HasItemEntries {
opers.push(updateOper); opers.push(updateOper);
}); });
entriesHasNoIds.forEach((entry) => { entriesHasNoIds.forEach((entry) => {
const insertOper = ItemEntry.tenant() const insertOper = ItemEntry.query()
.query()
.insert({ .insert({
reference_id: referenceId, reference_id: referenceId,
reference_type: referenceType, reference_type: referenceType,
@@ -59,7 +64,7 @@ export default class HasItemEntries {
return Promise.all([...opers]); return Promise.all([...opers]);
} }
static filterNonInventoryEntries(entries: [], items: []) { filterNonInventoryEntries(entries: [], items: []) {
const nonInventoryItems = items.filter((item: any) => item.type !== 'inventory'); const nonInventoryItems = items.filter((item: any) => item.type !== 'inventory');
const nonInventoryItemsIds = nonInventoryItems.map((i: any) => i.id); const nonInventoryItemsIds = nonInventoryItems.map((i: any) => i.id);
@@ -69,7 +74,7 @@ export default class HasItemEntries {
)); ));
} }
static filterInventoryEntries(entries: [], items: []) { filterInventoryEntries(entries: [], items: []) {
const inventoryItems = items.filter((item: any) => item.type === 'inventory'); const inventoryItems = items.filter((item: any) => item.type === 'inventory');
const inventoryItemsIds = inventoryItems.map((i: any) => i.id); const inventoryItemsIds = inventoryItems.map((i: any) => i.id);

View File

@@ -1,12 +1,26 @@
import { Account, AccountTransaction } from '@/models'; import { Service, Inject } from 'typedi';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from '@/services/Accounting/JournalPoster';
import TenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class JournalPosterService { export default class JournalPosterService {
@Inject()
tenancy: TenancyService;
/** /**
* Deletes the journal transactions that associated to the given reference id. * Deletes the journal transactions that associated to the given reference id.
* @param {number} tenantId - The given tenant id.
* @param {number} referenceId - The transaction reference id.
* @param {string} referenceType - The transaction reference type.
* @return {Promise}
*/ */
static async deleteJournalTransactions(referenceId, referenceType) { async deleteJournalTransactions(
tenantId: number,
referenceId: number,
referenceType: string
) {
const { Account, AccountTransaction } = this.tenancy.models(tenantId);
const transactions = await AccountTransaction.tenant() const transactions = await AccountTransaction.tenant()
.query() .query()
.whereIn('reference_type', [referenceType]) .whereIn('reference_type', [referenceType])

View File

@@ -1,13 +1,7 @@
import { omit, sumBy, chain } from 'lodash'; import { omit, sumBy, chain } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { import { Service, Inject } from 'typedi';
AccountTransaction, import { IPaymentReceiveOTD } from '@/interfaces';
PaymentReceive,
PaymentReceiveEntry,
SaleInvoice,
Customer,
Account,
} from '@/models';
import AccountsService from '@/services/Accounts/AccountsService'; import AccountsService from '@/services/Accounts/AccountsService';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from '@/services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry'; import JournalEntry from '@/services/Accounting/JournalEntry';
@@ -15,23 +9,41 @@ import JournalPosterService from '@/services/Sales/JournalPosterService';
import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries'; import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries';
import PaymentReceiveEntryRepository from '@/repositories/PaymentReceiveEntryRepository'; import PaymentReceiveEntryRepository from '@/repositories/PaymentReceiveEntryRepository';
import CustomerRepository from '@/repositories/CustomerRepository'; import CustomerRepository from '@/repositories/CustomerRepository';
import TenancyService from '@/services/Tenancy/TenancyService';
import { formatDateFields } from '@/utils'; import { formatDateFields } from '@/utils';
/** /**
* Payment receive service. * Payment receive service.
* @service * @service
*/ */
@Service()
export default class PaymentReceiveService { export default class PaymentReceiveService {
@Inject()
accountsService: AccountsService;
@Inject()
tenancy: TenancyService;
@Inject()
journalService: JournalPosterService;
/** /**
* Creates a new payment receive and store it to the storage * Creates a new payment receive and store it to the storage
* with associated invoices payment and journal transactions. * with associated invoices payment and journal transactions.
* @async * @async
* @param {number} tenantId - Tenant id.
* @param {IPaymentReceive} paymentReceive * @param {IPaymentReceive} paymentReceive
*/ */
static async createPaymentReceive(paymentReceive: any) { async createPaymentReceive(tenantId: number, paymentReceive: IPaymentReceiveOTD) {
const {
PaymentReceive,
PaymentReceiveEntry,
SaleInvoice,
Customer,
} = this.tenancy.models(tenantId);
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount'); const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
const storedPaymentReceive = await PaymentReceive.tenant() const storedPaymentReceive = await PaymentReceive.query()
.query()
.insert({ .insert({
amount: paymentAmount, amount: paymentAmount,
...formatDateFields(omit(paymentReceive, ['entries']), ['payment_date']), ...formatDateFields(omit(paymentReceive, ['entries']), ['payment_date']),
@@ -39,15 +51,13 @@ export default class PaymentReceiveService {
const storeOpers: Array<any> = []; const storeOpers: Array<any> = [];
paymentReceive.entries.forEach((entry: any) => { paymentReceive.entries.forEach((entry: any) => {
const oper = PaymentReceiveEntry.tenant() const oper = PaymentReceiveEntry.query()
.query()
.insert({ .insert({
payment_receive_id: storedPaymentReceive.id, payment_receive_id: storedPaymentReceive.id,
...entry, ...entry,
}); });
// Increment the invoice payment amount. // Increment the invoice payment amount.
const invoice = SaleInvoice.tenant() const invoice = SaleInvoice.query()
.query()
.where('id', entry.invoice_id) .where('id', entry.invoice_id)
.increment('payment_amount', entry.payment_amount); .increment('payment_amount', entry.payment_amount);
@@ -59,10 +69,10 @@ export default class PaymentReceiveService {
paymentAmount, paymentAmount,
); );
// Records the sale invoice journal transactions. // Records the sale invoice journal transactions.
const recordJournalTransactions = this.recordPaymentReceiveJournalEntries({ const recordJournalTransactions = this.recordPaymentReceiveJournalEntries(tenantId,{
id: storedPaymentReceive.id, id: storedPaymentReceive.id,
...paymentReceive, ...paymentReceive,
}); });
await Promise.all([ await Promise.all([
...storeOpers, ...storeOpers,
customerIncrementOper, customerIncrementOper,
@@ -82,19 +92,22 @@ export default class PaymentReceiveService {
* - Update the different customer balances. * - Update the different customer balances.
* - Update the different invoice payment amount. * - Update the different invoice payment amount.
* @async * @async
* @param {Integer} paymentReceiveId * @param {number} tenantId -
* @param {IPaymentReceive} paymentReceive * @param {Integer} paymentReceiveId -
* @param {IPaymentReceive} oldPaymentReceive * @param {IPaymentReceive} paymentReceive -
* @param {IPaymentReceive} oldPaymentReceive -
*/ */
static async editPaymentReceive( async editPaymentReceive(
tenantId: number,
paymentReceiveId: number, paymentReceiveId: number,
paymentReceive: any, paymentReceive: any,
oldPaymentReceive: any oldPaymentReceive: any
) { ) {
const { PaymentReceive } = this.tenancy.models(tenantId);
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount'); const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
// Update the payment receive transaction. // Update the payment receive transaction.
const updatePaymentReceive = await PaymentReceive.tenant() const updatePaymentReceive = await PaymentReceive.query()
.query()
.where('id', paymentReceiveId) .where('id', paymentReceiveId)
.update({ .update({
amount: paymentAmount, amount: paymentAmount,
@@ -131,6 +144,7 @@ export default class PaymentReceiveService {
} }
// Re-write the journal transactions of the given payment receive. // Re-write the journal transactions of the given payment receive.
const recordJournalTransactions = this.recordPaymentReceiveJournalEntries( const recordJournalTransactions = this.recordPaymentReceiveJournalEntries(
tenantId,
{ {
id: oldPaymentReceive.id, id: oldPaymentReceive.id,
...paymentReceive, ...paymentReceive,
@@ -147,6 +161,7 @@ export default class PaymentReceiveService {
); );
// Change the difference between the old and new invoice payment amount. // Change the difference between the old and new invoice payment amount.
const diffInvoicePaymentAmount = this.saveChangeInvoicePaymentAmount( const diffInvoicePaymentAmount = this.saveChangeInvoicePaymentAmount(
tenantId,
oldPaymentReceive.entries, oldPaymentReceive.entries,
paymentReceive.entries paymentReceive.entries
); );
@@ -169,24 +184,26 @@ export default class PaymentReceiveService {
* - Revert the customer balance. * - Revert the customer balance.
* - Revert the payment amount of the associated invoices. * - Revert the payment amount of the associated invoices.
* @async * @async
* @param {Integer} paymentReceiveId * @param {number} tenantId - Tenant id.
* @param {IPaymentReceive} paymentReceive * @param {Integer} paymentReceiveId - Payment receive id.
* @param {IPaymentReceive} paymentReceive - Payment receive object.
*/ */
static async deletePaymentReceive(paymentReceiveId: number, paymentReceive: any) { async deletePaymentReceive(tenantId: number, paymentReceiveId: number, paymentReceive: any) {
const { PaymentReceive, PaymentReceiveEntry, Customer } = this.tenancy.models(tenantId);
// Deletes the payment receive transaction. // Deletes the payment receive transaction.
await PaymentReceive.tenant() await PaymentReceive.query()
.query()
.where('id', paymentReceiveId) .where('id', paymentReceiveId)
.delete(); .delete();
// Deletes the payment receive associated entries. // Deletes the payment receive associated entries.
await PaymentReceiveEntry.tenant() await PaymentReceiveEntry.query()
.query()
.where('payment_receive_id', paymentReceiveId) .where('payment_receive_id', paymentReceiveId)
.delete(); .delete();
// Delete all associated journal transactions to payment receive transaction. // Delete all associated journal transactions to payment receive transaction.
const deleteTransactionsOper = JournalPosterService.deleteJournalTransactions( const deleteTransactionsOper = this.journalService.deleteJournalTransactions(
tenantId,
paymentReceiveId, paymentReceiveId,
'PaymentReceive' 'PaymentReceive'
); );
@@ -197,6 +214,7 @@ export default class PaymentReceiveService {
); );
// Revert the invoices payments amount. // Revert the invoices payments amount.
const revertInvoicesPaymentAmount = this.revertInvoicePaymentAmount( const revertInvoicesPaymentAmount = this.revertInvoicePaymentAmount(
tenantId,
paymentReceive.entries.map((entry: any) => ({ paymentReceive.entries.map((entry: any) => ({
invoiceId: entry.invoiceId, invoiceId: entry.invoiceId,
revertAmount: entry.paymentAmount, revertAmount: entry.paymentAmount,
@@ -211,11 +229,12 @@ export default class PaymentReceiveService {
/** /**
* Retrieve the payment receive details of the given id. * Retrieve the payment receive details of the given id.
* @param {Integer} paymentReceiveId * @param {number} tenantId - Tenant id.
* @param {Integer} paymentReceiveId - Payment receive id.
*/ */
static async getPaymentReceive(paymentReceiveId: number) { async getPaymentReceive(tenantId: number, paymentReceiveId: number) {
const paymentReceive = await PaymentReceive.tenant() const { PaymentReceive } = this.tenancy.models(tenantId);
.query() const paymentReceive = await PaymentReceive.query()
.where('id', paymentReceiveId) .where('id', paymentReceiveId)
.withGraphFetched('entries.invoice') .withGraphFetched('entries.invoice')
.first(); .first();
@@ -226,9 +245,9 @@ export default class PaymentReceiveService {
* Retrieve the payment receive details with associated invoices. * Retrieve the payment receive details with associated invoices.
* @param {Integer} paymentReceiveId * @param {Integer} paymentReceiveId
*/ */
static async getPaymentReceiveWithInvoices(paymentReceiveId: number) { async getPaymentReceiveWithInvoices(tenantId: number, paymentReceiveId: number) {
return PaymentReceive.tenant() const { PaymentReceive } = this.tenancy.models(tenantId);
.query() return PaymentReceive.query()
.where('id', paymentReceiveId) .where('id', paymentReceiveId)
.withGraphFetched('invoices') .withGraphFetched('invoices')
.first(); .first();
@@ -236,11 +255,12 @@ export default class PaymentReceiveService {
/** /**
* Detarmines whether the payment receive exists on the storage. * Detarmines whether the payment receive exists on the storage.
* @param {number} tenantId - Tenant id.
* @param {Integer} paymentReceiveId * @param {Integer} paymentReceiveId
*/ */
static async isPaymentReceiveExists(paymentReceiveId: number) { async isPaymentReceiveExists(tenantId: number, paymentReceiveId: number) {
const paymentReceives = await PaymentReceive.tenant() const { PaymentReceive } = this.tenancy.models(tenantId);
.query() const paymentReceives = await PaymentReceive.query()
.where('id', paymentReceiveId); .where('id', paymentReceiveId);
return paymentReceives.length > 0; return paymentReceives.length > 0;
} }
@@ -248,15 +268,17 @@ export default class PaymentReceiveService {
/** /**
* Detarmines the payment receive number existance. * Detarmines the payment receive number existance.
* @async * @async
* @param {number} tenantId - Tenant id.
* @param {Integer} paymentReceiveNumber - Payment receive number. * @param {Integer} paymentReceiveNumber - Payment receive number.
* @param {Integer} paymentReceiveId - Payment receive id. * @param {Integer} paymentReceiveId - Payment receive id.
*/ */
static async isPaymentReceiveNoExists( async isPaymentReceiveNoExists(
tenantId: number,
paymentReceiveNumber: string|number, paymentReceiveNumber: string|number,
paymentReceiveId: number paymentReceiveId: number
) { ) {
const paymentReceives = await PaymentReceive.tenant() const { PaymentReceive } = this.tenancy.models(tenantId);
.query() const paymentReceives = await PaymentReceive.query()
.where('payment_receive_no', paymentReceiveNumber) .where('payment_receive_no', paymentReceiveNumber)
.onBuild((query) => { .onBuild((query) => {
if (paymentReceiveId) { if (paymentReceiveId) {
@@ -273,21 +295,25 @@ export default class PaymentReceiveService {
* -------- * --------
* - Account receivable -> Debit * - Account receivable -> Debit
* - Payment account [current asset] -> Credit * - Payment account [current asset] -> Credit
*
* @async * @async
* @param {number} tenantId - Tenant id.
* @param {IPaymentReceive} paymentReceive * @param {IPaymentReceive} paymentReceive
* @param {Number} paymentReceiveId * @param {Number} paymentReceiveId
*/ */
static async recordPaymentReceiveJournalEntries( async recordPaymentReceiveJournalEntries(
tenantId: number,
paymentReceive: any, paymentReceive: any,
paymentReceiveId?: number paymentReceiveId?: number
) { ) {
const { Account, AccountTransaction } = this.tenancy.models(tenantId);
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount'); const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
const formattedDate = moment(paymentReceive.payment_date).format('YYYY-MM-DD'); const formattedDate = moment(paymentReceive.payment_date).format('YYYY-MM-DD');
const receivableAccount = await AccountsService.getAccountByType( const receivableAccount = await this.accountsService.getAccountByType(
tenantId,
'accounts_receivable' 'accounts_receivable'
); );
const accountsDepGraph = await Account.tenant().depGraph().query(); const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph); const journal = new JournalPoster(accountsDepGraph);
const commonJournal = { const commonJournal = {
debit: 0, debit: 0,
@@ -297,8 +323,7 @@ export default class PaymentReceiveService {
date: formattedDate, date: formattedDate,
}; };
if (paymentReceiveId) { if (paymentReceiveId) {
const transactions = await AccountTransaction.tenant() const transactions = await AccountTransaction.query()
.query()
.whereIn('reference_type', ['PaymentReceive']) .whereIn('reference_type', ['PaymentReceive'])
.where('reference_id', paymentReceiveId) .where('reference_id', paymentReceiveId)
.withGraphFetched('account.type'); .withGraphFetched('account.type');
@@ -330,15 +355,18 @@ export default class PaymentReceiveService {
/** /**
* Revert the payment amount of the given invoices ids. * Revert the payment amount of the given invoices ids.
* @async
* @param {number} tenantId - Tenant id.
* @param {Array} revertInvoices * @param {Array} revertInvoices
* @return {Promise}
*/ */
static async revertInvoicePaymentAmount(revertInvoices: any[]) { async revertInvoicePaymentAmount(tenantId: number, revertInvoices: any[]) {
const { SaleInvoice } = this.tenancy.models(tenantId);
const opers: Promise<T>[] = []; const opers: Promise<T>[] = [];
revertInvoices.forEach((revertInvoice) => { revertInvoices.forEach((revertInvoice) => {
const { revertAmount, invoiceId } = revertInvoice; const { revertAmount, invoiceId } = revertInvoice;
const oper = SaleInvoice.tenant() const oper = SaleInvoice.query()
.query()
.where('id', invoiceId) .where('id', invoiceId)
.decrement('payment_amount', revertAmount); .decrement('payment_amount', revertAmount);
opers.push(oper); opers.push(oper);
@@ -348,14 +376,18 @@ export default class PaymentReceiveService {
/** /**
* Saves difference changing between old and new invoice payment amount. * Saves difference changing between old and new invoice payment amount.
* @async
* @param {number} tenantId - Tenant id.
* @param {Array} paymentReceiveEntries * @param {Array} paymentReceiveEntries
* @param {Array} newPaymentReceiveEntries * @param {Array} newPaymentReceiveEntries
* @return * @return
*/ */
static async saveChangeInvoicePaymentAmount( async saveChangeInvoicePaymentAmount(
tenantId: number,
paymentReceiveEntries: [], paymentReceiveEntries: [],
newPaymentReceiveEntries: [], newPaymentReceiveEntries: [],
) { ) {
const { SaleInvoice } = this.tenancy.models(tenantId);
const opers: Promise<T>[] = []; const opers: Promise<T>[] = [];
const newEntriesTable = chain(newPaymentReceiveEntries) const newEntriesTable = chain(newPaymentReceiveEntries)
.groupBy('invoice_id') .groupBy('invoice_id')

View File

@@ -1,31 +1,44 @@
import { omit, difference, sumBy, mixin } from 'lodash'; import { omit, difference, sumBy, mixin } from 'lodash';
import moment from 'moment'; import { Service, Inject } from 'typedi';
import { SaleEstimate, ItemEntry } from '@/models';
import HasItemsEntries from '@/services/Sales/HasItemsEntries'; import HasItemsEntries from '@/services/Sales/HasItemsEntries';
import { formatDateFields } from '@/utils'; import { formatDateFields } from '@/utils';
import TenancyService from '@/services/Tenancy/TenancyService';
/**
* Sale estimate service.
* @Service
*/
@Service()
export default class SaleEstimateService { export default class SaleEstimateService {
@Inject()
tenancy: TenancyService;
@Inject()
itemsEntriesService: HasItemsEntries;
/** /**
* Creates a new estimate with associated entries. * Creates a new estimate with associated entries.
* @async * @async
* @param {number} tenantId - The tenant id.
* @param {EstimateDTO} estimate * @param {EstimateDTO} estimate
* @return {void} * @return {void}
*/ */
static async createEstimate(estimateDTO: any) { async createEstimate(tenantId: number, estimateDTO: any) {
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
const amount = sumBy(estimateDTO.entries, e => ItemEntry.calcAmount(e));
const estimate = { const estimate = {
amount: sumBy(estimateDTO.entries, 'amount'), amount,
...formatDateFields(estimateDTO, ['estimate_date', 'expiration_date']), ...formatDateFields(estimateDTO, ['estimate_date', 'expiration_date']),
}; };
const storedEstimate = await SaleEstimate.tenant() const storedEstimate = await SaleEstimate.query()
.query()
.insert({ .insert({
...omit(estimate, ['entries']), ...omit(estimate, ['entries']),
}); });
const storeEstimateEntriesOpers: any[] = []; const storeEstimateEntriesOpers: any[] = [];
estimate.entries.forEach((entry: any) => { estimate.entries.forEach((entry: any) => {
const oper = ItemEntry.tenant() const oper = ItemEntry.query()
.query()
.insert({ .insert({
reference_type: 'SaleEstimate', reference_type: 'SaleEstimate',
reference_id: storedEstimate.id, reference_id: storedEstimate.id,
@@ -41,27 +54,33 @@ export default class SaleEstimateService {
/** /**
* Edit details of the given estimate with associated entries. * Edit details of the given estimate with associated entries.
* @async * @async
* @param {number} tenantId - The tenant id.
* @param {Integer} estimateId * @param {Integer} estimateId
* @param {EstimateDTO} estimate * @param {EstimateDTO} estimate
* @return {void} * @return {void}
*/ */
static async editEstimate(estimateId: number, estimateDTO: any) { async editEstimate(tenantId: number, estimateId: number, estimateDTO: any) {
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
const amount = sumBy(estimateDTO.entries, e => ItemEntry.calcAmount(e));
const estimate = { const estimate = {
amount: sumBy(estimateDTO.entries, 'amount'), amount,
...formatDateFields(estimateDTO, ['estimate_date', 'expiration_date']), ...formatDateFields(estimateDTO, ['estimate_date', 'expiration_date']),
}; };
const updatedEstimate = await SaleEstimate.tenant() const updatedEstimate = await SaleEstimate.query()
.query()
.update({ .update({
...omit(estimate, ['entries']), ...omit(estimate, ['entries']),
}); });
const storedEstimateEntries = await ItemEntry.tenant() const storedEstimateEntries = await ItemEntry.query()
.query()
.where('reference_id', estimateId) .where('reference_id', estimateId)
.where('reference_type', 'SaleEstimate'); .where('reference_type', 'SaleEstimate');
const patchItemsEntries = HasItemsEntries.patchItemsEntries( const patchItemsEntries = this.itemsEntriesService.patchItemsEntries(
estimate.entries, storedEstimateEntries, 'SaleEstimate', estimateId tenantId,
estimate.entries,
storedEstimateEntries,
'SaleEstimate',
estimateId,
); );
return Promise.all([ return Promise.all([
patchItemsEntries, patchItemsEntries,
@@ -71,32 +90,32 @@ export default class SaleEstimateService {
/** /**
* Deletes the given estimate id with associated entries. * Deletes the given estimate id with associated entries.
* @async * @async
* @param {number} tenantId - The tenant id.
* @param {IEstimate} estimateId * @param {IEstimate} estimateId
* @return {void} * @return {void}
*/ */
static async deleteEstimate(estimateId: number) { async deleteEstimate(tenantId: number, estimateId: number) {
await ItemEntry.tenant() const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
.query() await ItemEntry.query()
.where('reference_id', estimateId) .where('reference_id', estimateId)
.where('reference_type', 'SaleEstimate') .where('reference_type', 'SaleEstimate')
.delete(); .delete();
await SaleEstimate.tenant() await SaleEstimate.query()
.query()
.where('id', estimateId) .where('id', estimateId)
.delete(); .delete();
} }
/** /**
* Validates the given estimate ID exists. * Validates the given estimate ID exists.
* @async * @async
* @param {number} tenantId - The tenant id.
* @param {Numeric} estimateId * @param {Numeric} estimateId
* @return {Boolean} * @return {Boolean}
*/ */
static async isEstimateExists(estimateId: number) { async isEstimateExists(estimateId: number) {
const foundEstimate = await SaleEstimate.tenant() const { SaleEstimate } = this.tenancy.models(tenantId);
.query() const foundEstimate = await SaleEstimate.query()
.where('id', estimateId); .where('id', estimateId);
return foundEstimate.length !== 0; return foundEstimate.length !== 0;
} }
@@ -104,16 +123,17 @@ export default class SaleEstimateService {
/** /**
* Validates the given estimate entries IDs. * Validates the given estimate entries IDs.
* @async * @async
* @param {Numeric} estimateId * @param {number} tenantId - The tenant id.
* @param {Numeric} estimateId - the sale estimate id.
* @param {IEstimate} estimate * @param {IEstimate} estimate
*/ */
static async isEstimateEntriesIDsExists(estimateId: number, estimate: any) { async isEstimateEntriesIDsExists(tenantId: number, estimateId: number, estimate: any) {
const { ItemEntry } = this.tenancy.models(tenantId);
const estimateEntriesIds = estimate.entries const estimateEntriesIds = estimate.entries
.filter((e: any) => e.id) .filter((e: any) => e.id)
.map((e: any) => e.id); .map((e: any) => e.id);
const estimateEntries = await ItemEntry.tenant() const estimateEntries = await ItemEntry.query()
.query()
.whereIn('id', estimateEntriesIds) .whereIn('id', estimateEntriesIds)
.where('reference_id', estimateId) .where('reference_id', estimateId)
.where('reference_type', 'SaleEstimate'); .where('reference_type', 'SaleEstimate');
@@ -128,12 +148,14 @@ export default class SaleEstimateService {
/** /**
* Retrieve the estimate details of the given estimate id. * Retrieve the estimate details of the given estimate id.
* @async
* @param {number} tenantId - The tenant id.
* @param {Integer} estimateId * @param {Integer} estimateId
* @return {IEstimate} * @return {IEstimate}
*/ */
static async getEstimate(estimateId: number) { async getEstimate(tenantId: number, estimateId: number) {
const estimate = await SaleEstimate.tenant() const { SaleEstimate } = this.tenancy.models(tenantId);
.query() const estimate = await SaleEstimate.query()
.where('id', estimateId) .where('id', estimateId)
.first(); .first();
@@ -142,11 +164,13 @@ export default class SaleEstimateService {
/** /**
* Retrieve the estimate details with associated entries. * Retrieve the estimate details with associated entries.
* @async
* @param {number} tenantId - The tenant id.
* @param {Integer} estimateId * @param {Integer} estimateId
*/ */
static async getEstimateWithEntries(estimateId: number) { async getEstimateWithEntries(tenantId: number, estimateId: number) {
const estimate = await SaleEstimate.tenant() const { SaleEstimate } = this.tenancy.models(tenantId);
.query() const estimate = await SaleEstimate.query()
.where('id', estimateId) .where('id', estimateId)
.withGraphFetched('entries') .withGraphFetched('entries')
.withGraphFetched('customer') .withGraphFetched('customer')
@@ -157,13 +181,15 @@ export default class SaleEstimateService {
/** /**
* Detarmines the estimate number uniqness. * Detarmines the estimate number uniqness.
* @async
* @param {number} tenantId - The tenant id.
* @param {String} estimateNumber * @param {String} estimateNumber
* @param {Integer} excludeEstimateId * @param {Integer} excludeEstimateId
* @return {Boolean} * @return {Boolean}
*/ */
static async isEstimateNumberUnique(estimateNumber: string, excludeEstimateId: number) { async isEstimateNumberUnique(tenantId: number, estimateNumber: string, excludeEstimateId: number) {
const foundEstimates = await SaleEstimate.tenant() const { SaleEstimate } = this.tenancy.models(tenantId);
.query() const foundEstimates = await SaleEstimate.query()
.onBuild((query: any) => { .onBuild((query: any) => {
query.where('estimate_number', estimateNumber); query.where('estimate_number', estimateNumber);

View File

@@ -1,52 +1,56 @@
import { Service, Inject } from 'typedi';
import { omit, sumBy, difference, pick, chain } from 'lodash'; import { omit, sumBy, difference, pick, chain } from 'lodash';
import { import { ISaleInvoice, ISaleInvoiceOTD, IItemEntry } from '@/interfaces';
SaleInvoice,
AccountTransaction,
InventoryTransaction,
Account,
ItemEntry,
Customer,
Item,
} from '@/models';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from '@/services/Accounting/JournalPoster';
import HasItemsEntries from '@/services/Sales/HasItemsEntries'; import HasItemsEntries from '@/services/Sales/HasItemsEntries';
import CustomerRepository from '@/repositories/CustomerRepository';
import InventoryService from '@/services/Inventory/Inventory'; import InventoryService from '@/services/Inventory/Inventory';
import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost'; import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost';
import { ISaleInvoice, ISaleInvoiceOTD, IItemEntry } from '@/interfaces'; import TenancyService from '@/services/Tenancy/TenancyService';
import { formatDateFields } from '@/utils'; import { formatDateFields } from '@/utils';
/** /**
* Sales invoices service * Sales invoices service
* @service * @service
*/ */
@Service()
export default class SaleInvoicesService extends SalesInvoicesCost { export default class SaleInvoicesService extends SalesInvoicesCost {
@Inject()
tenancy: TenancyService;
@Inject()
inventoryService: InventoryService;
@Inject()
itemsEntriesService: HasItemsEntries;
/** /**
* Creates a new sale invoices and store it to the storage * Creates a new sale invoices and store it to the storage
* with associated to entries and journal transactions. * with associated to entries and journal transactions.
* @async * @async
* @param {ISaleInvoice} * @param {number} tenantId =
* @param {ISaleInvoice} saleInvoiceDTO -
* @return {ISaleInvoice} * @return {ISaleInvoice}
*/ */
static async createSaleInvoice(saleInvoiceDTO: ISaleInvoiceOTD) { async createSaleInvoice(tenantId: number, saleInvoiceDTO: ISaleInvoiceOTD) {
const balance = sumBy(saleInvoiceDTO.entries, 'amount'); const { SaleInvoice, Customer, ItemEntry } = this.tenancy.models(tenantId);
const invLotNumber = await InventoryService.nextLotNumber();
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
const invLotNumber = await this.inventoryService.nextLotNumber(tenantId);
const saleInvoice: ISaleInvoice = { const saleInvoice: ISaleInvoice = {
...formatDateFields(saleInvoiceDTO, ['invoiceDate', 'dueDate']), ...formatDateFields(saleInvoiceDTO, ['invoice_date', 'due_date']),
balance, balance,
paymentAmount: 0, paymentAmount: 0,
invLotNumber, invLotNumber,
}; };
const storedInvoice = await SaleInvoice.tenant() const storedInvoice = await SaleInvoice.query()
.query()
.insert({ .insert({
...omit(saleInvoice, ['entries']), ...omit(saleInvoice, ['entries']),
}); });
const opers: Array<any> = []; const opers: Array<any> = [];
saleInvoice.entries.forEach((entry: any) => { saleInvoice.entries.forEach((entry: any) => {
const oper = ItemEntry.tenant() const oper = ItemEntry.query()
.query()
.insertAndFetch({ .insertAndFetch({
reference_type: 'SaleInvoice', reference_type: 'SaleInvoice',
reference_id: storedInvoice.id, reference_id: storedInvoice.id,
@@ -67,12 +71,11 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
...opers, incrementOper, ...opers, incrementOper,
]); ]);
// Records the inventory transactions for inventory items. // Records the inventory transactions for inventory items.
await this.recordInventoryTranscactions( await this.recordInventoryTranscactions(tenantId, saleInvoice, storedInvoice.id);
saleInvoice, storedInvoice.id
);
// Schedule sale invoice re-compute based on the item cost // Schedule sale invoice re-compute based on the item cost
// method and starting date. // method and starting date.
await this.scheduleComputeInvoiceItemsCost(storedInvoice.id); await this.scheduleComputeInvoiceItemsCost(tenantId, storedInvoice.id);
return storedInvoice; return storedInvoice;
} }
@@ -80,12 +83,15 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
/** /**
* Edit the given sale invoice. * Edit the given sale invoice.
* @async * @async
* @param {number} tenantId -
* @param {Number} saleInvoiceId - * @param {Number} saleInvoiceId -
* @param {ISaleInvoice} saleInvoice - * @param {ISaleInvoice} saleInvoice -
*/ */
static async editSaleInvoice(saleInvoiceId: number, saleInvoiceDTO: any) { async editSaleInvoice(tenantId: number, saleInvoiceId: number, saleInvoiceDTO: any) {
const balance = sumBy(saleInvoiceDTO.entries, 'amount'); const { SaleInvoice, ItemEntry, Customer } = this.tenancy.models(tenantId);
const oldSaleInvoice = await SaleInvoice.tenant().query()
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
const oldSaleInvoice = await SaleInvoice.query()
.where('id', saleInvoiceId) .where('id', saleInvoiceId)
.first(); .first();
@@ -94,24 +100,22 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
balance, balance,
invLotNumber: oldSaleInvoice.invLotNumber, invLotNumber: oldSaleInvoice.invLotNumber,
}; };
const updatedSaleInvoices: ISaleInvoice = await SaleInvoice.tenant() const updatedSaleInvoices: ISaleInvoice = await SaleInvoice.query()
.query()
.where('id', saleInvoiceId) .where('id', saleInvoiceId)
.update({ .update({
...omit(saleInvoice, ['entries', 'invLotNumber']), ...omit(saleInvoice, ['entries', 'invLotNumber']),
}); });
// Fetches the sale invoice items entries. // Fetches the sale invoice items entries.
const storedEntries = await ItemEntry.tenant() const storedEntries = await ItemEntry.query()
.query()
.where('reference_id', saleInvoiceId) .where('reference_id', saleInvoiceId)
.where('reference_type', 'SaleInvoice'); .where('reference_type', 'SaleInvoice');
// Patch update the sale invoice items entries. // Patch update the sale invoice items entries.
const patchItemsEntriesOper = HasItemsEntries.patchItemsEntries( const patchItemsEntriesOper = this.itemsEntriesService.patchItemsEntries(
saleInvoice.entries, storedEntries, 'SaleInvoice', saleInvoiceId, tenantId, saleInvoice.entries, storedEntries, 'SaleInvoice', saleInvoiceId,
); );
// Changes the diff customer balance between old and new amount. // Changes the diff customer balance between old and new amount.
const changeCustomerBalanceOper = CustomerRepository.changeDiffBalance( const changeCustomerBalanceOper = Customer.changeDiffBalance(
saleInvoice.customer_id, saleInvoice.customer_id,
oldSaleInvoice.customerId, oldSaleInvoice.customerId,
balance, balance,
@@ -119,7 +123,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
); );
// Records the inventory transactions for inventory items. // Records the inventory transactions for inventory items.
const recordInventoryTransOper = this.recordInventoryTranscactions( const recordInventoryTransOper = this.recordInventoryTranscactions(
saleInvoice, saleInvoiceId, true, tenantId, saleInvoice, saleInvoiceId, true,
); );
await Promise.all([ await Promise.all([
patchItemsEntriesOper, patchItemsEntriesOper,
@@ -128,23 +132,31 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
]); ]);
// Schedule sale invoice re-compute based on the item cost // Schedule sale invoice re-compute based on the item cost
// method and starting date. // method and starting date.
await this.scheduleComputeInvoiceItemsCost(saleInvoiceId, true); await this.scheduleComputeInvoiceItemsCost(tenantId, saleInvoiceId, true);
} }
/** /**
* Deletes the given sale invoice with associated entries * Deletes the given sale invoice with associated entries
* and journal transactions. * and journal transactions.
* @async * @async
* @param {Number} saleInvoiceId * @param {Number} saleInvoiceId - The given sale invoice id.
*/ */
static async deleteSaleInvoice(saleInvoiceId: number) { async deleteSaleInvoice(tenantId: number, saleInvoiceId: number) {
const oldSaleInvoice = await SaleInvoice.tenant().query() const {
SaleInvoice,
ItemEntry,
Customer,
Account,
InventoryTransaction,
AccountTransaction,
} = this.tenancy.models(tenantId);
const oldSaleInvoice = await SaleInvoice.query()
.findById(saleInvoiceId) .findById(saleInvoiceId)
.withGraphFetched('entries'); .withGraphFetched('entries');
await SaleInvoice.tenant().query().where('id', saleInvoiceId).delete(); await SaleInvoice.query().where('id', saleInvoiceId).delete();
await ItemEntry.tenant() await ItemEntry.query()
.query()
.where('reference_id', saleInvoiceId) .where('reference_id', saleInvoiceId)
.where('reference_type', 'SaleInvoice') .where('reference_type', 'SaleInvoice')
.delete(); .delete();
@@ -153,26 +165,25 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
oldSaleInvoice.customerId, oldSaleInvoice.customerId,
oldSaleInvoice.balance * -1, oldSaleInvoice.balance * -1,
); );
const invoiceTransactions = await AccountTransaction.tenant() const invoiceTransactions = await AccountTransaction.query()
.query()
.whereIn('reference_type', ['SaleInvoice']) .whereIn('reference_type', ['SaleInvoice'])
.where('reference_id', saleInvoiceId) .where('reference_id', saleInvoiceId)
.withGraphFetched('account.type'); .withGraphFetched('account.type');
const accountsDepGraph = await Account.tenant().depGraph().query(); const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph); const journal = new JournalPoster(accountsDepGraph);
journal.loadEntries(invoiceTransactions); journal.loadEntries(invoiceTransactions);
journal.removeEntries(); journal.removeEntries();
const inventoryTransactions = await InventoryTransaction.tenant() const inventoryTransactions = await InventoryTransaction.query()
.query()
.where('transaction_type', 'SaleInvoice') .where('transaction_type', 'SaleInvoice')
.where('transaction_id', saleInvoiceId); .where('transaction_id', saleInvoiceId);
// Revert inventory transactions. // Revert inventory transactions.
const revertInventoryTransactionsOper = this.revertInventoryTransactions( const revertInventoryTransactionsOper = this.revertInventoryTransactions(
inventoryTransactions tenantId,
inventoryTransactions,
); );
// Await all async operations. // Await all async operations.
await Promise.all([ await Promise.all([
@@ -183,7 +194,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
]); ]);
// Schedule sale invoice re-compute based on the item cost // Schedule sale invoice re-compute based on the item cost
// method and starting date. // method and starting date.
await this.scheduleComputeItemsCost(oldSaleInvoice) await this.scheduleComputeItemsCost(tenantId, oldSaleInvoice)
} }
/** /**
@@ -192,7 +203,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {number} saleInvoiceId - * @param {number} saleInvoiceId -
* @param {boolean} override - * @param {boolean} override -
*/ */
static recordInventoryTranscactions(saleInvoice, saleInvoiceId: number, override?: boolean){ recordInventoryTranscactions(tenantId: number, saleInvoice, saleInvoiceId: number, override?: boolean){
const inventortyTransactions = saleInvoice.entries const inventortyTransactions = saleInvoice.entries
.map((entry) => ({ .map((entry) => ({
...pick(entry, ['item_id', 'quantity', 'rate',]), ...pick(entry, ['item_id', 'quantity', 'rate',]),
@@ -204,8 +215,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
entryId: entry.id, entryId: entry.id,
})); }));
return InventoryService.recordInventoryTransactions( return this.inventoryService.recordInventoryTransactions(
inventortyTransactions, override, tenantId, inventortyTransactions, override,
); );
} }
@@ -214,15 +225,15 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {string} transactionType * @param {string} transactionType
* @param {number} transactionId * @param {number} transactionId
*/ */
static async revertInventoryTransactions(inventoryTransactions: array) { async revertInventoryTransactions(tenantId: number, inventoryTransactions: array) {
const { InventoryTransaction } = this.tenancy.models(tenantId);
const opers: Promise<[]>[] = []; const opers: Promise<[]>[] = [];
inventoryTransactions.forEach((trans: any) => { inventoryTransactions.forEach((trans: any) => {
switch(trans.direction) { switch(trans.direction) {
case 'OUT': case 'OUT':
if (trans.inventoryTransactionId) { if (trans.inventoryTransactionId) {
const revertRemaining = InventoryTransaction.tenant() const revertRemaining = InventoryTransaction.query()
.query()
.where('id', trans.inventoryTransactionId) .where('id', trans.inventoryTransactionId)
.where('direction', 'OUT') .where('direction', 'OUT')
.increment('remaining', trans.quanitity); .increment('remaining', trans.quanitity);
@@ -231,8 +242,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
} }
break; break;
case 'IN': case 'IN':
const removeRelationOper = InventoryTransaction.tenant() const removeRelationOper = InventoryTransaction.query()
.query()
.where('inventory_transaction_id', trans.id) .where('inventory_transaction_id', trans.id)
.where('direction', 'IN') .where('direction', 'IN')
.update({ .update({
@@ -250,8 +260,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @async * @async
* @param {Number} saleInvoiceId * @param {Number} saleInvoiceId
*/ */
static async getSaleInvoiceWithEntries(saleInvoiceId: number) { async getSaleInvoiceWithEntries(tenantId: number, saleInvoiceId: number) {
return SaleInvoice.tenant().query() const { SaleInvoice } = this.tenancy.models(tenantId);
return SaleInvoice.query()
.where('id', saleInvoiceId) .where('id', saleInvoiceId)
.withGraphFetched('entries') .withGraphFetched('entries')
.withGraphFetched('customer') .withGraphFetched('customer')
@@ -263,10 +274,11 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {Integer} saleInvoiceId * @param {Integer} saleInvoiceId
* @return {Boolean} * @return {Boolean}
*/ */
static async isSaleInvoiceExists(saleInvoiceId: number) { async isSaleInvoiceExists(tenantId: number, saleInvoiceId: number) {
const foundSaleInvoice = await SaleInvoice.tenant() const { SaleInvoice } = this.tenancy.models(tenantId);
.query() const foundSaleInvoice = await SaleInvoice.query()
.where('id', saleInvoiceId); .where('id', saleInvoiceId);
return foundSaleInvoice.length !== 0; return foundSaleInvoice.length !== 0;
} }
@@ -277,12 +289,15 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {Number} saleInvoiceId * @param {Number} saleInvoiceId
* @return {Boolean} * @return {Boolean}
*/ */
static async isSaleInvoiceNumberExists(saleInvoiceNumber: string|number, saleInvoiceId: number) { async isSaleInvoiceNumberExists(
const foundSaleInvoice = await SaleInvoice.tenant() tenantId: number,
.query() saleInvoiceNumber: string|number,
saleInvoiceId: number
) {
const { SaleInvoice } = this.tenancy.models(tenantId);
const foundSaleInvoice = await SaleInvoice.query()
.onBuild((query: any) => { .onBuild((query: any) => {
query.where('invoice_no', saleInvoiceNumber); query.where('invoice_no', saleInvoiceNumber);
if (saleInvoiceId) { if (saleInvoiceId) {
query.whereNot('id', saleInvoiceId); query.whereNot('id', saleInvoiceId);
} }
@@ -296,9 +311,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {Array} invoicesIds * @param {Array} invoicesIds
* @return {Array} * @return {Array}
*/ */
static async isInvoicesExist(invoicesIds: Array<number>) { async isInvoicesExist(tenantId: number, invoicesIds: Array<number>) {
const storedInvoices = await SaleInvoice.tenant() const { SaleInvoice } = this.tenancy.models(tenantId);
.query() const storedInvoices = await SaleInvoice.query()
.onBuild((builder: any) => { .onBuild((builder: any) => {
builder.whereIn('id', invoicesIds); builder.whereIn('id', invoicesIds);
return builder; return builder;
@@ -314,9 +329,13 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {ISaleInvoice} saleInvoice * @param {ISaleInvoice} saleInvoice
* @return {Promise} * @return {Promise}
*/ */
static async scheduleComputeInvoiceItemsCost(saleInvoiceId: number, override?: boolean) { async scheduleComputeInvoiceItemsCost(
const saleInvoice: ISaleInvoice = await SaleInvoice.tenant() tenantId: number,
.query() saleInvoiceId: number,
override?: boolean
) {
const { SaleInvoice } = this.tenancy.models(tenantId);
const saleInvoice: ISaleInvoice = await SaleInvoice.query()
.findById(saleInvoiceId) .findById(saleInvoiceId)
.withGraphFetched('entries.item'); .withGraphFetched('entries.item');
@@ -326,9 +345,10 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
.uniq().value(); .uniq().value();
if (inventoryItemsIds.length === 0) { if (inventoryItemsIds.length === 0) {
await this.writeNonInventoryInvoiceJournals(saleInvoice, override); await this.writeNonInventoryInvoiceJournals(tenantId, saleInvoice, override);
} else { } else {
await this.scheduleComputeItemsCost( await this.scheduleComputeItemsCost(
tenantId,
inventoryItemsIds, inventoryItemsIds,
saleInvoice.invoice_date, saleInvoice.invoice_date,
); );
@@ -339,13 +359,14 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* Writes the sale invoice journal entries. * Writes the sale invoice journal entries.
* @param {SaleInvoice} saleInvoice - * @param {SaleInvoice} saleInvoice -
*/ */
static async writeNonInventoryInvoiceJournals(saleInvoice: ISaleInvoice, override: boolean) { async writeNonInventoryInvoiceJournals(tenantId: number, saleInvoice: ISaleInvoice, override: boolean) {
const accountsDepGraph = await Account.tenant().depGraph().query(); const { Account, AccountTransaction } = this.tenancy.models(tenantId);
const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph); const journal = new JournalPoster(accountsDepGraph);
if (override) { if (override) {
const oldTransactions = await AccountTransaction.tenant() const oldTransactions = await AccountTransaction.query()
.query()
.where('reference_type', 'SaleInvoice') .where('reference_type', 'SaleInvoice')
.where('reference_id', saleInvoice.id) .where('reference_id', saleInvoice.id)
.withGraphFetched('account.type'); .withGraphFetched('account.type');

View File

@@ -1,17 +1,18 @@
import { Container } from 'typedi'; import { Container, Service, Inject } from 'typedi';
import {
SaleInvoice,
Account,
AccountTransaction,
Item,
} from '@/models';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from '@/services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry'; import JournalEntry from '@/services/Accounting/JournalEntry';
import InventoryService from '@/services/Inventory/Inventory'; import InventoryService from '@/services/Inventory/Inventory';
import { ISaleInvoice, IItemEntry, IItem } from '@/interfaces'; import TenancyService from '@/services/Tenancy/TenancyService';
import { ISaleInvoice } from '../../interfaces'; import { ISaleInvoice, IItemEntry } from '@/interfaces';
@Service()
export default class SaleInvoicesCost { export default class SaleInvoicesCost {
@Inject()
inventoryService: InventoryService;
@Inject()
tenancy: TenancyService;
/** /**
* Schedule sale invoice re-compute based on the item * Schedule sale invoice re-compute based on the item
* cost method and starting date. * cost method and starting date.
@@ -19,11 +20,16 @@ export default class SaleInvoicesCost {
* @param {Date} startingDate - Starting compute cost date. * @param {Date} startingDate - Starting compute cost date.
* @return {Promise<Agenda>} * @return {Promise<Agenda>}
*/ */
static async scheduleComputeItemsCost(inventoryItemsIds: number[], startingDate: Date) { async scheduleComputeItemsCost(
tenantId: number,
inventoryItemsIds: number[],
startingDate: Date
) {
const asyncOpers: Promise<[]>[] = []; const asyncOpers: Promise<[]>[] = [];
inventoryItemsIds.forEach((inventoryItemId: number) => { inventoryItemsIds.forEach((inventoryItemId: number) => {
const oper: Promise<[]> = InventoryService.scheduleComputeItemCost( const oper: Promise<[]> = this.inventoryService.scheduleComputeItemCost(
tenantId,
inventoryItemId, inventoryItemId,
startingDate, startingDate,
); );
@@ -37,21 +43,22 @@ export default class SaleInvoicesCost {
* @param {Date} startingDate * @param {Date} startingDate
* @return {Promise<agenda>} * @return {Promise<agenda>}
*/ */
static scheduleWriteJournalEntries(startingDate?: Date) { scheduleWriteJournalEntries(tenantId: number, startingDate?: Date) {
const agenda = Container.get('agenda'); const agenda = Container.get('agenda');
return agenda.schedule('in 3 seconds', 'rewrite-invoices-journal-entries', { return agenda.schedule('in 3 seconds', 'rewrite-invoices-journal-entries', {
startingDate, startingDate, tenantId,
}); });
} }
/** /**
* Writes journal entries from sales invoices. * Writes journal entries from sales invoices.
* @param {number} tenantId - The tenant id.
* @param {Date} startingDate * @param {Date} startingDate
* @param {boolean} override * @param {boolean} override
*/ */
static async writeJournalEntries(startingDate: Date, override: boolean) { async writeJournalEntries(tenantId: number, startingDate: Date, override: boolean) {
const salesInvoices = await SaleInvoice.tenant() const { AccountTransaction, SaleInvoice, Account } = this.tenancy.models(tenantId);
.query() const salesInvoices = await SaleInvoice.query()
.onBuild((builder: any) => { .onBuild((builder: any) => {
builder.modify('filterDateRange', startingDate); builder.modify('filterDateRange', startingDate);
builder.orderBy('invoice_date', 'ASC'); builder.orderBy('invoice_date', 'ASC');
@@ -59,12 +66,11 @@ export default class SaleInvoicesCost {
builder.withGraphFetched('entries.item') builder.withGraphFetched('entries.item')
builder.withGraphFetched('costTransactions(groupedEntriesCost)'); builder.withGraphFetched('costTransactions(groupedEntriesCost)');
}); });
const accountsDepGraph = await Account.tenant().depGraph().query(); const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph); const journal = new JournalPoster(accountsDepGraph);
if (override) { if (override) {
const oldTransactions = await AccountTransaction.tenant() const oldTransactions = await AccountTransaction.query()
.query()
.whereIn('reference_type', ['SaleInvoice']) .whereIn('reference_type', ['SaleInvoice'])
.onBuild((builder: any) => { .onBuild((builder: any) => {
builder.modify('filterDateRange', startingDate); builder.modify('filterDateRange', startingDate);
@@ -90,7 +96,7 @@ export default class SaleInvoicesCost {
* @param {ISaleInvoice} saleInvoice * @param {ISaleInvoice} saleInvoice
* @param {JournalPoster} journal * @param {JournalPoster} journal
*/ */
static saleInvoiceJournal(saleInvoice: ISaleInvoice, journal: JournalPoster) { saleInvoiceJournal(saleInvoice: ISaleInvoice, journal: JournalPoster) {
let inventoryTotal: number = 0; let inventoryTotal: number = 0;
const receivableAccount = { id: 10 }; const receivableAccount = { id: 10 };
const commonEntry = { const commonEntry = {

View File

@@ -1,36 +1,43 @@
import { omit, difference, sumBy } from 'lodash'; import { omit, difference, sumBy } from 'lodash';
import { import { Service, Inject } from 'typedi';
SaleReceipt,
Account,
ItemEntry,
} from '@/models';
import JournalPoster from '@/services/Accounting/JournalPoster';
import JournalPosterService from '@/services/Sales/JournalPosterService'; import JournalPosterService from '@/services/Sales/JournalPosterService';
import HasItemEntries from '@/services/Sales/HasItemsEntries'; import HasItemEntries from '@/services/Sales/HasItemsEntries';
import TenancyService from '@/services/Tenancy/TenancyService';
import { formatDateFields } from '@/utils'; import { formatDateFields } from '@/utils';
export default class SalesReceipt { @Service()
export default class SalesReceiptService {
@Inject()
tenancy: TenancyService;
@Inject()
journalService: JournalPosterService;
@Inject()
itemsEntriesService: HasItemEntries;
/** /**
* Creates a new sale receipt with associated entries. * Creates a new sale receipt with associated entries.
* @async * @async
* @param {ISaleReceipt} saleReceipt * @param {ISaleReceipt} saleReceipt
* @return {Object} * @return {Object}
*/ */
static async createSaleReceipt(saleReceiptDTO: any) { async createSaleReceipt(tenantId: number, saleReceiptDTO: any) {
const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId);
const amount = sumBy(saleReceiptDTO.entries, e => ItemEntry.calcAmount(e));
const saleReceipt = { const saleReceipt = {
amount: sumBy(saleReceiptDTO.entries, 'amount'); amount,
...formatDateFields(saleReceiptDTO, ['receipt_date']) ...formatDateFields(saleReceiptDTO, ['receipt_date'])
}; };
const storedSaleReceipt = await SaleReceipt.tenant() const storedSaleReceipt = await SaleReceipt.query()
.query()
.insert({ .insert({
...omit(saleReceipt, ['entries']), ...omit(saleReceipt, ['entries']),
}); });
const storeSaleReceiptEntriesOpers: Array<any> = []; const storeSaleReceiptEntriesOpers: Array<any> = [];
saleReceipt.entries.forEach((entry: any) => { saleReceipt.entries.forEach((entry: any) => {
const oper = ItemEntry.tenant() const oper = ItemEntry.query()
.query()
.insert({ .insert({
reference_type: 'SaleReceipt', reference_type: 'SaleReceipt',
reference_id: storedSaleReceipt.id, reference_id: storedSaleReceipt.id,
@@ -48,25 +55,30 @@ export default class SalesReceipt {
* @param {ISaleReceipt} saleReceipt * @param {ISaleReceipt} saleReceipt
* @return {void} * @return {void}
*/ */
static async editSaleReceipt(saleReceiptId: number, saleReceiptDTO: any) { async editSaleReceipt(tenantId: number, saleReceiptId: number, saleReceiptDTO: any) {
const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId);
const amount = sumBy(saleReceiptDTO.entries, e => ItemEntry.calcAmount(e));
const saleReceipt = { const saleReceipt = {
amount: sumBy(saleReceiptDTO.entries, 'amount'), amount,
...formatDateFields(saleReceiptDTO, ['receipt_date']) ...formatDateFields(saleReceiptDTO, ['receipt_date'])
}; };
const updatedSaleReceipt = await SaleReceipt.tenant() const updatedSaleReceipt = await SaleReceipt.query()
.query()
.where('id', saleReceiptId) .where('id', saleReceiptId)
.update({ .update({
...omit(saleReceipt, ['entries']), ...omit(saleReceipt, ['entries']),
}); });
const storedSaleReceiptEntries = await ItemEntry.tenant() const storedSaleReceiptEntries = await ItemEntry.query()
.query()
.where('reference_id', saleReceiptId) .where('reference_id', saleReceiptId)
.where('reference_type', 'SaleReceipt'); .where('reference_type', 'SaleReceipt');
// Patch sale receipt items entries. // Patch sale receipt items entries.
const patchItemsEntries = HasItemEntries.patchItemsEntries( const patchItemsEntries = this.itemsEntriesService.patchItemsEntries(
saleReceipt.entries, storedSaleReceiptEntries, 'SaleReceipt', saleReceiptId, tenantId,
saleReceipt.entries,
storedSaleReceiptEntries,
'SaleReceipt',
saleReceiptId,
); );
return Promise.all([patchItemsEntries]); return Promise.all([patchItemsEntries]);
} }
@@ -76,20 +88,20 @@ export default class SalesReceipt {
* @param {Integer} saleReceiptId * @param {Integer} saleReceiptId
* @return {void} * @return {void}
*/ */
static async deleteSaleReceipt(saleReceiptId: number) { async deleteSaleReceipt(tenantId: number, saleReceiptId: number) {
const deleteSaleReceiptOper = SaleReceipt.tenant() const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId);
.query() const deleteSaleReceiptOper = SaleReceipt.query()
.where('id', saleReceiptId) .where('id', saleReceiptId)
.delete(); .delete();
const deleteItemsEntriesOper = ItemEntry.tenant() const deleteItemsEntriesOper = ItemEntry.query()
.query()
.where('reference_id', saleReceiptId) .where('reference_id', saleReceiptId)
.where('reference_type', 'SaleReceipt') .where('reference_type', 'SaleReceipt')
.delete(); .delete();
// Delete all associated journal transactions to payment receive transaction. // Delete all associated journal transactions to payment receive transaction.
const deleteTransactionsOper = JournalPosterService.deleteJournalTransactions( const deleteTransactionsOper = this.journalService.deleteJournalTransactions(
tenantId,
saleReceiptId, saleReceiptId,
'SaleReceipt' 'SaleReceipt'
); );
@@ -105,9 +117,9 @@ export default class SalesReceipt {
* @param {Integer} saleReceiptId * @param {Integer} saleReceiptId
* @returns {Boolean} * @returns {Boolean}
*/ */
static async isSaleReceiptExists(saleReceiptId: number) { async isSaleReceiptExists(tenantId: number, saleReceiptId: number) {
const foundSaleReceipt = await SaleReceipt.tenant() const { SaleReceipt } = this.tenancy.models(tenantId);
.query() const foundSaleReceipt = await SaleReceipt.query()
.where('id', saleReceiptId); .where('id', saleReceiptId);
return foundSaleReceipt.length !== 0; return foundSaleReceipt.length !== 0;
} }
@@ -117,13 +129,13 @@ export default class SalesReceipt {
* @param {Integer} saleReceiptId * @param {Integer} saleReceiptId
* @param {ISaleReceipt} saleReceipt * @param {ISaleReceipt} saleReceipt
*/ */
static async isSaleReceiptEntriesIDsExists(saleReceiptId: number, saleReceipt: any) { async isSaleReceiptEntriesIDsExists(tenantId: number, saleReceiptId: number, saleReceipt: any) {
const { ItemEntry } = this.tenancy.models(tenantId);
const entriesIDs = saleReceipt.entries const entriesIDs = saleReceipt.entries
.filter((e) => e.id) .filter((e) => e.id)
.map((e) => e.id); .map((e) => e.id);
const storedEntries = await ItemEntry.tenant() const storedEntries = await ItemEntry.query()
.query()
.whereIn('id', entriesIDs) .whereIn('id', entriesIDs)
.where('reference_id', saleReceiptId) .where('reference_id', saleReceiptId)
.where('reference_type', 'SaleReceipt'); .where('reference_type', 'SaleReceipt');
@@ -140,21 +152,12 @@ export default class SalesReceipt {
* Retrieve sale receipt with associated entries. * Retrieve sale receipt with associated entries.
* @param {Integer} saleReceiptId * @param {Integer} saleReceiptId
*/ */
static async getSaleReceiptWithEntries(saleReceiptId: number) { async getSaleReceiptWithEntries(tenantId: number, saleReceiptId: number) {
const saleReceipt = await SaleReceipt.tenant().query() const { SaleReceipt } = this.tenancy.models(tenantId);
const saleReceipt = await SaleReceipt.query()
.where('id', saleReceiptId) .where('id', saleReceiptId)
.withGraphFetched('entries'); .withGraphFetched('entries');
return saleReceipt; return saleReceipt;
} }
/**
* Records journal transactions for sale receipt.
* @param {ISaleReceipt} saleReceipt
* @return {Promise}
*/
static async _recordJournalTransactions(saleReceipt: any) {
const accountsDepGraph = await Account.tenant().depGraph().query();
const journalPoster = new JournalPoster(accountsDepGraph);
}
} }

View File

@@ -0,0 +1,21 @@
import { Container } from 'typedi';
export default class HasTenancyService {
/**
* Retrieve the given tenant container.
* @param {number} tenantId
* @return {Container}
*/
tenantContainer(tenantId: number) {
return Container.of(`tenant-${tenantId}`);
}
/**
* Retrieve models of the givne tenant id.
* @param {number} tenantId - The tenant id.
*/
models(tenantId: number) {
console.log(tenantId);
return this.tenantContainer(tenantId).get('models');
}
}

View File

@@ -3,7 +3,7 @@ import moment from 'moment';
import SystemModel from '@/system/models/SystemModel'; import SystemModel from '@/system/models/SystemModel';
import { IVouchersFilter } from '@/interfaces'; import { IVouchersFilter } from '@/interfaces';
export default class Voucher extends mixin(SystemModel) { export default class Voucher extends SystemModel {
/** /**
* Table name. * Table name.
*/ */
@@ -40,7 +40,7 @@ export default class Voucher extends mixin(SystemModel) {
}, },
// Filters vouchers list. // Filters vouchers list.
filter(builder, vouchersFilter: IVouchersFilter) { filter(builder, vouchersFilter) {
if (vouchersFilter.active) { if (vouchersFilter.active) {
builder.modify('filterActiveVoucher') builder.modify('filterActiveVoucher')
} }
@@ -80,7 +80,7 @@ export default class Voucher extends mixin(SystemModel) {
* @param {string} voucherCode * @param {string} voucherCode
* @return {Promise} * @return {Promise}
*/ */
static deleteVoucher(voucherCode: string, viaAttribute: string = 'voucher_code') { static deleteVoucher(voucherCode, viaAttribute = 'voucher_code') {
return this.query() return this.query()
.where(viaAttribute, voucherCode) .where(viaAttribute, voucherCode)
.delete(); .delete();
@@ -91,7 +91,7 @@ export default class Voucher extends mixin(SystemModel) {
* @param {string} voucherCode * @param {string} voucherCode
* @return {Promise} * @return {Promise}
*/ */
static markVoucherAsDisabled(voucherCode: string, viaAttribute: string = 'voucher_code') { static markVoucherAsDisabled(voucherCode, viaAttribute = 'voucher_code') {
return this.query() return this.query()
.where(viaAttribute, voucherCode) .where(viaAttribute, voucherCode)
.patch({ .patch({
@@ -104,7 +104,7 @@ export default class Voucher extends mixin(SystemModel) {
* Marks the given voucher code as sent on the storage. * Marks the given voucher code as sent on the storage.
* @param {string} voucherCode * @param {string} voucherCode
*/ */
static markVoucherAsSent(voucherCode: string, viaAttribute: string = 'voucher_code') { static markVoucherAsSent(voucherCode, viaAttribute = 'voucher_code') {
return this.query() return this.query()
.where(viaAttribute, voucherCode) .where(viaAttribute, voucherCode)
.patch({ .patch({
@@ -118,7 +118,7 @@ export default class Voucher extends mixin(SystemModel) {
* @param {string} voucherCode * @param {string} voucherCode
* @return {Promise} * @return {Promise}
*/ */
static markVoucherAsUsed(voucherCode: string, viaAttribute: string = 'voucher_code') { static markVoucherAsUsed(voucherCode, viaAttribute = 'voucher_code') {
return this.query() return this.query()
.where(viaAttribute, voucherCode) .where(viaAttribute, voucherCode)
.patch({ .patch({

View File

@@ -1,4 +1,5 @@
import BaseModel from '@/models/Model'; import BaseModel from '@/models/Model';
export default class SystemModel extends BaseModel{ export default class SystemModel extends BaseModel{
} }

View File

@@ -144,7 +144,7 @@ function applyMixins(derivedCtor, baseCtors) {
}); });
} }
const formatDateFields = (inputDTO, fields, format = 'YYYY-DD-MM') => { const formatDateFields = (inputDTO, fields, format = 'YYYY-MM-DD') => {
const _inputDTO = { ...inputDTO }; const _inputDTO = { ...inputDTO };
fields.forEach((field) => { fields.forEach((field) => {

View File

@@ -10,5 +10,10 @@
"esModuleInterop": true, "esModuleInterop": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"baseUrl": "./",
"paths": {
"@": ["src/"],
"~": ["tests/"]
}
} }
} }