mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
add server to monorepo.
This commit is contained in:
257
packages/server/src/services/Inventory/InventoryAverageCost.ts
Normal file
257
packages/server/src/services/Inventory/InventoryAverageCost.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
import { pick } from 'lodash';
|
||||
import { Knex } from 'knex';
|
||||
import { IInventoryTransaction } from '@/interfaces';
|
||||
import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod';
|
||||
|
||||
export default class InventoryAverageCostMethod
|
||||
extends InventoryCostMethod
|
||||
implements IInventoryCostMethod
|
||||
{
|
||||
startingDate: Date;
|
||||
itemId: number;
|
||||
costTransactions: any[];
|
||||
trx: Knex.Transaction;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {number} tenantId - The given tenant id.
|
||||
* @param {Date} startingDate -
|
||||
* @param {number} itemId - The given inventory item id.
|
||||
*/
|
||||
constructor(
|
||||
tenantId: number,
|
||||
startingDate: Date,
|
||||
itemId: number,
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
super(tenantId, startingDate, itemId);
|
||||
|
||||
this.trx = trx;
|
||||
this.startingDate = startingDate;
|
||||
this.itemId = itemId;
|
||||
this.costTransactions = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes items costs from the given date using average cost method.
|
||||
* ----------
|
||||
* - Calculate the items average cost in the given date.
|
||||
* - Remove the journal entries that associated to the inventory transacions
|
||||
* after the given date.
|
||||
* - Re-compute the inventory transactions and re-write the journal entries
|
||||
* after the given date.
|
||||
* ----------
|
||||
* @async
|
||||
* @param {Date} startingDate
|
||||
* @param {number} referenceId
|
||||
* @param {string} referenceType
|
||||
*/
|
||||
public async computeItemCost() {
|
||||
const { InventoryTransaction } = this.tenantModels;
|
||||
const { averageCost, openingQuantity, openingCost } =
|
||||
await this.getOpeningAvaregeCost(this.startingDate, this.itemId);
|
||||
|
||||
const afterInvTransactions: IInventoryTransaction[] =
|
||||
await InventoryTransaction.query()
|
||||
.modify('filterDateRange', this.startingDate)
|
||||
.orderBy('date', 'ASC')
|
||||
.orderByRaw("FIELD(direction, 'IN', 'OUT')")
|
||||
.orderBy('createdAt', 'ASC')
|
||||
.where('item_id', this.itemId)
|
||||
.withGraphFetched('item');
|
||||
|
||||
// Tracking inventroy transactions and retrieve cost transactions based on
|
||||
// average rate cost method.
|
||||
const costTransactions = this.trackingCostTransactions(
|
||||
afterInvTransactions,
|
||||
openingQuantity,
|
||||
openingCost
|
||||
);
|
||||
// Revert the inveout out lots transactions
|
||||
await this.revertTheInventoryOutLotTrans();
|
||||
|
||||
// Store inventory lots cost transactions.
|
||||
await this.storeInventoryLotsCost(costTransactions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items Avarege cost from specific date from inventory transactions.
|
||||
* @async
|
||||
* @param {Date} closingDate
|
||||
* @return {number}
|
||||
*/
|
||||
public async getOpeningAvaregeCost(closingDate: Date, itemId: number) {
|
||||
const { InventoryCostLotTracker } = this.tenantModels;
|
||||
|
||||
const commonBuilder = (builder: any) => {
|
||||
if (closingDate) {
|
||||
builder.where('date', '<', closingDate);
|
||||
}
|
||||
builder.where('item_id', itemId);
|
||||
builder.sum('rate as rate');
|
||||
builder.sum('quantity as quantity');
|
||||
builder.sum('cost as cost');
|
||||
builder.first();
|
||||
};
|
||||
// Calculates the total inventory total quantity and rate `IN` transactions.
|
||||
const inInvSumationOper: Promise<any> = InventoryCostLotTracker.query()
|
||||
.onBuild(commonBuilder)
|
||||
.where('direction', 'IN');
|
||||
|
||||
// Calculates the total inventory total quantity and rate `OUT` transactions.
|
||||
const outInvSumationOper: Promise<any> = InventoryCostLotTracker.query()
|
||||
.onBuild(commonBuilder)
|
||||
.where('direction', 'OUT');
|
||||
|
||||
const [inInvSumation, outInvSumation] = await Promise.all([
|
||||
inInvSumationOper,
|
||||
outInvSumationOper,
|
||||
]);
|
||||
return this.computeItemAverageCost(
|
||||
inInvSumation?.cost || 0,
|
||||
inInvSumation?.quantity || 0,
|
||||
outInvSumation?.cost || 0,
|
||||
outInvSumation?.quantity || 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the item average cost.
|
||||
* @static
|
||||
* @param {number} quantityIn
|
||||
* @param {number} rateIn
|
||||
* @param {number} quantityOut
|
||||
* @param {number} rateOut
|
||||
*/
|
||||
public computeItemAverageCost(
|
||||
totalCostIn: number,
|
||||
totalQuantityIn: number,
|
||||
|
||||
totalCostOut: number,
|
||||
totalQuantityOut: number
|
||||
) {
|
||||
const openingCost = totalCostIn - totalCostOut;
|
||||
const openingQuantity = totalQuantityIn - totalQuantityOut;
|
||||
|
||||
const averageCost = openingQuantity ? openingCost / openingQuantity : 0;
|
||||
|
||||
return { averageCost, openingCost, openingQuantity };
|
||||
}
|
||||
|
||||
private getCost(rate: number, quantity: number) {
|
||||
return quantity ? rate * quantity : rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the journal entries from specific item inventory transactions.
|
||||
* @param {IInventoryTransaction[]} invTransactions
|
||||
* @param {number} openingAverageCost
|
||||
* @param {string} referenceType
|
||||
* @param {number} referenceId
|
||||
* @param {JournalCommand} journalCommands
|
||||
*/
|
||||
public trackingCostTransactions(
|
||||
invTransactions: IInventoryTransaction[],
|
||||
openingQuantity: number = 0,
|
||||
openingCost: number = 0
|
||||
) {
|
||||
const costTransactions: any[] = [];
|
||||
|
||||
// Cumulative item quantity and cost. This will decrement after
|
||||
// each out transactions depends on its quantity and cost.
|
||||
let accQuantity: number = openingQuantity;
|
||||
let accCost: number = openingCost;
|
||||
|
||||
invTransactions.forEach((invTransaction: IInventoryTransaction) => {
|
||||
const commonEntry = {
|
||||
invTransId: invTransaction.id,
|
||||
...pick(invTransaction, [
|
||||
'date',
|
||||
'direction',
|
||||
'itemId',
|
||||
'quantity',
|
||||
'rate',
|
||||
'entryId',
|
||||
'transactionId',
|
||||
'transactionType',
|
||||
'createdAt',
|
||||
'costAccountId',
|
||||
'branchId',
|
||||
'warehouseId',
|
||||
]),
|
||||
inventoryTransactionId: invTransaction.id,
|
||||
};
|
||||
switch (invTransaction.direction) {
|
||||
case 'IN':
|
||||
const inCost = this.getCost(
|
||||
invTransaction.rate,
|
||||
invTransaction.quantity
|
||||
);
|
||||
// Increases the quantity and cost in `IN` inventory transactions.
|
||||
accQuantity += invTransaction.quantity;
|
||||
accCost += inCost;
|
||||
|
||||
costTransactions.push({
|
||||
...commonEntry,
|
||||
cost: inCost,
|
||||
});
|
||||
break;
|
||||
case 'OUT':
|
||||
// Average cost = Total cost / Total quantity
|
||||
const averageCost = accQuantity ? accCost / accQuantity : 0;
|
||||
|
||||
const quantity =
|
||||
accQuantity > 0
|
||||
? Math.min(invTransaction.quantity, accQuantity)
|
||||
: invTransaction.quantity;
|
||||
|
||||
// Cost = the transaction quantity * Average cost.
|
||||
const cost = this.getCost(averageCost, quantity);
|
||||
|
||||
// Revenue = transaction quanity * rate.
|
||||
// const revenue = quantity * invTransaction.rate;
|
||||
costTransactions.push({
|
||||
...commonEntry,
|
||||
quantity,
|
||||
cost,
|
||||
});
|
||||
accQuantity = Math.max(accQuantity - quantity, 0);
|
||||
accCost = Math.max(accCost - cost, 0);
|
||||
|
||||
if (invTransaction.quantity > quantity) {
|
||||
const remainingQuantity = Math.max(
|
||||
invTransaction.quantity - quantity,
|
||||
0
|
||||
);
|
||||
const remainingIncome = remainingQuantity * invTransaction.rate;
|
||||
|
||||
costTransactions.push({
|
||||
...commonEntry,
|
||||
quantity: remainingQuantity,
|
||||
cost: 0,
|
||||
});
|
||||
accQuantity = Math.max(accQuantity - remainingQuantity, 0);
|
||||
accCost = Math.max(accCost - remainingIncome, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
return costTransactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the inventory lots `OUT` transactions.
|
||||
* @param {Date} openingDate - Opening date.
|
||||
* @param {number} itemId - Item id.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async revertTheInventoryOutLotTrans(): Promise<void> {
|
||||
const { InventoryCostLotTracker } = this.tenantModels;
|
||||
|
||||
await InventoryCostLotTracker.query(this.trx)
|
||||
.modify('filterDateRange', this.startingDate)
|
||||
.orderBy('date', 'DESC')
|
||||
.where('item_id', this.itemId)
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user