feat: Schedule to compute items cost.

This commit is contained in:
Ahmed Bouhuolia
2020-08-19 01:30:12 +02:00
parent 52d01b4ed8
commit c2a60e6ba5
4 changed files with 175 additions and 175 deletions

View File

@@ -4,13 +4,14 @@ import InventoryService from '@/services/Inventory/Inventory';
export default class ComputeItemCostJob {
public async handler(job, done: Function): Promise<void> {
const Logger = Container.get('logger');
const { startingDate, itemId, costMethod } = job.attrs.data;
const { startingDate, itemId, costMethod = 'FIFO' } = job.attrs.data;
try {
await InventoryService.computeItemCost(startingDate, itemId, costMethod);
Logger.log(`Compute item cost: ${job.attrs.data}`);
done();
} catch(e) {
console.log(e);
Logger.error(`Compute item cost: ${job.attrs.data}, error: ${e}`);
done(e);
}

View File

@@ -1,3 +1,4 @@
import { Container } from 'typedi';
import {
InventoryTransaction,
Item,
@@ -5,7 +6,6 @@ import {
} from '@/models';
import InventoryAverageCost from '@/services/Inventory/InventoryAverageCost';
import InventoryCostLotTracker from '@/services/Inventory/InventoryCostLotTracker';
import { option } from 'commander';
type TCostMethod = 'FIFO' | 'LIFO' | 'AVG';
@@ -33,6 +33,23 @@ export default class InventoryService {
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.
* @param {Bill} bill
@@ -89,10 +106,6 @@ export default class InventoryService {
.delete();
}
revertInventoryLotsCost(fromDate?: Date) {
}
/**
* Retrieve the lot number after the increment.
*/
@@ -102,7 +115,7 @@ export default class InventoryService {
.where('key', LOT_NUMBER_KEY)
.increment('value', 1);
if (effectRows) {
if (effectRows === 0) {
await Option.tenant().query()
.insert({
key: LOT_NUMBER_KEY,

View File

@@ -1,6 +1,5 @@
import { omit, sumBy, pick } from 'lodash';
import moment from 'moment';
import { Container } from 'typedi';
import {
Account,
Bill,
@@ -36,7 +35,7 @@ export default class BillsService {
* @param {IBill} bill -
* @return {void}
*/
static async createBill(billDTO) {
static async createBill(billDTO) {
const invLotNumber = await InventoryService.nextLotNumber();
const bill = {
...formatDateFields(billDTO, ['bill_date', 'due_date']),
@@ -85,40 +84,6 @@ export default class BillsService {
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.
*
@@ -185,6 +150,72 @@ export default class BillsService {
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.
* @async
@@ -254,48 +285,21 @@ export default class BillsService {
}
/**
* Deletes the bill with associated entries.
* @param {Integer} billId
* @return {void}
* Schedule a job to re-compute the bill's items based on cost method
* of the each one.
* @param {Bill} bill
*/
static async deleteBill(billId) {
const bill = await Bill.tenant().query()
.where('id', billId)
.withGraphFetched('entries')
.first();
static scheduleComputeItemsCost(bill) {
const asyncOpers = [];
// 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);
bill.entries.forEach((entry) => {
const oper = InventoryService.scheduleComputeItemCost(
entry.item_id || entry.itemId,
bill.bill_date || bill.billDate,
);
asyncOpers.push(oper);
});
return Promise.all(asyncOpers);
}
/**

View File

@@ -1,5 +1,4 @@
import { omit, sumBy, difference, pick } from 'lodash';
import { Container } from 'typedi';
import {
SaleInvoice,
AccountTransaction,
@@ -74,56 +73,6 @@ export default class SaleInvoicesService {
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.
* @async
@@ -179,42 +128,6 @@ export default class SaleInvoicesService {
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
* and journal transactions.
@@ -271,13 +184,82 @@ export default class SaleInvoicesService {
}
/**
* Records the journal entries of sale invoice.
* @async
* @param {ISaleInvoice} saleInvoice
* @return {void}
* Records the inventory transactions from the givne sale invoice input.
* @param {SaleInvoice} saleInvoice -
* @param {number} saleInvoiceId -
* @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);
}
/**