mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 22:00:31 +00:00
fix: Graph fetch relations with sales & purchases models.
feat: Inventory tracker algorithm lots with FIFO or LIFO cost method.
This commit is contained in:
@@ -1,16 +0,0 @@
|
||||
import { InventoryTransaction } from "../../models";
|
||||
|
||||
|
||||
export default class InventoryService {
|
||||
|
||||
async isInventoryPurchaseSold(transactionType, transactionId) {
|
||||
|
||||
}
|
||||
|
||||
static deleteTransactions(transactionId, transactionType) {
|
||||
return InventoryTransaction.tenant().query()
|
||||
.where('transaction_type', transactionType)
|
||||
.where('transaction_id', transactionId)
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
149
server/src/services/Inventory/Inventory.ts
Normal file
149
server/src/services/Inventory/Inventory.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
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';
|
||||
|
||||
export default class InventoryService {
|
||||
/**
|
||||
* Records the inventory transactions.
|
||||
* @param {Bill} bill
|
||||
* @param {number} billId
|
||||
*/
|
||||
static async recordInventoryTransactions(
|
||||
entries: [],
|
||||
date: Date,
|
||||
transactionType: string,
|
||||
transactionId: number,
|
||||
) {
|
||||
const storedOpers: any = [];
|
||||
const entriesItemsIds = entries.map((e: any) => e.item_id);
|
||||
const inventoryItems = await Item.tenant()
|
||||
.query()
|
||||
.whereIn('id', entriesItemsIds)
|
||||
.where('type', 'inventory');
|
||||
|
||||
const inventoryItemsIds = inventoryItems.map((i) => i.id);
|
||||
|
||||
// Filter the bill entries that have inventory items.
|
||||
const inventoryEntries = entries.filter(
|
||||
(entry) => inventoryItemsIds.indexOf(entry.item_id) !== -1
|
||||
);
|
||||
inventoryEntries.forEach((entry: any) => {
|
||||
const oper = InventoryTransaction.tenant().query().insert({
|
||||
date,
|
||||
|
||||
item_id: entry.item_id,
|
||||
quantity: entry.quantity,
|
||||
rate: entry.rate,
|
||||
|
||||
transaction_type: transactionType,
|
||||
transaction_id: transactionId,
|
||||
});
|
||||
storedOpers.push(oper);
|
||||
});
|
||||
return Promise.all(storedOpers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given inventory transactions.
|
||||
* @param {string} transactionType
|
||||
* @param {number} transactionId
|
||||
* @return {Promise}
|
||||
*/
|
||||
static deleteInventoryTransactions(
|
||||
transactionId: number,
|
||||
transactionType: string,
|
||||
) {
|
||||
return InventoryTransaction.tenant().query()
|
||||
.where('transaction_type', transactionType)
|
||||
.where('transaction_id', transactionId)
|
||||
.delete();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
11
server/src/services/Inventory/InventoryCostLotTracker.js
Normal file
11
server/src/services/Inventory/InventoryCostLotTracker.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
export default class InventoryCostLotTracker {
|
||||
|
||||
recalcInventoryLotsCost(inventoryTransactions) {
|
||||
|
||||
}
|
||||
|
||||
deleteTransactionsFromDate(fromDate) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export default class BillPaymentsService {
|
||||
* @param {IBillPayment} oldBillPayment
|
||||
*/
|
||||
static async editBillPayment(billPaymentId, billPayment, oldBillPayment) {
|
||||
const amount = sumBy(bilPayment.entries, 'payment_amount');
|
||||
const amount = sumBy(billPayment.entries, 'payment_amount');
|
||||
const updateBillPayment = await BillPayment.tenant()
|
||||
.query()
|
||||
.where('id', billPaymentId)
|
||||
|
||||
@@ -54,19 +54,23 @@ export default class BillsService {
|
||||
});
|
||||
saveEntriesOpers.push(oper);
|
||||
});
|
||||
// Increment vendor balance.
|
||||
// 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,
|
||||
// );
|
||||
await Promise.all([
|
||||
...saveEntriesOpers,
|
||||
incrementOper,
|
||||
this.recordInventoryTransactions(bill, storedBill.id),
|
||||
// this.recordInventoryTransactions(bill, storedBill.id),
|
||||
this.recordJournalTransactions({ ...bill, id: storedBill.id }),
|
||||
// writeInvTransactionsOper,
|
||||
]);
|
||||
return storedBill;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Edits details of the given bill id with associated entries.
|
||||
*
|
||||
@@ -112,51 +116,23 @@ export default class BillsService {
|
||||
amount,
|
||||
oldBill.amount,
|
||||
);
|
||||
// Record bill journal transactions.
|
||||
const recordTransactionsOper = this.recordJournalTransactions(bill, billId);
|
||||
|
||||
// // 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,
|
||||
// );
|
||||
await Promise.all([
|
||||
patchEntriesOper,
|
||||
recordTransactionsOper,
|
||||
changeVendorBalanceOper,
|
||||
// deleteInvTransactionsOper,
|
||||
// writeInvTransactionsOper,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records inventory transactions.
|
||||
* @param {IBill} bill -
|
||||
* @return {void}
|
||||
*/
|
||||
static async recordInventoryTransactions(bill, billId) {
|
||||
const storeInventoryTransactions = [];
|
||||
const entriesItemsIds = bill.entries.map((e) => e.item_id);
|
||||
const inventoryItems = await Item.tenant()
|
||||
.query()
|
||||
.whereIn('id', entriesItemsIds)
|
||||
.where('type', 'inventory');
|
||||
|
||||
const inventoryItemsIds = inventoryItems.map((i) => i.id);
|
||||
const inventoryEntries = bill.entries.filter(
|
||||
(entry) => inventoryItemsIds.indexOf(entry.item_id) !== -1
|
||||
);
|
||||
inventoryEntries.forEach((entry) => {
|
||||
const oper = InventoryTransaction.tenant().query().insert({
|
||||
direction: 'IN',
|
||||
date: bill.bill_date,
|
||||
|
||||
item_id: entry.item_id,
|
||||
quantity: entry.quantity,
|
||||
rate: entry.rate,
|
||||
remaining: entry.quantity,
|
||||
|
||||
transaction_type: 'Bill',
|
||||
transaction_id: billId,
|
||||
});
|
||||
storeInventoryTransactions.push(oper);
|
||||
});
|
||||
return Promise.all([...storeInventoryTransactions]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the bill journal transactions.
|
||||
* @async
|
||||
@@ -253,9 +229,8 @@ export default class BillsService {
|
||||
'Bill'
|
||||
);
|
||||
// Delete bill associated inventory transactions.
|
||||
const deleteInventoryTransOper = InventoryService.deleteTransactions(
|
||||
billId,
|
||||
'Bill'
|
||||
const deleteInventoryTransOper = InventoryService.deleteInventoryTransactions(
|
||||
billId, 'Bill'
|
||||
);
|
||||
// Revert vendor balance.
|
||||
const revertVendorBalance = Vendor.changeBalance(bill.vendorId, bill.amount * -1);
|
||||
|
||||
@@ -98,8 +98,8 @@ export default class SaleEstimateService {
|
||||
*/
|
||||
static async isEstimateEntriesIDsExists(estimateId: number, estimate: any) {
|
||||
const estimateEntriesIds = estimate.entries
|
||||
.filter((e) => e.id)
|
||||
.map((e) => e.id);
|
||||
.filter((e: any) => e.id)
|
||||
.map((e: any) => e.id);
|
||||
|
||||
const estimateEntries = await ItemEntry.tenant()
|
||||
.query()
|
||||
@@ -138,6 +138,7 @@ export default class SaleEstimateService {
|
||||
.query()
|
||||
.where('id', estimateId)
|
||||
.withGraphFetched('entries')
|
||||
.withGraphFetched('customer')
|
||||
.first();
|
||||
|
||||
return estimate;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { omit, sumBy, difference } from 'lodash';
|
||||
import { omit, sumBy, difference, chain, sum } from 'lodash';
|
||||
import {
|
||||
SaleInvoice,
|
||||
AccountTransaction,
|
||||
InventoryTransaction,
|
||||
Account,
|
||||
ItemEntry,
|
||||
Customer,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import HasItemsEntries from '@/services/Sales/HasItemsEntries';
|
||||
import CustomerRepository from '@/repositories/CustomerRepository';
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* Sales invoices service
|
||||
@@ -48,10 +50,40 @@ export default class SaleInvoicesService {
|
||||
saleInvoice.customer_id,
|
||||
balance,
|
||||
);
|
||||
await Promise.all([...opers, incrementOper]);
|
||||
// Records the inventory transactions for inventory items.
|
||||
const recordInventoryTransOpers = this.recordInventoryTransactions(
|
||||
saleInvoice, storedInvoice.id
|
||||
);
|
||||
// Await all async operations.
|
||||
await Promise.all([
|
||||
...opers,
|
||||
incrementOper,
|
||||
recordInventoryTransOpers,
|
||||
]);
|
||||
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.
|
||||
*
|
||||
* @param {SaleInvoice} saleInvoice -
|
||||
* @param {Array} inventoryTransactions -
|
||||
*/
|
||||
static async recordJournalEntries(saleInvoice: any, inventoryTransactions: array[]) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the given sale invoice.
|
||||
* @async
|
||||
@@ -94,6 +126,48 @@ export default class SaleInvoicesService {
|
||||
]);
|
||||
}
|
||||
|
||||
async recalcInventoryTransactionsCost(inventoryTransactions: array) {
|
||||
const inventoryTransactionsMap = this.mapInventoryTransByItem(inventoryTransactions);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the inventory transactions.
|
||||
* @param {string} transactionType
|
||||
* @param {number} transactionId
|
||||
*/
|
||||
static async revertInventoryTransactions(inventoryTransactions: array) {
|
||||
const opers = [];
|
||||
|
||||
inventoryTransactions.forEach((trans: any) => {
|
||||
switch(trans.direction) {
|
||||
case 'OUT':
|
||||
if (trans.inventoryTransactionId) {
|
||||
const revertRemaining = InventoryTransaction.tenant()
|
||||
.query()
|
||||
.where('id', trans.inventoryTransactionId)
|
||||
.where('direction', 'OUT')
|
||||
.increment('remaining', trans.quanitity);
|
||||
|
||||
opers.push(revertRemaining);
|
||||
}
|
||||
break;
|
||||
case 'IN':
|
||||
const removeRelationOper = InventoryTransaction.tenant()
|
||||
.query()
|
||||
.where('inventory_transaction_id', trans.id)
|
||||
.where('direction', 'IN')
|
||||
.update({
|
||||
inventory_transaction_id: null,
|
||||
});
|
||||
opers.push(removeRelationOper);
|
||||
break;
|
||||
}
|
||||
});
|
||||
return Promise.all(opers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given sale invoice with associated entries
|
||||
* and journal transactions.
|
||||
@@ -126,10 +200,19 @@ export default class SaleInvoicesService {
|
||||
journal.loadEntries(invoiceTransactions);
|
||||
journal.removeEntries();
|
||||
|
||||
const inventoryTransactions = await InventoryTransaction.tenant()
|
||||
.query()
|
||||
.where('transaction_type', 'SaleInvoice')
|
||||
.where('transaction_id', saleInvoiceId);
|
||||
|
||||
// Revert inventory transactions.
|
||||
const revertInventoryTransactionsOper = this.revertInventoryTransactions(inventoryTransactions);
|
||||
|
||||
await Promise.all([
|
||||
journal.deleteEntries(),
|
||||
journal.saveBalance(),
|
||||
revertCustomerBalanceOper,
|
||||
revertInventoryTransactionsOper,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -152,6 +235,7 @@ export default class SaleInvoicesService {
|
||||
return SaleInvoice.tenant().query()
|
||||
.where('id', saleInvoiceId)
|
||||
.withGraphFetched('entries')
|
||||
.withGraphFetched('customer')
|
||||
.first();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user