- 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

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

View File

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

View File

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

View File

@@ -1,28 +1,42 @@
import { omit } from 'lodash';
import { Inject } from 'typedi';
import TenancyService from '@/services/Tenancy/TenancyService';
import { IInventoryLotCost } from '@/interfaces';
import { InventoryLotCostTracker } from '@/models';
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.
* @param {IInventoryLotCost[]} costLotsTransactions
* @return {Promise[]}
*/
public storeInventoryLotsCost(costLotsTransactions: IInventoryLotCost[]): Promise<object> {
const { InventoryLotCostTracker } = this.tenantModels;
const opers: any = [];
costLotsTransactions.forEach((transaction: IInventoryLotCost) => {
if (transaction.lotTransId && transaction.decrement) {
const decrementOper = InventoryLotCostTracker.tenant()
.query()
const decrementOper = InventoryLotCostTracker.query()
.where('id', transaction.lotTransId)
.decrement('remaining', transaction.decrement);
opers.push(decrementOper);
} else if(!transaction.lotTransId) {
const operation = InventoryLotCostTracker.tenant().query()
.insert({
...omit(transaction, ['decrement', 'invTransId', 'lotTransId']),
});
const operation = InventoryLotCostTracker.query()
.insert({
...omit(transaction, ['decrement', 'invTransId', 'lotTransId']),
});
opers.push(operation);
}
});