mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 21:30:31 +00:00
feat: Re-compute the given items cost job.
feat: Optimize the architecture.
This commit is contained in:
135
server/src/services/Accounting/JournalCommands.ts
Normal file
135
server/src/services/Accounting/JournalCommands.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { sumBy, chain } from 'lodash';
|
||||
import JournalPoster from "./JournalPoster";
|
||||
import JournalEntry from "./JournalEntry";
|
||||
import { AccountTransaction } from '@/models';
|
||||
import { IInventoryTransaction } from '@/interfaces';
|
||||
import AccountsService from '../Accounts/AccountsService';
|
||||
import { IInventoryTransaction, IInventoryTransaction } from '../../interfaces';
|
||||
|
||||
interface IInventoryCostEntity {
|
||||
date: Date,
|
||||
|
||||
referenceType: string,
|
||||
referenceId: number,
|
||||
|
||||
costAccount: number,
|
||||
incomeAccount: number,
|
||||
inventoryAccount: number,
|
||||
|
||||
inventory: number,
|
||||
cost: number,
|
||||
income: number,
|
||||
};
|
||||
|
||||
export default class JournalCommands{
|
||||
journal: JournalPoster;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {JournalPoster} journal -
|
||||
*/
|
||||
constructor(journal: JournalPoster) {
|
||||
this.journal = journal;
|
||||
Object.assign(this, arguments[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes and revert accounts balance journal entries that associated
|
||||
* to the given inventory transactions.
|
||||
* @param {IInventoryTransaction[]} inventoryTransactions
|
||||
* @param {Journal} journal
|
||||
*/
|
||||
revertEntriesFromInventoryTransactions(inventoryTransactions: IInventoryTransaction[]) {
|
||||
const groupedInvTransactions = chain(inventoryTransactions)
|
||||
.groupBy((invTransaction: IInventoryTransaction) => invTransaction.transactionType)
|
||||
.map((groupedTrans: IInventoryTransaction[], transType: string) => [groupedTrans, transType])
|
||||
.value();
|
||||
|
||||
console.log(groupedInvTransactions);
|
||||
|
||||
return Promise.all(
|
||||
groupedInvTransactions.map(async (grouped: [IInventoryTransaction[], string]) => {
|
||||
const [invTransGroup, referenceType] = grouped;
|
||||
const referencesIds = invTransGroup.map((trans: IInventoryTransaction) => trans.transactionId);
|
||||
|
||||
const _transactions = await AccountTransaction.tenant()
|
||||
.query()
|
||||
.where('reference_type', referenceType)
|
||||
.whereIn('reference_id', referencesIds)
|
||||
.withGraphFetched('account.type');
|
||||
|
||||
console.log(_transactions, referencesIds);
|
||||
|
||||
if (_transactions.length > 0) {
|
||||
this.journal.loadEntries(_transactions);
|
||||
this.journal.removeEntries(_transactions.map((t: any) => t.id));
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} referenceType -
|
||||
* @param {number} referenceId -
|
||||
* @param {ISaleInvoice[]} sales -
|
||||
*/
|
||||
public async inventoryEntries(
|
||||
transactions: IInventoryCostEntity[],
|
||||
) {
|
||||
const receivableAccount = { id: 10 };
|
||||
const payableAccount = { id: 11 };
|
||||
|
||||
transactions.forEach((sale: IInventoryCostEntity) => {
|
||||
const commonEntry = {
|
||||
date: sale.date,
|
||||
referenceId: sale.referenceId,
|
||||
referenceType: sale.referenceType,
|
||||
};
|
||||
switch(sale.referenceType) {
|
||||
case 'Bill':
|
||||
const inventoryDebit: JournalEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
debit: sale.inventory,
|
||||
account: sale.inventoryAccount,
|
||||
});
|
||||
const payableEntry: JournalEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
credit: sale.inventory,
|
||||
account: payableAccount.id,
|
||||
});
|
||||
this.journal.debit(inventoryDebit);
|
||||
this.journal.credit(payableEntry);
|
||||
break;
|
||||
case 'SaleInvoice':
|
||||
const receivableEntry: JournalEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
debit: sale.income,
|
||||
account: receivableAccount.id,
|
||||
});
|
||||
const incomeEntry: JournalEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
credit: sale.income,
|
||||
account: sale.incomeAccount,
|
||||
});
|
||||
// Cost journal transaction.
|
||||
const costEntry: JournalEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
debit: sale.cost,
|
||||
account: sale.costAccount,
|
||||
});
|
||||
const inventoryCredit: JournalEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
credit: sale.cost,
|
||||
account: sale.inventoryAccount,
|
||||
});
|
||||
this.journal.debit(receivableEntry);
|
||||
this.journal.debit(costEntry);
|
||||
|
||||
this.journal.credit(incomeEntry);
|
||||
this.journal.credit(inventoryCredit);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -248,6 +248,17 @@ export default class JournalPoster {
|
||||
this.deletedEntriesIds.push(...removeEntries.map((entry) => entry.id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert the given transactions.
|
||||
* @param {*} entries
|
||||
*/
|
||||
removeTransactions(entries) {
|
||||
this.loadEntries(entries);
|
||||
|
||||
|
||||
this.deletedEntriesIds.push(...entriesIDsShouldDel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the stacked entries.
|
||||
*/
|
||||
|
||||
@@ -17,8 +17,6 @@ export default class AccountsService {
|
||||
.where('account_type_id', accountType.id)
|
||||
.first();
|
||||
|
||||
console.log(account);
|
||||
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,36 @@
|
||||
import { InventoryTransaction, Item } from '@/models';
|
||||
import InventoryCostLotTracker from './InventoryCostLotTracker';
|
||||
import { IInventoryTransaction, IInventoryLotCost } from '@/interfaces/InventoryTransaction';
|
||||
import { IInventoryLotCost, IInventoryLotCost } from '../../interfaces/InventoryTransaction';
|
||||
import { pick } from 'lodash';
|
||||
import {
|
||||
InventoryTransaction,
|
||||
Item
|
||||
} from '@/models';
|
||||
import InventoryAverageCost from '@/services/Inventory/InventoryAverageCost';
|
||||
import InventoryCostLotTracker from '@/services/Inventory/InventoryCostLotTracker';
|
||||
|
||||
type TCostMethod = 'FIFO' | 'LIFO' | 'AVG';
|
||||
|
||||
export default class InventoryService {
|
||||
/**
|
||||
* 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 {Date} fromDate
|
||||
* @param {number} itemId
|
||||
*/
|
||||
static async computeItemCost(fromDate: Date, itemId: number) {
|
||||
const costMethod: TCostMethod = 'FIFO';
|
||||
let costMethodComputer: IInventoryCostMethod;
|
||||
|
||||
switch(costMethod) {
|
||||
case 'FIFO':
|
||||
case 'LIFO':
|
||||
costMethodComputer = new InventoryCostLotTracker(fromDate, itemId);
|
||||
break;
|
||||
case 'AVG':
|
||||
costMethodComputer = new InventoryAverageCost(fromDate, itemId);
|
||||
break
|
||||
}
|
||||
await costMethodComputer.initialize();
|
||||
await costMethodComputer.computeItemCost()
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the inventory transactions.
|
||||
* @param {Bill} bill
|
||||
@@ -15,6 +41,7 @@ export default class InventoryService {
|
||||
date: Date,
|
||||
transactionType: string,
|
||||
transactionId: number,
|
||||
direction: string,
|
||||
) {
|
||||
const storedOpers: any = [];
|
||||
const entriesItemsIds = entries.map((e: any) => e.item_id);
|
||||
@@ -23,20 +50,19 @@ export default class InventoryService {
|
||||
.whereIn('id', entriesItemsIds)
|
||||
.where('type', 'inventory');
|
||||
|
||||
const inventoryItemsIds = inventoryItems.map((i) => i.id);
|
||||
const inventoryItemsIds = inventoryItems.map((i: any) => i.id);
|
||||
|
||||
// Filter the bill entries that have inventory items.
|
||||
const inventoryEntries = entries.filter(
|
||||
(entry) => inventoryItemsIds.indexOf(entry.item_id) !== -1
|
||||
(entry: any) => inventoryItemsIds.indexOf(entry.item_id) !== -1
|
||||
);
|
||||
inventoryEntries.forEach((entry: any) => {
|
||||
const oper = InventoryTransaction.tenant().query().insert({
|
||||
date,
|
||||
|
||||
direction,
|
||||
item_id: entry.item_id,
|
||||
quantity: entry.quantity,
|
||||
rate: entry.rate,
|
||||
|
||||
transaction_type: transactionType,
|
||||
transaction_id: transactionId,
|
||||
});
|
||||
@@ -64,86 +90,4 @@ export default class InventoryService {
|
||||
revertInventoryLotsCost(fromDate?: Date) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the journal entries transactions.
|
||||
* @param {IInventoryLotCost[]} inventoryTransactions -
|
||||
*
|
||||
*/
|
||||
static async recordJournalEntries(inventoryLots: IInventoryLotCost[]) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracking the given inventory transactions to lots costs transactions.
|
||||
* @param {IInventoryTransaction[]} inventoryTransactions - Inventory transactions.
|
||||
* @return {IInventoryLotCost[]}
|
||||
*/
|
||||
static async trackingInventoryLotsCost(inventoryTransactions: IInventoryTransaction[]) {
|
||||
// Collect cost lots transactions to insert them to the storage in bulk.
|
||||
const costLotsTransactions: IInventoryLotCost[] = [];
|
||||
|
||||
// Collect inventory transactions by item id.
|
||||
const inventoryByItem: any = {};
|
||||
// Collection `IN` inventory tranaction by transaction id.
|
||||
const inventoryINTrans: any = {};
|
||||
|
||||
inventoryTransactions.forEach((transaction: IInventoryTransaction) => {
|
||||
const { itemId, id } = transaction;
|
||||
(inventoryByItem[itemId] || (inventoryByItem[itemId] = []));
|
||||
|
||||
const commonLotTransaction: IInventoryLotCost = {
|
||||
...pick(transaction, [
|
||||
'date', 'rate', 'itemId', 'quantity',
|
||||
'direction', 'transactionType', 'transactionId',
|
||||
]),
|
||||
};
|
||||
// Record inventory `IN` cost lot transaction.
|
||||
if (transaction.direction === 'IN') {
|
||||
inventoryByItem[itemId].push(id);
|
||||
inventoryINTrans[id] = {
|
||||
...commonLotTransaction,
|
||||
remaining: commonLotTransaction.quantity,
|
||||
};
|
||||
costLotsTransactions.push(inventoryINTrans[id]);
|
||||
|
||||
// Record inventory 'OUT' cost lots from 'IN' transactions.
|
||||
} else if (transaction.direction === 'OUT') {
|
||||
let invRemaining = transaction.quantity;
|
||||
|
||||
inventoryByItem?.[itemId]?.forEach((
|
||||
_invTransactionId: number,
|
||||
index: number,
|
||||
) => {
|
||||
const _invINTransaction = inventoryINTrans[_invTransactionId];
|
||||
|
||||
// Detarmines the 'OUT' lot tranasctions whether bigger than 'IN' remaining transaction.
|
||||
const biggerThanRemaining = (_invINTransaction.remaining - transaction.quantity) > 0;
|
||||
const decrement = (biggerThanRemaining) ? transaction.quantity : _invINTransaction.remaining;
|
||||
|
||||
_invINTransaction.remaining = Math.max(
|
||||
_invINTransaction.remaining - decrement, 0,
|
||||
);
|
||||
invRemaining = Math.max(invRemaining - decrement, 0);
|
||||
|
||||
costLotsTransactions.push({
|
||||
...commonLotTransaction,
|
||||
quantity: decrement,
|
||||
});
|
||||
// Pop the 'IN' lots that has zero remaining.
|
||||
if (_invINTransaction.remaining === 0) {
|
||||
inventoryByItem?.[itemId].splice(index, 1);
|
||||
}
|
||||
});
|
||||
if (invRemaining > 0) {
|
||||
costLotsTransactions.push({
|
||||
...commonLotTransaction,
|
||||
quantity: invRemaining,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return costLotsTransactions;
|
||||
}
|
||||
|
||||
}
|
||||
207
server/src/services/Inventory/InventoryAverageCost.ts
Normal file
207
server/src/services/Inventory/InventoryAverageCost.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import { Account, InventoryTransaction } from '@/models';
|
||||
import { IInventoryTransaction } from '@/interfaces';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import JournalCommands from '@/services/Accounting/JournalCommands';
|
||||
|
||||
export default class InventoryAverageCostMethod implements IInventoryCostMethod {
|
||||
journal: JournalPoster;
|
||||
journalCommands: JournalCommands;
|
||||
fromDate: Date;
|
||||
itemId: number;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {Date} fromDate -
|
||||
* @param {number} itemId -
|
||||
*/
|
||||
constructor(
|
||||
fromDate: Date,
|
||||
itemId: number,
|
||||
) {
|
||||
this.fromDate = fromDate;
|
||||
this.itemId = itemId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the inventory average cost method.
|
||||
* @async
|
||||
*/
|
||||
async initialize() {
|
||||
const accountsDepGraph = await Account.tenant().depGraph().query();
|
||||
|
||||
this.journal = new JournalPoster(accountsDepGraph);
|
||||
this.journalCommands = new JournalCommands(this.journal);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* ----------
|
||||
* @asycn
|
||||
* @param {Date} fromDate
|
||||
* @param {number} referenceId
|
||||
* @param {string} referenceType
|
||||
*/
|
||||
public async computeItemCost() {
|
||||
const openingAvgCost = await this.getOpeningAvaregeCost(this.fromDate, this.itemId);
|
||||
|
||||
// @todo from `invTransactions`.
|
||||
const afterInvTransactions: IInventoryTransaction[] = await InventoryTransaction
|
||||
.tenant()
|
||||
.query()
|
||||
.where('date', '>=', this.fromDate)
|
||||
// .where('direction', 'OUT')
|
||||
.orderBy('date', 'asc')
|
||||
.withGraphFetched('item');
|
||||
|
||||
// Remove and revert accounts balance journal entries from
|
||||
// inventory transactions.
|
||||
await this.journalCommands
|
||||
.revertEntriesFromInventoryTransactions(afterInvTransactions);
|
||||
|
||||
// Re-write the journal entries from the new recorded inventory transactions.
|
||||
await this.jEntriesFromItemInvTransactions(
|
||||
afterInvTransactions,
|
||||
openingAvgCost,
|
||||
);
|
||||
// Saves the new recorded journal entries to the storage.
|
||||
await Promise.all([
|
||||
this.journal.deleteEntries(),
|
||||
this.journal.saveEntries(),
|
||||
this.journal.saveBalance(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items Avarege cost from specific date from inventory transactions.
|
||||
* @static
|
||||
* @param {Date} fromDate
|
||||
* @return {number}
|
||||
*/
|
||||
public async getOpeningAvaregeCost(fromDate: Date, itemId: number) {
|
||||
const commonBuilder = (builder: any) => {
|
||||
if (fromDate) {
|
||||
builder.where('date', '<', fromDate);
|
||||
}
|
||||
builder.where('item_id', itemId);
|
||||
builder.groupBy('rate');
|
||||
builder.groupBy('quantity');
|
||||
builder.groupBy('item_id');
|
||||
builder.groupBy('direction');
|
||||
builder.sum('rate as rate');
|
||||
builder.sum('quantity as quantity');
|
||||
};
|
||||
// Calculates the total inventory total quantity and rate `IN` transactions.
|
||||
|
||||
// @todo total `IN` transactions.
|
||||
const inInvSumationOper: Promise<any> = InventoryTransaction.tenant()
|
||||
.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()
|
||||
.onBuild(commonBuilder)
|
||||
.where('direction', 'OUT')
|
||||
.first();
|
||||
|
||||
const [inInvSumation, outInvSumation] = await Promise.all([
|
||||
inInvSumationOper,
|
||||
outInvSumationOper,
|
||||
]);
|
||||
return this.computeItemAverageCost(
|
||||
inInvSumation?.quantity || 0,
|
||||
inInvSumation?.rate || 0,
|
||||
outInvSumation?.quantity || 0,
|
||||
outInvSumation?.rate || 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the item average cost.
|
||||
* @static
|
||||
* @param {number} quantityIn
|
||||
* @param {number} rateIn
|
||||
* @param {number} quantityOut
|
||||
* @param {number} rateOut
|
||||
*/
|
||||
public computeItemAverageCost(
|
||||
quantityIn: number,
|
||||
rateIn: number,
|
||||
|
||||
quantityOut: number,
|
||||
rateOut: number,
|
||||
) {
|
||||
const totalQuantity = (quantityIn - quantityOut);
|
||||
const totalRate = (rateIn - rateOut);
|
||||
const averageCost = (totalRate) ? (totalQuantity / totalRate) : totalQuantity;
|
||||
|
||||
return averageCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the journal entries from specific item inventory transactions.
|
||||
* @param {IInventoryTransaction[]} invTransactions
|
||||
* @param {number} openingAverageCost
|
||||
* @param {string} referenceType
|
||||
* @param {number} referenceId
|
||||
* @param {JournalCommand} journalCommands
|
||||
*/
|
||||
async jEntriesFromItemInvTransactions(
|
||||
invTransactions: IInventoryTransaction[],
|
||||
openingAverageCost: number,
|
||||
) {
|
||||
const transactions: any[] = [];
|
||||
let accQuantity: number = 0;
|
||||
let accCost: number = 0;
|
||||
|
||||
invTransactions.forEach((invTransaction: IInventoryTransaction) => {
|
||||
const commonEntry = {
|
||||
date: invTransaction.date,
|
||||
referenceType: invTransaction.transactionType,
|
||||
referenceId: invTransaction.transactionId,
|
||||
};
|
||||
switch(invTransaction.direction) {
|
||||
case 'IN':
|
||||
accQuantity += invTransaction.quantity;
|
||||
accCost += invTransaction.rate * invTransaction.quantity;
|
||||
|
||||
const inventory = invTransaction.quantity * invTransaction.rate;
|
||||
|
||||
transactions.push({
|
||||
...commonEntry,
|
||||
inventory,
|
||||
inventoryAccount: invTransaction.item.inventoryAccountId,
|
||||
});
|
||||
break;
|
||||
case 'OUT':
|
||||
const income = invTransaction.quantity * invTransaction.rate;
|
||||
const transactionAvgCost = accCost ? (accCost / accQuantity) : 0;
|
||||
const averageCost = transactionAvgCost;
|
||||
const cost = (invTransaction.quantity * averageCost);
|
||||
|
||||
accQuantity -= invTransaction.quantity;
|
||||
accCost -= accCost;
|
||||
|
||||
transactions.push({
|
||||
...commonEntry,
|
||||
income,
|
||||
cost,
|
||||
incomeAccount: invTransaction.item.sellAccountId,
|
||||
costAccount: invTransaction.item.costAccountId,
|
||||
inventoryAccount: invTransaction.item.inventoryAccountId,
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
this.journalCommands.inventoryEntries(transactions);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
export default class InventoryCostLotTracker {
|
||||
|
||||
recalcInventoryLotsCost(inventoryTransactions) {
|
||||
|
||||
}
|
||||
|
||||
deleteTransactionsFromDate(fromDate) {
|
||||
|
||||
}
|
||||
}
|
||||
318
server/src/services/Inventory/InventoryCostLotTracker.ts
Normal file
318
server/src/services/Inventory/InventoryCostLotTracker.ts
Normal file
@@ -0,0 +1,318 @@
|
||||
import { omit, pick, chain } from 'lodash';
|
||||
import uniqid from 'uniqid';
|
||||
import {
|
||||
InventoryTransaction,
|
||||
InventoryLotCostTracker,
|
||||
Account,
|
||||
Item,
|
||||
} from "@/models";
|
||||
import { IInventoryLotCost, IInventoryTransaction } from "@/interfaces";
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import JournalCommands from '@/services/Accounting/JournalCommands';
|
||||
|
||||
type TCostMethod = 'FIFO' | 'LIFO';
|
||||
|
||||
export default class InventoryCostLotTracker implements IInventoryCostMethod {
|
||||
journal: JournalPoster;
|
||||
journalCommands: JournalCommands;
|
||||
startingDate: Date;
|
||||
headDate: Date;
|
||||
itemId: number;
|
||||
costMethod: TCostMethod;
|
||||
itemsById: Map<number, any>;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {Date} startingDate -
|
||||
* @param {number} itemId -
|
||||
* @param {string} costMethod -
|
||||
*/
|
||||
constructor(startingDate: Date, itemId: number, costMethod: TCostMethod = 'FIFO') {
|
||||
this.startingDate = startingDate;
|
||||
this.itemId = itemId;
|
||||
this.costMethod = costMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the inventory average cost method.
|
||||
* @async
|
||||
*/
|
||||
public async initialize() {
|
||||
const accountsDepGraph = await Account.tenant().depGraph().query();
|
||||
this.journal = new JournalPoster(accountsDepGraph);
|
||||
this.journalCommands = new JournalCommands(this.journal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes items costs from the given date using FIFO or LIFO cost method.
|
||||
* --------
|
||||
* - Revert the inventory lots after the given date.
|
||||
* - Remove all the journal entries from the inventory transactions
|
||||
* after the given date.
|
||||
* - Re-tracking the inventory lots from inventory transactions.
|
||||
* - Re-write the journal entries from the given inventory transactions.
|
||||
* @async
|
||||
* @return {void}
|
||||
*/
|
||||
public async computeItemCost(): Promise<any> {
|
||||
await this.revertInventoryLots(this.startingDate);
|
||||
|
||||
const afterInvTransactions: IInventoryTransaction[] =
|
||||
await InventoryTransaction.tenant()
|
||||
.query()
|
||||
.where('date', '>=', this.startingDate)
|
||||
.orderBy('date', 'ASC')
|
||||
.where('item_id', this.itemId)
|
||||
.withGraphFetched('item');
|
||||
|
||||
const availiableINLots: IInventoryLotCost[] =
|
||||
await InventoryLotCostTracker.tenant()
|
||||
.query()
|
||||
.where('date', '<', this.startingDate)
|
||||
.orderBy('date', 'ASC')
|
||||
.where('item_id', this.itemId)
|
||||
.where('direction', 'IN')
|
||||
.whereNot('remaining', 0);
|
||||
|
||||
const merged = [
|
||||
...availiableINLots.map((trans) => ({ lotTransId: trans.id, ...trans })),
|
||||
...afterInvTransactions.map((trans) => ({ invTransId: trans.id, ...trans })),
|
||||
];
|
||||
const itemsIds = chain(merged).map(e => e.itemId).uniq().value();
|
||||
|
||||
const storedItems = await Item.tenant()
|
||||
.query()
|
||||
.where('type', 'inventory')
|
||||
.whereIn('id', itemsIds);
|
||||
|
||||
this.itemsById = new Map(storedItems.map((item: any) => [item.id, item]));
|
||||
|
||||
// Re-tracking the inventory `IN` and `OUT` lots costs.
|
||||
const trackedInvLotsCosts = this.trackingInventoryLotsCost(merged);
|
||||
const storedTrackedInvLotsOper = this.storeInventoryLotsCost(trackedInvLotsCosts);
|
||||
|
||||
// Remove and revert accounts balance journal entries from inventory transactions.
|
||||
const revertJEntriesOper = this.revertJournalEntries(afterInvTransactions);
|
||||
|
||||
// Records the journal entries operation.
|
||||
this.recordJournalEntries(trackedInvLotsCosts);
|
||||
|
||||
return Promise.all([
|
||||
storedTrackedInvLotsOper,
|
||||
revertJEntriesOper.then(() =>
|
||||
Promise.all([
|
||||
// Saves the new recorded journal entries to the storage.
|
||||
this.journal.deleteEntries(),
|
||||
this.journal.saveEntries(),
|
||||
this.journal.saveBalance(),
|
||||
])),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert the inventory lots to the given date by removing the inventory lots
|
||||
* transactions after the given date and increment the remaining that
|
||||
* associate to lot number.
|
||||
* @async
|
||||
* @return {Promise}
|
||||
*/
|
||||
public async revertInventoryLots(startingDate: Date) {
|
||||
const asyncOpers: any[] = [];
|
||||
const inventoryLotsTrans = await InventoryLotCostTracker.tenant()
|
||||
.query()
|
||||
.orderBy('date', 'DESC')
|
||||
.where('item_id', this.itemId)
|
||||
.where('date', '>=', startingDate)
|
||||
.where('direction', 'OUT');
|
||||
|
||||
const deleteInvLotsTrans = InventoryLotCostTracker.tenant()
|
||||
.query()
|
||||
.where('date', '>=', startingDate)
|
||||
.where('item_id', this.itemId)
|
||||
.delete();
|
||||
|
||||
inventoryLotsTrans.forEach((inventoryLot: IInventoryLotCost) => {
|
||||
if (!inventoryLot.lotNumber) { return; }
|
||||
|
||||
const incrementOper = InventoryLotCostTracker.tenant()
|
||||
.query()
|
||||
.where('lot_number', inventoryLot.lotNumber)
|
||||
.where('direction', 'IN')
|
||||
.increment('remaining', inventoryLot.quantity);
|
||||
|
||||
asyncOpers.push(incrementOper);
|
||||
});
|
||||
return Promise.all([deleteInvLotsTrans, ...asyncOpers]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the journal entries from inventory lots costs transaction.
|
||||
* @param {} inventoryLots
|
||||
*/
|
||||
async revertJournalEntries(
|
||||
inventoryLots: IInventoryLotCost[],
|
||||
) {
|
||||
const invoiceTransactions = inventoryLots
|
||||
.filter(e => e.transactionType === 'SaleInvoice');
|
||||
|
||||
return this.journalCommands
|
||||
.revertEntriesFromInventoryTransactions(invoiceTransactions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the journal entries transactions.
|
||||
* @async
|
||||
* @param {IInventoryLotCost[]} inventoryTransactions -
|
||||
* @param {string} referenceType -
|
||||
* @param {number} referenceId -
|
||||
* @param {Date} date -
|
||||
* @return {Promise}
|
||||
*/
|
||||
public recordJournalEntries(
|
||||
inventoryLots: IInventoryLotCost[],
|
||||
): void {
|
||||
const outTransactions: any[] = [];
|
||||
const inTransByLotNumber: any = {};
|
||||
const transactions: any = [];
|
||||
|
||||
inventoryLots.forEach((invTransaction: IInventoryLotCost) => {
|
||||
switch(invTransaction.direction) {
|
||||
case 'IN':
|
||||
inTransByLotNumber[invTransaction.lotNumber] = invTransaction;
|
||||
break;
|
||||
case 'OUT':
|
||||
outTransactions.push(invTransaction);
|
||||
break;
|
||||
}
|
||||
});
|
||||
outTransactions.forEach((outTransaction: IInventoryLotCost) => {
|
||||
const { lotNumber, quantity, rate, itemId } = outTransaction;
|
||||
const income = quantity * rate;
|
||||
const item = this.itemsById.get(itemId);
|
||||
|
||||
const transaction = {
|
||||
date: outTransaction.date,
|
||||
referenceType: outTransaction.transactionType,
|
||||
referenceId: outTransaction.transactionId,
|
||||
cost: 0,
|
||||
income,
|
||||
incomeAccount: item.sellAccountId,
|
||||
costAccount: item.costAccountId,
|
||||
inventoryAccount: item.inventoryAccountId,
|
||||
};
|
||||
if (lotNumber && inTransByLotNumber[lotNumber]) {
|
||||
const inInvTrans = inTransByLotNumber[lotNumber];
|
||||
transaction.cost = (outTransaction.quantity * inInvTrans.rate);
|
||||
}
|
||||
transactions.push(transaction);
|
||||
});
|
||||
this.journalCommands.inventoryEntries(transactions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the inventory lots costs transactions in bulk.
|
||||
* @param {IInventoryLotCost[]} costLotsTransactions
|
||||
* @return {Promise[]}
|
||||
*/
|
||||
storeInventoryLotsCost(costLotsTransactions: IInventoryLotCost[]): Promise<object> {
|
||||
const opers: any = [];
|
||||
|
||||
costLotsTransactions.forEach((transaction: IInventoryLotCost) => {
|
||||
if (transaction.lotTransId && transaction.decrement) {
|
||||
const decrementOper = InventoryLotCostTracker.tenant()
|
||||
.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']),
|
||||
});
|
||||
opers.push(operation);
|
||||
}
|
||||
});
|
||||
return Promise.all(opers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracking the given inventory transactions to lots costs transactions.
|
||||
* @param {IInventoryTransaction[]} inventoryTransactions - Inventory transactions.
|
||||
* @return {IInventoryLotCost[]}
|
||||
*/
|
||||
public trackingInventoryLotsCost(
|
||||
inventoryTransactions: IInventoryTransaction[],
|
||||
) : IInventoryLotCost {
|
||||
// Collect cost lots transactions to insert them to the storage in bulk.
|
||||
const costLotsTransactions: IInventoryLotCost[] = [];
|
||||
// Collect inventory transactions by item id.
|
||||
const inventoryByItem: any = {};
|
||||
// Collection `IN` inventory tranaction by transaction id.
|
||||
const inventoryINTrans: any = {};
|
||||
|
||||
inventoryTransactions.forEach((transaction: IInventoryTransaction) => {
|
||||
const { itemId, id } = transaction;
|
||||
(inventoryByItem[itemId] || (inventoryByItem[itemId] = []));
|
||||
|
||||
const commonLotTransaction: IInventoryLotCost = {
|
||||
...pick(transaction, [
|
||||
'date', 'rate', 'itemId', 'quantity', 'invTransId', 'lotTransId',
|
||||
'direction', 'transactionType', 'transactionId', 'lotNumber', 'remaining'
|
||||
]),
|
||||
};
|
||||
// Record inventory `IN` cost lot transaction.
|
||||
if (transaction.direction === 'IN') {
|
||||
inventoryByItem[itemId].push(id);
|
||||
inventoryINTrans[id] = {
|
||||
...commonLotTransaction,
|
||||
decrement: 0,
|
||||
remaining: commonLotTransaction.remaining || commonLotTransaction.quantity,
|
||||
lotNumber: commonLotTransaction.lotNumber || uniqid.time(),
|
||||
};
|
||||
costLotsTransactions.push(inventoryINTrans[id]);
|
||||
|
||||
// Record inventory 'OUT' cost lots from 'IN' transactions.
|
||||
} else if (transaction.direction === 'OUT') {
|
||||
let invRemaining = transaction.quantity;
|
||||
|
||||
inventoryByItem?.[itemId]?.some((
|
||||
_invTransactionId: number,
|
||||
index: number,
|
||||
) => {
|
||||
const _invINTransaction = inventoryINTrans[_invTransactionId];
|
||||
if (invRemaining <= 0) { return true; }
|
||||
|
||||
// Detarmines the 'OUT' lot tranasctions whether bigger than 'IN' remaining transaction.
|
||||
const biggerThanRemaining = (_invINTransaction.remaining - transaction.quantity) > 0;
|
||||
const decrement = (biggerThanRemaining) ? transaction.quantity : _invINTransaction.remaining;
|
||||
|
||||
_invINTransaction.decrement += decrement;
|
||||
_invINTransaction.remaining = Math.max(
|
||||
_invINTransaction.remaining - decrement,
|
||||
0,
|
||||
);
|
||||
invRemaining = Math.max(invRemaining - decrement, 0);
|
||||
|
||||
costLotsTransactions.push({
|
||||
...commonLotTransaction,
|
||||
quantity: decrement,
|
||||
lotNumber: _invINTransaction.lotNumber,
|
||||
});
|
||||
// Pop the 'IN' lots that has zero remaining.
|
||||
if (_invINTransaction.remaining === 0) {
|
||||
inventoryByItem?.[itemId].splice(index, 1);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (invRemaining > 0) {
|
||||
costLotsTransactions.push({
|
||||
...commonLotTransaction,
|
||||
quantity: invRemaining,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return costLotsTransactions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,44 @@ import { Item } 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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { omit, sumBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { Container } from 'typedi';
|
||||
import {
|
||||
Account,
|
||||
Bill,
|
||||
@@ -22,7 +23,7 @@ import HasItemsEntries from '@/services/Sales/HasItemsEntries';
|
||||
export default class BillsService {
|
||||
/**
|
||||
* Creates a new bill and stored it to the storage.
|
||||
*
|
||||
*|
|
||||
* Precedures.
|
||||
* ----
|
||||
* - Insert bill transactions to the storage.
|
||||
@@ -30,11 +31,13 @@ export default class BillsService {
|
||||
* - Increment the given vendor id.
|
||||
* - Record bill journal transactions on the given accounts.
|
||||
* - Record bill items inventory transactions.
|
||||
*
|
||||
* ----
|
||||
* @param {IBill} bill -
|
||||
* @return {void}
|
||||
*/
|
||||
static async createBill(bill) {
|
||||
const agenda = Container.get('agenda');
|
||||
|
||||
const amount = sumBy(bill.entries, 'amount');
|
||||
const saveEntriesOpers = [];
|
||||
|
||||
@@ -57,20 +60,37 @@ export default class BillsService {
|
||||
// Increments vendor balance.
|
||||
const incrementOper = Vendor.changeBalance(bill.vendor_id, amount);
|
||||
|
||||
// // Rewrite the inventory transactions for inventory items.
|
||||
// const writeInvTransactionsOper = InventoryService.recordInventoryTransactions(
|
||||
// bill.entries, 'Bill', billId,
|
||||
// );
|
||||
// Rewrite the inventory transactions for inventory items.
|
||||
const writeInvTransactionsOper = InventoryService.recordInventoryTransactions(
|
||||
bill.entries, bill.bill_date, 'Bill', storedBill.id, 'IN',
|
||||
);
|
||||
// Writes the journal entries for the given bill transaction.
|
||||
const writeJEntriesOper = this.recordJournalTransactions({
|
||||
id: storedBill.id,
|
||||
...bill
|
||||
});
|
||||
await Promise.all([
|
||||
...saveEntriesOpers,
|
||||
incrementOper,
|
||||
// this.recordInventoryTransactions(bill, storedBill.id),
|
||||
this.recordJournalTransactions({ ...bill, id: storedBill.id }),
|
||||
// writeInvTransactionsOper,
|
||||
]);
|
||||
incrementOper,
|
||||
writeInvTransactionsOper,
|
||||
writeJEntriesOper,
|
||||
]);
|
||||
|
||||
// Schedule bill re-compute based on the item cost
|
||||
// method and starting date.
|
||||
await this.scheduleComputeItemsCost(bill);
|
||||
|
||||
return storedBill;
|
||||
}
|
||||
|
||||
scheduleComputeItemCost(bill) {
|
||||
return agenda.schedule('in 1 second', 'compute-item-cost', {
|
||||
startingDate: bill.bill_date || bill.billDate,
|
||||
itemId: bill.entries[0].item_id || bill.entries[0].itemId,
|
||||
costMethod: 'FIFO',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits details of the given bill id with associated entries.
|
||||
*
|
||||
@@ -116,21 +136,31 @@ export default class BillsService {
|
||||
amount,
|
||||
oldBill.amount,
|
||||
);
|
||||
// // Deletes the old inventory transactions.
|
||||
// const deleteInvTransactionsOper = InventorySevice.deleteInventoryTransactions(
|
||||
// billId, 'Bill',
|
||||
// );
|
||||
// // Re-write the inventory transactions for inventory items.
|
||||
// const writeInvTransactionsOper = InventoryService.recordInventoryTransactions(
|
||||
// bill.entries, 'Bill', billId,
|
||||
// );
|
||||
// Re-write the inventory transactions for inventory items.
|
||||
const writeInvTransactionsOper = InventoryService.recordInventoryTransactions(
|
||||
bill.entries, bill.bill_date, 'Bill', billId, 'IN'
|
||||
);
|
||||
// Delete bill associated inventory transactions.
|
||||
const deleteInventoryTransOper = InventoryService.deleteInventoryTransactions(
|
||||
billId, 'Bill'
|
||||
);
|
||||
// Writes the journal entries for the given bill transaction.
|
||||
const writeJEntriesOper = this.recordJournalTransactions({
|
||||
id: billId,
|
||||
...bill,
|
||||
}, billId);
|
||||
|
||||
await Promise.all([
|
||||
patchEntriesOper,
|
||||
recordTransactionsOper,
|
||||
changeVendorBalanceOper,
|
||||
// deleteInvTransactionsOper,
|
||||
// writeInvTransactionsOper,
|
||||
writeInvTransactionsOper,
|
||||
deleteInventoryTransOper,
|
||||
writeJEntriesOper,
|
||||
]);
|
||||
|
||||
// Schedule sale invoice re-compute based on the item cost
|
||||
// method and starting date.
|
||||
await this.scheduleComputeItemsCost(bill);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,19 +179,15 @@ export default class BillsService {
|
||||
.whereIn('id', entriesItemsIds);
|
||||
|
||||
const storedItemsMap = new Map(storedItems.map((item) => [item.id, item]));
|
||||
const payableAccount = await AccountsService.getAccountByType(
|
||||
'accounts_payable'
|
||||
);
|
||||
if (!payableAccount) {
|
||||
throw new Error('New payable account on the storage.');
|
||||
}
|
||||
const payableAccount = await AccountsService.getAccountByType('accounts_payable');
|
||||
|
||||
const accountsDepGraph = await Account.tenant().depGraph().query();
|
||||
const journal = new JournalPoster(accountsDepGraph);
|
||||
|
||||
const commonJournalMeta = {
|
||||
debit: 0,
|
||||
credit: 0,
|
||||
referenceId: billId,
|
||||
referenceId: bill.id,
|
||||
referenceType: 'Bill',
|
||||
date: formattedDate,
|
||||
accural: true,
|
||||
@@ -198,7 +224,7 @@ export default class BillsService {
|
||||
});
|
||||
journal.debit(debitEntry);
|
||||
});
|
||||
await Promise.all([
|
||||
return Promise.all([
|
||||
journal.deleteEntries(),
|
||||
journal.saveEntries(),
|
||||
journal.saveBalance(),
|
||||
@@ -211,7 +237,10 @@ export default class BillsService {
|
||||
* @return {void}
|
||||
*/
|
||||
static async deleteBill(billId) {
|
||||
const bill = await Bill.tenant().query().where('id', billId).first();
|
||||
const bill = await Bill.tenant().query()
|
||||
.where('id', billId)
|
||||
.withGraphFetched('entries')
|
||||
.first();
|
||||
|
||||
// Delete all associated bill entries.
|
||||
const deleteBillEntriesOper = ItemEntry.tenant()
|
||||
@@ -242,6 +271,9 @@ export default class BillsService {
|
||||
deleteInventoryTransOper,
|
||||
revertVendorBalance,
|
||||
]);
|
||||
// Schedule sale invoice re-compute based on the item cost
|
||||
// method and starting date.
|
||||
await this.scheduleComputeItemsCost(bill);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,7 +316,6 @@ export default class BillsService {
|
||||
return Bill.tenant().query().where('id', billId).first();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the given bill details with associated items entries.
|
||||
* @param {Integer} billId -
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { omit, difference, sumBy, mixin } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { SaleEstimate, ItemEntry } from '@/models';
|
||||
import HasItemsEntries from '@/services/Sales/HasItemsEntries';
|
||||
|
||||
@@ -11,6 +12,7 @@ export default class SaleEstimateService {
|
||||
*/
|
||||
static async createEstimate(estimate: any) {
|
||||
const amount = sumBy(estimate.entries, 'amount');
|
||||
|
||||
const storedEstimate = await SaleEstimate.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { omit, sumBy, difference, chain, sum } from 'lodash';
|
||||
import { omit, sumBy, difference } from 'lodash';
|
||||
import { Container } from 'typedi';
|
||||
import {
|
||||
SaleInvoice,
|
||||
AccountTransaction,
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import HasItemsEntries from '@/services/Sales/HasItemsEntries';
|
||||
import CustomerRepository from '@/repositories/CustomerRepository';
|
||||
import moment from 'moment';
|
||||
import InventoryService from '@/services/Inventory/Inventory';
|
||||
|
||||
/**
|
||||
* Sales invoices service
|
||||
@@ -51,8 +52,8 @@ export default class SaleInvoicesService {
|
||||
balance,
|
||||
);
|
||||
// Records the inventory transactions for inventory items.
|
||||
const recordInventoryTransOpers = this.recordInventoryTransactions(
|
||||
saleInvoice, storedInvoice.id
|
||||
const recordInventoryTransOpers = InventoryService.recordInventoryTransactions(
|
||||
saleInvoice.entries, saleInvoice.invoice_date, 'SaleInvoice', storedInvoice.id, 'OUT',
|
||||
);
|
||||
// Await all async operations.
|
||||
await Promise.all([
|
||||
@@ -60,19 +61,13 @@ export default class SaleInvoicesService {
|
||||
incrementOper,
|
||||
recordInventoryTransOpers,
|
||||
]);
|
||||
// Schedule sale invoice re-compute based on the item cost
|
||||
// method and starting date.
|
||||
await this.scheduleComputeItemsCost(saleInvoice);
|
||||
|
||||
return storedInvoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the inventory items transactions.
|
||||
* @param {SaleInvoice} saleInvoice -
|
||||
* @param {number} saleInvoiceId -
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async recordInventoryTransactions(saleInvoice, saleInvoiceId) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the sale invoice journal entries and calculate the items cost
|
||||
* based on the given cost method in the options FIFO, LIFO or average cost rate.
|
||||
@@ -84,6 +79,23 @@ export default class SaleInvoicesService {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule sale invoice re-compute based on the item
|
||||
* cost method and starting date
|
||||
*
|
||||
* @param saleInvoice
|
||||
* @return {Promise<Agenda>}
|
||||
*/
|
||||
static scheduleComputeItemsCost(saleInvoice) {
|
||||
const agenda = Container.get('agenda');
|
||||
|
||||
return agenda.schedule('in 1 second', 'compute-item-cost', {
|
||||
startingDate: saleInvoice.invoice_date || saleInvoice.invoiceDate,
|
||||
itemId: saleInvoice.entries[0].item_id || saleInvoice.entries[0].itemId,
|
||||
costMethod: 'FIFO',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the given sale invoice.
|
||||
* @async
|
||||
@@ -124,12 +136,10 @@ export default class SaleInvoicesService {
|
||||
patchItemsEntriesOper,
|
||||
changeCustomerBalanceOper,
|
||||
]);
|
||||
}
|
||||
|
||||
async recalcInventoryTransactionsCost(inventoryTransactions: array) {
|
||||
const inventoryTransactionsMap = this.mapInventoryTransByItem(inventoryTransactions);
|
||||
|
||||
|
||||
// Schedule sale invoice re-compute based on the item cost
|
||||
// method and starting date.
|
||||
await this.scheduleComputeItemsCost(saleInvoice);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,7 +148,7 @@ export default class SaleInvoicesService {
|
||||
* @param {number} transactionId
|
||||
*/
|
||||
static async revertInventoryTransactions(inventoryTransactions: array) {
|
||||
const opers = [];
|
||||
const opers: Promise<[]>[] = [];
|
||||
|
||||
inventoryTransactions.forEach((trans: any) => {
|
||||
switch(trans.direction) {
|
||||
@@ -175,7 +185,9 @@ export default class SaleInvoicesService {
|
||||
* @param {Number} saleInvoiceId
|
||||
*/
|
||||
static async deleteSaleInvoice(saleInvoiceId: number) {
|
||||
const oldSaleInvoice = await SaleInvoice.tenant().query().findById(saleInvoiceId);
|
||||
const oldSaleInvoice = await SaleInvoice.tenant().query()
|
||||
.findById(saleInvoiceId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
await SaleInvoice.tenant().query().where('id', saleInvoiceId).delete();
|
||||
await ItemEntry.tenant()
|
||||
@@ -206,14 +218,20 @@ export default class SaleInvoicesService {
|
||||
.where('transaction_id', saleInvoiceId);
|
||||
|
||||
// Revert inventory transactions.
|
||||
const revertInventoryTransactionsOper = this.revertInventoryTransactions(inventoryTransactions);
|
||||
|
||||
const revertInventoryTransactionsOper = this.revertInventoryTransactions(
|
||||
inventoryTransactions
|
||||
);
|
||||
|
||||
// Await all async operations.
|
||||
await Promise.all([
|
||||
journal.deleteEntries(),
|
||||
journal.saveBalance(),
|
||||
revertCustomerBalanceOper,
|
||||
revertInventoryTransactionsOper,
|
||||
]);
|
||||
// Schedule sale invoice re-compute based on the item cost
|
||||
// method and starting date.
|
||||
await this.scheduleComputeItemsCost(oldSaleInvoice)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,7 +279,7 @@ export default class SaleInvoicesService {
|
||||
static async isSaleInvoiceNumberExists(saleInvoiceNumber: string|number, saleInvoiceId: number) {
|
||||
const foundSaleInvoice = await SaleInvoice.tenant()
|
||||
.query()
|
||||
.onBuild((query) => {
|
||||
.onBuild((query: any) => {
|
||||
query.where('invoice_no', saleInvoiceNumber);
|
||||
|
||||
if (saleInvoiceId) {
|
||||
@@ -269,7 +287,7 @@ export default class SaleInvoicesService {
|
||||
}
|
||||
return query;
|
||||
});
|
||||
return foundSaleInvoice.length !== 0;
|
||||
return (foundSaleInvoice.length !== 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,7 +298,7 @@ export default class SaleInvoicesService {
|
||||
static async isInvoicesExist(invoicesIds: Array<number>) {
|
||||
const storedInvoices = await SaleInvoice.tenant()
|
||||
.query()
|
||||
.onBuild((builder) => {
|
||||
.onBuild((builder: any) => {
|
||||
builder.whereIn('id', invoicesIds);
|
||||
return builder;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user