mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 06:10:31 +00:00
feat: Schedule to compute items cost.
This commit is contained in:
@@ -4,13 +4,14 @@ import InventoryService from '@/services/Inventory/Inventory';
|
|||||||
export default class ComputeItemCostJob {
|
export default class ComputeItemCostJob {
|
||||||
public async handler(job, done: Function): Promise<void> {
|
public async handler(job, done: Function): Promise<void> {
|
||||||
const Logger = Container.get('logger');
|
const Logger = Container.get('logger');
|
||||||
const { startingDate, itemId, costMethod } = job.attrs.data;
|
const { startingDate, itemId, costMethod = 'FIFO' } = job.attrs.data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await InventoryService.computeItemCost(startingDate, itemId, costMethod);
|
await InventoryService.computeItemCost(startingDate, itemId, costMethod);
|
||||||
Logger.log(`Compute item cost: ${job.attrs.data}`);
|
Logger.log(`Compute item cost: ${job.attrs.data}`);
|
||||||
done();
|
done();
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
console.log(e);
|
||||||
Logger.error(`Compute item cost: ${job.attrs.data}, error: ${e}`);
|
Logger.error(`Compute item cost: ${job.attrs.data}, error: ${e}`);
|
||||||
done(e);
|
done(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Container } from 'typedi';
|
||||||
import {
|
import {
|
||||||
InventoryTransaction,
|
InventoryTransaction,
|
||||||
Item,
|
Item,
|
||||||
@@ -5,7 +6,6 @@ import {
|
|||||||
} from '@/models';
|
} from '@/models';
|
||||||
import InventoryAverageCost from '@/services/Inventory/InventoryAverageCost';
|
import InventoryAverageCost from '@/services/Inventory/InventoryAverageCost';
|
||||||
import InventoryCostLotTracker from '@/services/Inventory/InventoryCostLotTracker';
|
import InventoryCostLotTracker from '@/services/Inventory/InventoryCostLotTracker';
|
||||||
import { option } from 'commander';
|
|
||||||
|
|
||||||
type TCostMethod = 'FIFO' | 'LIFO' | 'AVG';
|
type TCostMethod = 'FIFO' | 'LIFO' | 'AVG';
|
||||||
|
|
||||||
@@ -33,6 +33,23 @@ export default class InventoryService {
|
|||||||
await costMethodComputer.computeItemCost()
|
await costMethodComputer.computeItemCost()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SChedule item cost compute job.
|
||||||
|
* @param {number} itemId
|
||||||
|
* @param {Date} startingDate
|
||||||
|
*/
|
||||||
|
static async scheduleComputeItemCost(itemId: number, startingDate: Date|string) {
|
||||||
|
const agenda = Container.get('agenda');
|
||||||
|
|
||||||
|
// Delete the scheduled job in case has the same given data.
|
||||||
|
await agenda.cancel({
|
||||||
|
name: 'compute-item-cost',
|
||||||
|
});
|
||||||
|
return agenda.schedule('in 3 seconds', 'compute-item-cost', {
|
||||||
|
startingDate, itemId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records the inventory transactions.
|
* Records the inventory transactions.
|
||||||
* @param {Bill} bill
|
* @param {Bill} bill
|
||||||
@@ -89,10 +106,6 @@ export default class InventoryService {
|
|||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
revertInventoryLotsCost(fromDate?: Date) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the lot number after the increment.
|
* Retrieve the lot number after the increment.
|
||||||
*/
|
*/
|
||||||
@@ -102,7 +115,7 @@ export default class InventoryService {
|
|||||||
.where('key', LOT_NUMBER_KEY)
|
.where('key', LOT_NUMBER_KEY)
|
||||||
.increment('value', 1);
|
.increment('value', 1);
|
||||||
|
|
||||||
if (effectRows) {
|
if (effectRows === 0) {
|
||||||
await Option.tenant().query()
|
await Option.tenant().query()
|
||||||
.insert({
|
.insert({
|
||||||
key: LOT_NUMBER_KEY,
|
key: LOT_NUMBER_KEY,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { omit, sumBy, pick } from 'lodash';
|
import { omit, sumBy, pick } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Container } from 'typedi';
|
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
Bill,
|
Bill,
|
||||||
@@ -85,40 +84,6 @@ export default class BillsService {
|
|||||||
return storedBill;
|
return storedBill;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedule a job to re-compute the bill's items based on cost method
|
|
||||||
* of the each one.
|
|
||||||
* @param {Bill} bill
|
|
||||||
*/
|
|
||||||
static scheduleComputeItemsCost(bill) {
|
|
||||||
const agenda = Container.get('agenda');
|
|
||||||
|
|
||||||
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',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records the inventory transactions from the given bill input.
|
|
||||||
* @param {Bill} bill
|
|
||||||
* @param {number} billId
|
|
||||||
*/
|
|
||||||
static recordInventoryTransactions(bill, billId, override) {
|
|
||||||
const inventoryTransactions = bill.entries
|
|
||||||
.map((entry) => ({
|
|
||||||
...pick(entry, ['item_id', 'quantity', 'rate']),
|
|
||||||
lotNumber: bill.invLotNumber,
|
|
||||||
transactionType: 'Bill',
|
|
||||||
transactionId: billId,
|
|
||||||
direction: 'IN',
|
|
||||||
date: bill.bill_date,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return InventoryService.recordInventoryTransactions(inventoryTransactions, override);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edits details of the given bill id with associated entries.
|
* Edits details of the given bill id with associated entries.
|
||||||
*
|
*
|
||||||
@@ -185,6 +150,72 @@ export default class BillsService {
|
|||||||
await this.scheduleComputeItemsCost(bill);
|
await this.scheduleComputeItemsCost(bill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the bill with associated entries.
|
||||||
|
* @param {Integer} billId
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
static async deleteBill(billId) {
|
||||||
|
const bill = await Bill.tenant().query()
|
||||||
|
.where('id', billId)
|
||||||
|
.withGraphFetched('entries')
|
||||||
|
.first();
|
||||||
|
|
||||||
|
// Delete all associated bill entries.
|
||||||
|
const deleteBillEntriesOper = ItemEntry.tenant()
|
||||||
|
.query()
|
||||||
|
.where('reference_type', 'Bill')
|
||||||
|
.where('reference_id', billId)
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
// Delete the bill transaction.
|
||||||
|
const deleteBillOper = Bill.tenant().query().where('id', billId).delete();
|
||||||
|
|
||||||
|
// Delete associated bill journal transactions.
|
||||||
|
const deleteTransactionsOper = JournalPosterService.deleteJournalTransactions(
|
||||||
|
billId,
|
||||||
|
'Bill'
|
||||||
|
);
|
||||||
|
// Delete bill associated inventory transactions.
|
||||||
|
const deleteInventoryTransOper = InventoryService.deleteInventoryTransactions(
|
||||||
|
billId, 'Bill'
|
||||||
|
);
|
||||||
|
// Revert vendor balance.
|
||||||
|
const revertVendorBalance = Vendor.changeBalance(bill.vendorId, bill.amount * -1);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
deleteBillOper,
|
||||||
|
deleteBillEntriesOper,
|
||||||
|
deleteTransactionsOper,
|
||||||
|
deleteInventoryTransOper,
|
||||||
|
revertVendorBalance,
|
||||||
|
]);
|
||||||
|
// Schedule sale invoice re-compute based on the item cost
|
||||||
|
// method and starting date.
|
||||||
|
await this.scheduleComputeItemsCost(bill);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the inventory transactions from the given bill input.
|
||||||
|
* @param {Bill} bill
|
||||||
|
* @param {number} billId
|
||||||
|
*/
|
||||||
|
static recordInventoryTransactions(bill, billId, override) {
|
||||||
|
const inventoryTransactions = bill.entries
|
||||||
|
.map((entry) => ({
|
||||||
|
...pick(entry, ['item_id', 'quantity', 'rate']),
|
||||||
|
lotNumber: bill.invLotNumber,
|
||||||
|
transactionType: 'Bill',
|
||||||
|
transactionId: billId,
|
||||||
|
direction: 'IN',
|
||||||
|
date: bill.bill_date,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return InventoryService.recordInventoryTransactions(
|
||||||
|
inventoryTransactions, override
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records the bill journal transactions.
|
* Records the bill journal transactions.
|
||||||
* @async
|
* @async
|
||||||
@@ -254,48 +285,21 @@ export default class BillsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the bill with associated entries.
|
* Schedule a job to re-compute the bill's items based on cost method
|
||||||
* @param {Integer} billId
|
* of the each one.
|
||||||
* @return {void}
|
* @param {Bill} bill
|
||||||
*/
|
*/
|
||||||
static async deleteBill(billId) {
|
static scheduleComputeItemsCost(bill) {
|
||||||
const bill = await Bill.tenant().query()
|
const asyncOpers = [];
|
||||||
.where('id', billId)
|
|
||||||
.withGraphFetched('entries')
|
|
||||||
.first();
|
|
||||||
|
|
||||||
// Delete all associated bill entries.
|
bill.entries.forEach((entry) => {
|
||||||
const deleteBillEntriesOper = ItemEntry.tenant()
|
const oper = InventoryService.scheduleComputeItemCost(
|
||||||
.query()
|
entry.item_id || entry.itemId,
|
||||||
.where('reference_type', 'Bill')
|
bill.bill_date || bill.billDate,
|
||||||
.where('reference_id', billId)
|
);
|
||||||
.delete();
|
asyncOpers.push(oper);
|
||||||
|
});
|
||||||
// Delete the bill transaction.
|
return Promise.all(asyncOpers);
|
||||||
const deleteBillOper = Bill.tenant().query().where('id', billId).delete();
|
|
||||||
|
|
||||||
// Delete associated bill journal transactions.
|
|
||||||
const deleteTransactionsOper = JournalPosterService.deleteJournalTransactions(
|
|
||||||
billId,
|
|
||||||
'Bill'
|
|
||||||
);
|
|
||||||
// Delete bill associated inventory transactions.
|
|
||||||
const deleteInventoryTransOper = InventoryService.deleteInventoryTransactions(
|
|
||||||
billId, 'Bill'
|
|
||||||
);
|
|
||||||
// Revert vendor balance.
|
|
||||||
const revertVendorBalance = Vendor.changeBalance(bill.vendorId, bill.amount * -1);
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
deleteBillOper,
|
|
||||||
deleteBillEntriesOper,
|
|
||||||
deleteTransactionsOper,
|
|
||||||
deleteInventoryTransOper,
|
|
||||||
revertVendorBalance,
|
|
||||||
]);
|
|
||||||
// Schedule sale invoice re-compute based on the item cost
|
|
||||||
// method and starting date.
|
|
||||||
await this.scheduleComputeItemsCost(bill);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { omit, sumBy, difference, pick } from 'lodash';
|
import { omit, sumBy, difference, pick } from 'lodash';
|
||||||
import { Container } from 'typedi';
|
|
||||||
import {
|
import {
|
||||||
SaleInvoice,
|
SaleInvoice,
|
||||||
AccountTransaction,
|
AccountTransaction,
|
||||||
@@ -74,56 +73,6 @@ export default class SaleInvoicesService {
|
|||||||
return storedInvoice;
|
return storedInvoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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[]) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records the inventory transactions from the givne sale invoice input.
|
|
||||||
* @param {SaleInvoice} saleInvoice -
|
|
||||||
* @param {number} saleInvoiceId -
|
|
||||||
* @param {boolean} override -
|
|
||||||
*/
|
|
||||||
static recordInventoryTranscactions(saleInvoice, saleInvoiceId: number, override?: boolean){
|
|
||||||
const inventortyTransactions = saleInvoice.entries
|
|
||||||
.map((entry) => ({
|
|
||||||
...pick(entry, ['item_id', 'quantity', 'rate']),
|
|
||||||
lotNumber: saleInvoice.invLotNumber,
|
|
||||||
transactionType: 'SaleInvoice',
|
|
||||||
transactionId: saleInvoiceId,
|
|
||||||
direction: 'OUT',
|
|
||||||
date: saleInvoice.invoice_date,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return InventoryService.recordInventoryTransactions(
|
|
||||||
inventortyTransactions, override,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedule sale invoice re-compute based on the item
|
|
||||||
* cost method and starting date
|
|
||||||
*
|
|
||||||
* @param {SaleInvoice} 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.
|
* Edit the given sale invoice.
|
||||||
* @async
|
* @async
|
||||||
@@ -179,42 +128,6 @@ export default class SaleInvoicesService {
|
|||||||
await this.scheduleComputeItemsCost(saleInvoice);
|
await this.scheduleComputeItemsCost(saleInvoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the inventory transactions.
|
|
||||||
* @param {string} transactionType
|
|
||||||
* @param {number} transactionId
|
|
||||||
*/
|
|
||||||
static async revertInventoryTransactions(inventoryTransactions: array) {
|
|
||||||
const opers: Promise<[]>[] = [];
|
|
||||||
|
|
||||||
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
|
* Deletes the given sale invoice with associated entries
|
||||||
* and journal transactions.
|
* and journal transactions.
|
||||||
@@ -271,13 +184,82 @@ export default class SaleInvoicesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records the journal entries of sale invoice.
|
* Records the inventory transactions from the givne sale invoice input.
|
||||||
* @async
|
* @param {SaleInvoice} saleInvoice -
|
||||||
* @param {ISaleInvoice} saleInvoice
|
* @param {number} saleInvoiceId -
|
||||||
* @return {void}
|
* @param {boolean} override -
|
||||||
*/
|
*/
|
||||||
async recordJournalEntries(saleInvoice: any) {
|
static recordInventoryTranscactions(saleInvoice, saleInvoiceId: number, override?: boolean){
|
||||||
|
const inventortyTransactions = saleInvoice.entries
|
||||||
|
.map((entry) => ({
|
||||||
|
...pick(entry, ['item_id', 'quantity', 'rate']),
|
||||||
|
lotNumber: saleInvoice.invLotNumber,
|
||||||
|
transactionType: 'SaleInvoice',
|
||||||
|
transactionId: saleInvoiceId,
|
||||||
|
direction: 'OUT',
|
||||||
|
date: saleInvoice.invoice_date,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return InventoryService.recordInventoryTransactions(
|
||||||
|
inventortyTransactions, override,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule sale invoice re-compute based on the item
|
||||||
|
* cost method and starting date
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {SaleInvoice} saleInvoice -
|
||||||
|
* @return {Promise<Agenda>}
|
||||||
|
*/
|
||||||
|
private static scheduleComputeItemsCost(saleInvoice: any) {
|
||||||
|
const asyncOpers: Promise<[]>[] = [];
|
||||||
|
|
||||||
|
saleInvoice.entries.forEach((entry: any) => {
|
||||||
|
const oper: Promise<[]> = InventoryService.scheduleComputeItemCost(
|
||||||
|
entry.item_id || entry.itemId,
|
||||||
|
saleInvoice.bill_date || saleInvoice.billDate,
|
||||||
|
);
|
||||||
|
asyncOpers.push(oper);
|
||||||
|
});
|
||||||
|
return Promise.all(asyncOpers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the inventory transactions.
|
||||||
|
* @param {string} transactionType
|
||||||
|
* @param {number} transactionId
|
||||||
|
*/
|
||||||
|
static async revertInventoryTransactions(inventoryTransactions: array) {
|
||||||
|
const opers: Promise<[]>[] = [];
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user