mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
- feat: Optimize tenancy software architecture.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user