mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
refactor
This commit is contained in:
@@ -1,16 +1,16 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { Bill } from '../models/Bill';
|
import { Bill } from '../models/Bill';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
|
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
|
||||||
import { InventoryService } from '@/modules/InventoryCost/Inventory';
|
import { InventoryTransactionsService } from '@/modules/InventoryCost/InventoryTransactions.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BillInventoryTransactions {
|
export class BillInventoryTransactions {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly itemsEntriesService: ItemsEntriesService,
|
private readonly itemsEntriesService: ItemsEntriesService,
|
||||||
private readonly inventoryService: InventoryService,
|
private readonly inventoryTransactionsService: InventoryTransactionsService,
|
||||||
|
|
||||||
private readonly bill: typeof Bill
|
@Inject(Bill.name)
|
||||||
|
private readonly bill: typeof Bill,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,19 +21,18 @@ export class BillInventoryTransactions {
|
|||||||
public async recordInventoryTransactions(
|
public async recordInventoryTransactions(
|
||||||
billId: number,
|
billId: number,
|
||||||
override?: boolean,
|
override?: boolean,
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Retireve bill with assocaited entries and allocated cost entries.
|
// Retireve bill with assocaited entries and allocated cost entries.
|
||||||
|
|
||||||
const bill = await this.bill.query(trx)
|
const bill = await this.bill
|
||||||
|
.query(trx)
|
||||||
.findById(billId)
|
.findById(billId)
|
||||||
.withGraphFetched('entries.allocatedCostEntries');
|
.withGraphFetched('entries.allocatedCostEntries');
|
||||||
|
|
||||||
// Loads the inventory items entries of the given sale invoice.
|
// Loads the inventory items entries of the given sale invoice.
|
||||||
const inventoryEntries =
|
const inventoryEntries =
|
||||||
await this.itemsEntriesService.filterInventoryEntries(
|
await this.itemsEntriesService.filterInventoryEntries(bill.entries);
|
||||||
bill.entries
|
|
||||||
);
|
|
||||||
const transaction = {
|
const transaction = {
|
||||||
transactionId: bill.id,
|
transactionId: bill.id,
|
||||||
transactionType: 'Bill',
|
transactionType: 'Bill',
|
||||||
@@ -46,10 +45,10 @@ export class BillInventoryTransactions {
|
|||||||
|
|
||||||
warehouseId: bill.warehouseId,
|
warehouseId: bill.warehouseId,
|
||||||
};
|
};
|
||||||
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
|
await this.inventoryTransactionsService.recordInventoryTransactionsFromItemsEntries(
|
||||||
transaction,
|
transaction,
|
||||||
override,
|
override,
|
||||||
trx
|
trx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,13 +60,13 @@ export class BillInventoryTransactions {
|
|||||||
*/
|
*/
|
||||||
public async revertInventoryTransactions(
|
public async revertInventoryTransactions(
|
||||||
billId: number,
|
billId: number,
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction,
|
||||||
) {
|
) {
|
||||||
// Deletes the inventory transactions by the given reference id and type.
|
// Deletes the inventory transactions by the given reference id and type.
|
||||||
await this.inventoryService.deleteInventoryTransactions(
|
await this.inventoryTransactionsService.deleteInventoryTransactions(
|
||||||
billId,
|
billId,
|
||||||
'Bill',
|
'Bill',
|
||||||
trx
|
trx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InventoryTransactionsService } from '@/modules/InventoryCost/InventoryTransactions.service';
|
||||||
import { InventoryService } from '@/modules/InventoryCost/Inventory';
|
|
||||||
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
|
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
|
||||||
import { CreditNote } from '../models/CreditNote';
|
import { CreditNote } from '../models/CreditNote';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CreditNoteInventoryTransactions {
|
export class CreditNoteInventoryTransactions {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly inventoryService: InventoryService,
|
private readonly inventoryService: InventoryTransactionsService,
|
||||||
private readonly itemsEntriesService: ItemsEntriesService,
|
private readonly itemsEntriesService: ItemsEntriesService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,362 +1,214 @@
|
|||||||
import { pick } from 'lodash';
|
// import { pick } from 'lodash';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
// import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
// import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { Knex } from 'knex';
|
// import { Knex } from 'knex';
|
||||||
import {
|
// import {
|
||||||
IInventoryLotCost,
|
// IInventoryLotCost,
|
||||||
IInventoryTransaction,
|
// IInventoryTransaction,
|
||||||
TInventoryTransactionDirection,
|
// TInventoryTransactionDirection,
|
||||||
IItemEntry,
|
// IItemEntry,
|
||||||
IItemEntryTransactionType,
|
// IItemEntryTransactionType,
|
||||||
IInventoryTransactionsCreatedPayload,
|
// IInventoryTransactionsCreatedPayload,
|
||||||
IInventoryTransactionsDeletedPayload,
|
// IInventoryTransactionsDeletedPayload,
|
||||||
IInventoryItemCostScheduledPayload,
|
// IInventoryItemCostScheduledPayload,
|
||||||
} from '@/interfaces';
|
// } from '@/interfaces';
|
||||||
import { InventoryAverageCostMethod } from './InventoryAverageCost';
|
// import { InventoryAverageCostMethod } from './InventoryAverageCost';
|
||||||
import { InventoryCostLotTracker } from './InventoryCostLotTracker';
|
// import { InventoryCostLotTracker } from './InventoryCostLotTracker';
|
||||||
import { ItemsEntriesService } from '../Items/ItemsEntries.service';
|
// import { ItemsEntriesService } from '../Items/ItemsEntries.service';
|
||||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
// import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||||
import { Item } from '../Items/models/Item';
|
// import { Item } from '../Items/models/Item';
|
||||||
import { SETTINGS_PROVIDER } from '../Settings/Settings.types';
|
// import { SETTINGS_PROVIDER } from '../Settings/Settings.types';
|
||||||
import { SettingsStore } from '../Settings/SettingsStore';
|
// import { SettingsStore } from '../Settings/SettingsStore';
|
||||||
import { events } from '@/common/events/events';
|
// import { events } from '@/common/events/events';
|
||||||
import { InventoryTransaction } from './models/InventoryTransaction';
|
// import { InventoryTransaction } from './models/InventoryTransaction';
|
||||||
import InventoryCostMethod from './InventoryCostMethod';
|
// import InventoryCostMethod from './InventoryCostMethod';
|
||||||
|
|
||||||
@Injectable()
|
// @Injectable()
|
||||||
export class InventoryService {
|
// export class InventoryService {
|
||||||
constructor(
|
// constructor(
|
||||||
private readonly eventEmitter: EventEmitter2,
|
// private readonly eventEmitter: EventEmitter2,
|
||||||
private readonly uow: UnitOfWork,
|
// private readonly uow: UnitOfWork,
|
||||||
|
|
||||||
@Inject(InventoryTransaction.name)
|
// @Inject(InventoryTransaction.name)
|
||||||
private readonly inventoryTransactionModel: typeof InventoryTransaction,
|
// private readonly inventoryTransactionModel: typeof InventoryTransaction,
|
||||||
|
|
||||||
@Inject(InventoryCostLotTracker.name)
|
// @Inject(InventoryCostLotTracker.name)
|
||||||
private readonly inventoryCostLotTracker: typeof InventoryCostLotTracker,
|
// private readonly inventoryCostLotTracker: typeof InventoryCostLotTracker,
|
||||||
|
|
||||||
@Inject(SETTINGS_PROVIDER)
|
// @Inject(SETTINGS_PROVIDER)
|
||||||
private readonly settings: SettingsStore,
|
// private readonly settings: SettingsStore,
|
||||||
) {}
|
// ) {}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Transforms the items entries to inventory transactions.
|
// * Transforms the items entries to inventory transactions.
|
||||||
*/
|
// */
|
||||||
transformItemEntriesToInventory(transaction: {
|
// transformItemEntriesToInventory(transaction: {
|
||||||
transactionId: number;
|
// transactionId: number;
|
||||||
transactionType: IItemEntryTransactionType;
|
// transactionType: IItemEntryTransactionType;
|
||||||
transactionNumber?: string;
|
// transactionNumber?: string;
|
||||||
|
|
||||||
exchangeRate?: number;
|
// exchangeRate?: number;
|
||||||
|
|
||||||
warehouseId: number | null;
|
// warehouseId: number | null;
|
||||||
|
|
||||||
date: Date | string;
|
// date: Date | string;
|
||||||
direction: TInventoryTransactionDirection;
|
// direction: TInventoryTransactionDirection;
|
||||||
entries: IItemEntry[];
|
// entries: IItemEntry[];
|
||||||
createdAt: Date;
|
// createdAt: Date;
|
||||||
}): IInventoryTransaction[] {
|
// }): IInventoryTransaction[] {
|
||||||
const exchangeRate = transaction.exchangeRate || 1;
|
// const exchangeRate = transaction.exchangeRate || 1;
|
||||||
|
|
||||||
return transaction.entries.map((entry: IItemEntry) => ({
|
// return transaction.entries.map((entry: IItemEntry) => ({
|
||||||
...pick(entry, ['itemId', 'quantity']),
|
// ...pick(entry, ['itemId', 'quantity']),
|
||||||
rate: entry.rate * exchangeRate,
|
// rate: entry.rate * exchangeRate,
|
||||||
transactionType: transaction.transactionType,
|
// transactionType: transaction.transactionType,
|
||||||
transactionId: transaction.transactionId,
|
// transactionId: transaction.transactionId,
|
||||||
direction: transaction.direction,
|
// direction: transaction.direction,
|
||||||
date: transaction.date,
|
// date: transaction.date,
|
||||||
entryId: entry.id,
|
// entryId: entry.id,
|
||||||
createdAt: transaction.createdAt,
|
// createdAt: transaction.createdAt,
|
||||||
costAccountId: entry.costAccountId,
|
// costAccountId: entry.costAccountId,
|
||||||
|
|
||||||
warehouseId: entry.warehouseId || transaction.warehouseId,
|
// warehouseId: entry.warehouseId || transaction.warehouseId,
|
||||||
meta: {
|
// meta: {
|
||||||
transactionNumber: transaction.transactionNumber,
|
// transactionNumber: transaction.transactionNumber,
|
||||||
description: entry.description,
|
// description: entry.description,
|
||||||
},
|
// },
|
||||||
}));
|
// }));
|
||||||
}
|
// }
|
||||||
|
|
||||||
async computeItemCost(fromDate: Date, itemId: number) {
|
// async computeItemCost(fromDate: Date, itemId: number) {
|
||||||
return this.uow.withTransaction((trx: Knex.Transaction) => {
|
// return this.uow.withTransaction((trx: Knex.Transaction) => {
|
||||||
return this.computeInventoryItemCost(fromDate, itemId);
|
// return this.computeInventoryItemCost(fromDate, itemId);
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Computes the given item cost and records the inventory lots transactions
|
// * 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.
|
// * and journal entries based on the cost method FIFO, LIFO or average cost rate.
|
||||||
* @param {Date} fromDate - From date.
|
// * @param {Date} fromDate - From date.
|
||||||
* @param {number} itemId - Item id.
|
// * @param {number} itemId - Item id.
|
||||||
*/
|
// */
|
||||||
async computeInventoryItemCost(
|
// async computeInventoryItemCost(
|
||||||
fromDate: Date,
|
// fromDate: Date,
|
||||||
itemId: number,
|
// itemId: number,
|
||||||
trx?: Knex.Transaction,
|
// trx?: Knex.Transaction,
|
||||||
) {
|
// ) {
|
||||||
// Fetches the item with associated item category.
|
// // Fetches the item with associated item category.
|
||||||
const item = await Item.query().findById(itemId);
|
// const item = await Item.query().findById(itemId);
|
||||||
|
|
||||||
// Cannot continue if the given item was not inventory item.
|
// // Cannot continue if the given item was not inventory item.
|
||||||
if (item.type !== 'inventory') {
|
// if (item.type !== 'inventory') {
|
||||||
throw new Error('You could not compute item cost has no inventory type.');
|
// throw new Error('You could not compute item cost has no inventory type.');
|
||||||
}
|
// }
|
||||||
let costMethodComputer: InventoryCostMethod;
|
// let costMethodComputer: InventoryCostMethod;
|
||||||
|
|
||||||
// Switch between methods based on the item cost method.
|
// // Switch between methods based on the item cost method.
|
||||||
switch ('AVG') {
|
// switch ('AVG') {
|
||||||
case 'FIFO':
|
// case 'FIFO':
|
||||||
case 'LIFO':
|
// case 'LIFO':
|
||||||
costMethodComputer = new InventoryCostLotTracker(
|
// costMethodComputer = new InventoryCostLotTracker(
|
||||||
tenantId,
|
// tenantId,
|
||||||
fromDate,
|
// fromDate,
|
||||||
itemId,
|
// itemId,
|
||||||
);
|
// );
|
||||||
break;
|
// break;
|
||||||
case 'AVG':
|
// case 'AVG':
|
||||||
costMethodComputer = new InventoryAverageCostMethod(
|
// costMethodComputer = new InventoryAverageCostMethod(
|
||||||
fromDate,
|
// fromDate,
|
||||||
itemId,
|
// itemId,
|
||||||
trx,
|
// trx,
|
||||||
);
|
// );
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
return costMethodComputer.computeItemCost();
|
// return costMethodComputer.computeItemCost();
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Schedule item cost compute job.
|
// * Schedule item cost compute job.
|
||||||
* @param {number} tenantId
|
// * @param {number} tenantId
|
||||||
* @param {number} itemId
|
// * @param {number} itemId
|
||||||
* @param {Date} startingDate
|
// * @param {Date} startingDate
|
||||||
*/
|
// */
|
||||||
async scheduleComputeItemCost(
|
// async scheduleComputeItemCost(
|
||||||
tenantId: number,
|
// tenantId: number,
|
||||||
itemId: number,
|
// itemId: number,
|
||||||
startingDate: Date | string,
|
// startingDate: Date | string,
|
||||||
) {
|
// ) {
|
||||||
const agenda = Container.get('agenda');
|
// const agenda = Container.get('agenda');
|
||||||
|
|
||||||
const commonJobsQuery = {
|
// const commonJobsQuery = {
|
||||||
name: 'compute-item-cost',
|
// name: 'compute-item-cost',
|
||||||
lastRunAt: { $exists: false },
|
// lastRunAt: { $exists: false },
|
||||||
'data.tenantId': tenantId,
|
// 'data.tenantId': tenantId,
|
||||||
'data.itemId': itemId,
|
// 'data.itemId': itemId,
|
||||||
};
|
// };
|
||||||
// Cancel any `compute-item-cost` in the queue has upper starting date
|
// // Cancel any `compute-item-cost` in the queue has upper starting date
|
||||||
// with the same given item.
|
// // with the same given item.
|
||||||
await agenda.cancel({
|
// await agenda.cancel({
|
||||||
...commonJobsQuery,
|
// ...commonJobsQuery,
|
||||||
'data.startingDate': { $lte: startingDate },
|
// 'data.startingDate': { $lte: startingDate },
|
||||||
});
|
// });
|
||||||
// Retrieve any `compute-item-cost` in the queue has lower starting date
|
// // Retrieve any `compute-item-cost` in the queue has lower starting date
|
||||||
// with the same given item.
|
// // with the same given item.
|
||||||
const dependsJobs = await agenda.jobs({
|
// const dependsJobs = await agenda.jobs({
|
||||||
...commonJobsQuery,
|
// ...commonJobsQuery,
|
||||||
'data.startingDate': { $gte: startingDate },
|
// 'data.startingDate': { $gte: startingDate },
|
||||||
});
|
// });
|
||||||
// If the depends jobs cleared.
|
// // If the depends jobs cleared.
|
||||||
if (dependsJobs.length === 0) {
|
// if (dependsJobs.length === 0) {
|
||||||
await agenda.schedule(
|
// await agenda.schedule(
|
||||||
config.scheduleComputeItemCost,
|
// config.scheduleComputeItemCost,
|
||||||
'compute-item-cost',
|
// 'compute-item-cost',
|
||||||
{
|
// {
|
||||||
startingDate,
|
// startingDate,
|
||||||
itemId,
|
// itemId,
|
||||||
tenantId,
|
// tenantId,
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
// Triggers `onComputeItemCostJobScheduled` event.
|
// // Triggers `onComputeItemCostJobScheduled` event.
|
||||||
await this.eventPublisher.emitAsync(
|
// await this.eventPublisher.emitAsync(
|
||||||
events.inventory.onComputeItemCostJobScheduled,
|
// events.inventory.onComputeItemCostJobScheduled,
|
||||||
{
|
// {
|
||||||
startingDate,
|
// startingDate,
|
||||||
itemId,
|
// itemId,
|
||||||
tenantId,
|
// tenantId,
|
||||||
} as IInventoryItemCostScheduledPayload,
|
// } as IInventoryItemCostScheduledPayload,
|
||||||
);
|
// );
|
||||||
} else {
|
// } else {
|
||||||
// Re-schedule the jobs that have higher date from current moment.
|
// // Re-schedule the jobs that have higher date from current moment.
|
||||||
await Promise.all(
|
// await Promise.all(
|
||||||
dependsJobs.map((job) =>
|
// dependsJobs.map((job) =>
|
||||||
job.schedule(config.scheduleComputeItemCost).save(),
|
// job.schedule(config.scheduleComputeItemCost).save(),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
|
||||||
* Records the inventory transactions.
|
|
||||||
* @param {number} tenantId - Tenant id.
|
|
||||||
* @param {Bill} bill - Bill model object.
|
|
||||||
* @param {number} billId - Bill id.
|
|
||||||
* @return {Promise<void>}
|
|
||||||
*/
|
|
||||||
async recordInventoryTransactions(
|
|
||||||
transactions: IInventoryTransaction[],
|
|
||||||
override: boolean = false,
|
|
||||||
trx?: Knex.Transaction,
|
|
||||||
): Promise<void> {
|
|
||||||
const bulkInsertOpers = [];
|
|
||||||
|
|
||||||
transactions.forEach((transaction: IInventoryTransaction) => {
|
// /**
|
||||||
const oper = this.recordInventoryTransaction(transaction, override, trx);
|
// * Mark item cost computing is running.
|
||||||
bulkInsertOpers.push(oper);
|
// * @param {boolean} isRunning -
|
||||||
});
|
// */
|
||||||
const inventoryTransactions = await Promise.all(bulkInsertOpers);
|
// async markItemsCostComputeRunning(isRunning: boolean = true) {
|
||||||
|
// this.settings.set({
|
||||||
|
// key: 'cost_compute_running',
|
||||||
|
// group: 'inventory',
|
||||||
|
// value: isRunning,
|
||||||
|
// });
|
||||||
|
// await this.settings.save();
|
||||||
|
// }
|
||||||
|
|
||||||
// Triggers `onInventoryTransactionsCreated` event.
|
// /**
|
||||||
await this.eventEmitter.emitAsync(
|
// * Checks if the items cost compute is running.
|
||||||
events.inventory.onInventoryTransactionsCreated,
|
// * @returns {boolean}
|
||||||
{
|
// */
|
||||||
inventoryTransactions,
|
// isItemsCostComputeRunning() {
|
||||||
trx,
|
// return (
|
||||||
} as IInventoryTransactionsCreatedPayload,
|
// this.settings.get({
|
||||||
);
|
// key: 'cost_compute_running',
|
||||||
}
|
// group: 'inventory',
|
||||||
|
// }) ?? false
|
||||||
/**
|
// );
|
||||||
* Writes the inventory transactiosn on the storage from the given
|
// }
|
||||||
* inventory transactions entries.
|
// }
|
||||||
*
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {IInventoryTransaction} inventoryEntry -
|
|
||||||
* @param {boolean} deleteOld -
|
|
||||||
*/
|
|
||||||
async recordInventoryTransaction(
|
|
||||||
inventoryEntry: IInventoryTransaction,
|
|
||||||
deleteOld: boolean = false,
|
|
||||||
trx: Knex.Transaction,
|
|
||||||
): Promise<IInventoryTransaction> {
|
|
||||||
if (deleteOld) {
|
|
||||||
await this.deleteInventoryTransactions(
|
|
||||||
inventoryEntry.transactionId,
|
|
||||||
inventoryEntry.transactionType,
|
|
||||||
trx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return this.inventoryTransactionModel.query(trx).insertGraph({
|
|
||||||
...inventoryEntry,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records the inventory transactions from items entries that have (inventory) type.
|
|
||||||
*
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} transactionId
|
|
||||||
* @param {string} transactionType
|
|
||||||
* @param {Date|string} transactionDate
|
|
||||||
* @param {boolean} override
|
|
||||||
*/
|
|
||||||
async recordInventoryTransactionsFromItemsEntries(
|
|
||||||
transaction: {
|
|
||||||
transactionId: number;
|
|
||||||
transactionType: IItemEntryTransactionType;
|
|
||||||
exchangeRate: number;
|
|
||||||
|
|
||||||
date: Date | string;
|
|
||||||
direction: TInventoryTransactionDirection;
|
|
||||||
entries: IItemEntry[];
|
|
||||||
createdAt: Date | string;
|
|
||||||
|
|
||||||
warehouseId: number;
|
|
||||||
},
|
|
||||||
override: boolean = false,
|
|
||||||
trx?: Knex.Transaction,
|
|
||||||
): Promise<void> {
|
|
||||||
// Can't continue if there is no entries has inventory items in the invoice.
|
|
||||||
if (transaction.entries.length <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Inventory transactions.
|
|
||||||
const inventoryTranscations =
|
|
||||||
this.transformItemEntriesToInventory(transaction);
|
|
||||||
|
|
||||||
// Records the inventory transactions of the given sale invoice.
|
|
||||||
await this.recordInventoryTransactions(
|
|
||||||
inventoryTranscations,
|
|
||||||
override,
|
|
||||||
trx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the given inventory transactions.
|
|
||||||
* @param {number} tenantId - Tenant id.
|
|
||||||
* @param {string} transactionType
|
|
||||||
* @param {number} transactionId
|
|
||||||
* @return {Promise<{
|
|
||||||
* oldInventoryTransactions: IInventoryTransaction[]
|
|
||||||
* }>}
|
|
||||||
*/
|
|
||||||
async deleteInventoryTransactions(
|
|
||||||
transactionId: number,
|
|
||||||
transactionType: string,
|
|
||||||
trx?: Knex.Transaction,
|
|
||||||
): Promise<{ oldInventoryTransactions: InventoryTransaction[] }> {
|
|
||||||
// Retrieve the inventory transactions of the given sale invoice.
|
|
||||||
const oldInventoryTransactions = await this.inventoryTransactionModel
|
|
||||||
.query(trx)
|
|
||||||
.where({ transactionId, transactionType });
|
|
||||||
|
|
||||||
// Deletes the inventory transactions by the given transaction type and id.
|
|
||||||
await this.inventoryTransactionModel
|
|
||||||
.query(trx)
|
|
||||||
.where({ transactionType, transactionId })
|
|
||||||
.delete();
|
|
||||||
|
|
||||||
// Triggers `onInventoryTransactionsDeleted` event.
|
|
||||||
await this.eventEmitter.emitAsync(
|
|
||||||
events.inventory.onInventoryTransactionsDeleted,
|
|
||||||
{
|
|
||||||
oldInventoryTransactions,
|
|
||||||
transactionId,
|
|
||||||
transactionType,
|
|
||||||
trx,
|
|
||||||
} as IInventoryTransactionsDeletedPayload,
|
|
||||||
);
|
|
||||||
return { oldInventoryTransactions };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records the inventory cost lot transaction.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {IInventoryLotCost} inventoryLotEntry
|
|
||||||
* @return {Promise<IInventoryLotCost>}
|
|
||||||
*/
|
|
||||||
async recordInventoryCostLotTransaction(
|
|
||||||
tenantId: number,
|
|
||||||
inventoryLotEntry: IInventoryLotCost,
|
|
||||||
): Promise<void> {
|
|
||||||
return this.inventoryCostLotTracker.query().insert({
|
|
||||||
...inventoryLotEntry,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark item cost computing is running.
|
|
||||||
* @param {boolean} isRunning -
|
|
||||||
*/
|
|
||||||
async markItemsCostComputeRunning(isRunning: boolean = true) {
|
|
||||||
this.settings.set({
|
|
||||||
key: 'cost_compute_running',
|
|
||||||
group: 'inventory',
|
|
||||||
value: isRunning,
|
|
||||||
});
|
|
||||||
await this.settings.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the items cost compute is running.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isItemsCostComputeRunning() {
|
|
||||||
return (
|
|
||||||
this.settings.get({
|
|
||||||
key: 'cost_compute_running',
|
|
||||||
group: 'inventory',
|
|
||||||
}) ?? false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,254 +1,254 @@
|
|||||||
import { pick } from 'lodash';
|
// import { pick } from 'lodash';
|
||||||
import { Knex } from 'knex';
|
// import { Knex } from 'knex';
|
||||||
import InventoryCostMethod from './InventoryCostMethod';
|
// import InventoryCostMethod from './InventoryCostMethod';
|
||||||
import { InventoryTransaction } from './models/InventoryTransaction';
|
// import { InventoryTransaction } from './models/InventoryTransaction';
|
||||||
|
|
||||||
export class InventoryAverageCostMethod extends InventoryCostMethod {
|
// export class InventoryAverageCostMethod extends InventoryCostMethod {
|
||||||
startingDate: Date;
|
// startingDate: Date;
|
||||||
itemId: number;
|
// itemId: number;
|
||||||
costTransactions: any[];
|
// costTransactions: any[];
|
||||||
trx: Knex.Transaction;
|
// trx: Knex.Transaction;
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Constructor method.
|
// * Constructor method.
|
||||||
* @param {number} tenantId - The given tenant id.
|
// * @param {number} tenantId - The given tenant id.
|
||||||
* @param {Date} startingDate -
|
// * @param {Date} startingDate -
|
||||||
* @param {number} itemId - The given inventory item id.
|
// * @param {number} itemId - The given inventory item id.
|
||||||
*/
|
// */
|
||||||
constructor(
|
// constructor(
|
||||||
tenantId: number,
|
// tenantId: number,
|
||||||
startingDate: Date,
|
// startingDate: Date,
|
||||||
itemId: number,
|
// itemId: number,
|
||||||
trx?: Knex.Transaction,
|
// trx?: Knex.Transaction,
|
||||||
) {
|
// ) {
|
||||||
super(tenantId, startingDate, itemId);
|
// super(tenantId, startingDate, itemId);
|
||||||
|
|
||||||
this.trx = trx;
|
// this.trx = trx;
|
||||||
this.startingDate = startingDate;
|
// this.startingDate = startingDate;
|
||||||
this.itemId = itemId;
|
// this.itemId = itemId;
|
||||||
this.costTransactions = [];
|
// this.costTransactions = [];
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Computes items costs from the given date using average cost method.
|
// * Computes items costs from the given date using average cost method.
|
||||||
* ----------
|
// * ----------
|
||||||
* - Calculate the items average cost in the given date.
|
// * - Calculate the items average cost in the given date.
|
||||||
* - Remove the journal entries that associated to the inventory transacions
|
// * - Remove the journal entries that associated to the inventory transacions
|
||||||
* after the given date.
|
// * after the given date.
|
||||||
* - Re-compute the inventory transactions and re-write the journal entries
|
// * - Re-compute the inventory transactions and re-write the journal entries
|
||||||
* after the given date.
|
// * after the given date.
|
||||||
* ----------
|
// * ----------
|
||||||
* @async
|
// * @async
|
||||||
* @param {Date} startingDate
|
// * @param {Date} startingDate
|
||||||
* @param {number} referenceId
|
// * @param {number} referenceId
|
||||||
* @param {string} referenceType
|
// * @param {string} referenceType
|
||||||
*/
|
// */
|
||||||
public async computeItemCost() {
|
// public async computeItemCost() {
|
||||||
const { InventoryTransaction } = this.tenantModels;
|
// const { InventoryTransaction } = this.tenantModels;
|
||||||
const { averageCost, openingQuantity, openingCost } =
|
// const { averageCost, openingQuantity, openingCost } =
|
||||||
await this.getOpeningAverageCost(this.startingDate, this.itemId);
|
// await this.getOpeningAverageCost(this.startingDate, this.itemId);
|
||||||
|
|
||||||
const afterInvTransactions =
|
// const afterInvTransactions =
|
||||||
await InventoryTransaction.query()
|
// await InventoryTransaction.query()
|
||||||
.modify('filterDateRange', this.startingDate)
|
// .modify('filterDateRange', this.startingDate)
|
||||||
.orderBy('date', 'ASC')
|
// .orderBy('date', 'ASC')
|
||||||
.orderByRaw("FIELD(direction, 'IN', 'OUT')")
|
// .orderByRaw("FIELD(direction, 'IN', 'OUT')")
|
||||||
.orderBy('createdAt', 'ASC')
|
// .orderBy('createdAt', 'ASC')
|
||||||
.where('item_id', this.itemId)
|
// .where('item_id', this.itemId)
|
||||||
.withGraphFetched('item');
|
// .withGraphFetched('item');
|
||||||
|
|
||||||
// Tracking inventroy transactions and retrieve cost transactions based on
|
// // Tracking inventroy transactions and retrieve cost transactions based on
|
||||||
// average rate cost method.
|
// // average rate cost method.
|
||||||
const costTransactions = this.trackingCostTransactions(
|
// const costTransactions = this.trackingCostTransactions(
|
||||||
afterInvTransactions,
|
// afterInvTransactions,
|
||||||
openingQuantity,
|
// openingQuantity,
|
||||||
openingCost,
|
// openingCost,
|
||||||
);
|
// );
|
||||||
// Revert the inveout out lots transactions
|
// // Revert the inveout out lots transactions
|
||||||
await this.revertTheInventoryOutLotTrans();
|
// await this.revertTheInventoryOutLotTrans();
|
||||||
|
|
||||||
// Store inventory lots cost transactions.
|
// // Store inventory lots cost transactions.
|
||||||
await this.storeInventoryLotsCost(costTransactions);
|
// await this.storeInventoryLotsCost(costTransactions);
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Get items Average cost from specific date from inventory transactions.
|
// * Get items Average cost from specific date from inventory transactions.
|
||||||
* @async
|
// * @async
|
||||||
* @param {Date} closingDate
|
// * @param {Date} closingDate
|
||||||
* @return {number}
|
// * @return {number}
|
||||||
*/
|
// */
|
||||||
public async getOpeningAverageCost(closingDate: Date, itemId: number) {
|
// public async getOpeningAverageCost(closingDate: Date, itemId: number) {
|
||||||
const { InventoryCostLotTracker } = this.tenantModels;
|
// const { InventoryCostLotTracker } = this.tenantModels;
|
||||||
|
|
||||||
const commonBuilder = (builder: any) => {
|
// const commonBuilder = (builder: any) => {
|
||||||
if (closingDate) {
|
// if (closingDate) {
|
||||||
builder.where('date', '<', closingDate);
|
// builder.where('date', '<', closingDate);
|
||||||
}
|
// }
|
||||||
builder.where('item_id', itemId);
|
// builder.where('item_id', itemId);
|
||||||
builder.sum('rate as rate');
|
// builder.sum('rate as rate');
|
||||||
builder.sum('quantity as quantity');
|
// builder.sum('quantity as quantity');
|
||||||
builder.sum('cost as cost');
|
// builder.sum('cost as cost');
|
||||||
builder.first();
|
// builder.first();
|
||||||
};
|
// };
|
||||||
// Calculates the total inventory total quantity and rate `IN` transactions.
|
// // Calculates the total inventory total quantity and rate `IN` transactions.
|
||||||
const inInvSumationOper: Promise<any> = InventoryCostLotTracker.query()
|
// const inInvSumationOper: Promise<any> = InventoryCostLotTracker.query()
|
||||||
.onBuild(commonBuilder)
|
// .onBuild(commonBuilder)
|
||||||
.where('direction', 'IN');
|
// .where('direction', 'IN');
|
||||||
|
|
||||||
// Calculates the total inventory total quantity and rate `OUT` transactions.
|
// // Calculates the total inventory total quantity and rate `OUT` transactions.
|
||||||
const outInvSumationOper: Promise<any> = InventoryCostLotTracker.query()
|
// const outInvSumationOper: Promise<any> = InventoryCostLotTracker.query()
|
||||||
.onBuild(commonBuilder)
|
// .onBuild(commonBuilder)
|
||||||
.where('direction', 'OUT');
|
// .where('direction', 'OUT');
|
||||||
|
|
||||||
const [inInvSumation, outInvSumation] = await Promise.all([
|
// const [inInvSumation, outInvSumation] = await Promise.all([
|
||||||
inInvSumationOper,
|
// inInvSumationOper,
|
||||||
outInvSumationOper,
|
// outInvSumationOper,
|
||||||
]);
|
// ]);
|
||||||
return this.computeItemAverageCost(
|
// return this.computeItemAverageCost(
|
||||||
inInvSumation?.cost || 0,
|
// inInvSumation?.cost || 0,
|
||||||
inInvSumation?.quantity || 0,
|
// inInvSumation?.quantity || 0,
|
||||||
outInvSumation?.cost || 0,
|
// outInvSumation?.cost || 0,
|
||||||
outInvSumation?.quantity || 0,
|
// outInvSumation?.quantity || 0,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Computes the item average cost.
|
// * Computes the item average cost.
|
||||||
* @static
|
// * @static
|
||||||
* @param {number} quantityIn
|
// * @param {number} quantityIn
|
||||||
* @param {number} rateIn
|
// * @param {number} rateIn
|
||||||
* @param {number} quantityOut
|
// * @param {number} quantityOut
|
||||||
* @param {number} rateOut
|
// * @param {number} rateOut
|
||||||
*/
|
// */
|
||||||
public computeItemAverageCost(
|
// public computeItemAverageCost(
|
||||||
totalCostIn: number,
|
// totalCostIn: number,
|
||||||
totalQuantityIn: number,
|
// totalQuantityIn: number,
|
||||||
|
|
||||||
totalCostOut: number,
|
// totalCostOut: number,
|
||||||
totalQuantityOut: number,
|
// totalQuantityOut: number,
|
||||||
) {
|
// ) {
|
||||||
const openingCost = totalCostIn - totalCostOut;
|
// const openingCost = totalCostIn - totalCostOut;
|
||||||
const openingQuantity = totalQuantityIn - totalQuantityOut;
|
// const openingQuantity = totalQuantityIn - totalQuantityOut;
|
||||||
|
|
||||||
const averageCost = openingQuantity ? openingCost / openingQuantity : 0;
|
// const averageCost = openingQuantity ? openingCost / openingQuantity : 0;
|
||||||
|
|
||||||
return { averageCost, openingCost, openingQuantity };
|
// return { averageCost, openingCost, openingQuantity };
|
||||||
}
|
// }
|
||||||
|
|
||||||
private getCost(rate: number, quantity: number) {
|
// private getCost(rate: number, quantity: number) {
|
||||||
return quantity ? rate * quantity : rate;
|
// return quantity ? rate * quantity : rate;
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Records the journal entries from specific item inventory transactions.
|
// * Records the journal entries from specific item inventory transactions.
|
||||||
* @param {IInventoryTransaction[]} invTransactions
|
// * @param {IInventoryTransaction[]} invTransactions
|
||||||
* @param {number} openingAverageCost
|
// * @param {number} openingAverageCost
|
||||||
* @param {string} referenceType
|
// * @param {string} referenceType
|
||||||
* @param {number} referenceId
|
// * @param {number} referenceId
|
||||||
* @param {JournalCommand} journalCommands
|
// * @param {JournalCommand} journalCommands
|
||||||
*/
|
// */
|
||||||
public trackingCostTransactions(
|
// public trackingCostTransactions(
|
||||||
invTransactions: InventoryTransaction[],
|
// invTransactions: InventoryTransaction[],
|
||||||
openingQuantity: number = 0,
|
// openingQuantity: number = 0,
|
||||||
openingCost: number = 0,
|
// openingCost: number = 0,
|
||||||
) {
|
// ) {
|
||||||
const costTransactions: any[] = [];
|
// const costTransactions: any[] = [];
|
||||||
|
|
||||||
// Cumulative item quantity and cost. This will decrement after
|
// // Cumulative item quantity and cost. This will decrement after
|
||||||
// each out transactions depends on its quantity and cost.
|
// // each out transactions depends on its quantity and cost.
|
||||||
let accQuantity: number = openingQuantity;
|
// let accQuantity: number = openingQuantity;
|
||||||
let accCost: number = openingCost;
|
// let accCost: number = openingCost;
|
||||||
|
|
||||||
invTransactions.forEach((invTransaction: InventoryTransaction) => {
|
// invTransactions.forEach((invTransaction: InventoryTransaction) => {
|
||||||
const commonEntry = {
|
// const commonEntry = {
|
||||||
invTransId: invTransaction.id,
|
// invTransId: invTransaction.id,
|
||||||
...pick(invTransaction, [
|
// ...pick(invTransaction, [
|
||||||
'date',
|
// 'date',
|
||||||
'direction',
|
// 'direction',
|
||||||
'itemId',
|
// 'itemId',
|
||||||
'quantity',
|
// 'quantity',
|
||||||
'rate',
|
// 'rate',
|
||||||
'entryId',
|
// 'entryId',
|
||||||
'transactionId',
|
// 'transactionId',
|
||||||
'transactionType',
|
// 'transactionType',
|
||||||
'createdAt',
|
// 'createdAt',
|
||||||
'costAccountId',
|
// 'costAccountId',
|
||||||
'branchId',
|
// 'branchId',
|
||||||
'warehouseId',
|
// 'warehouseId',
|
||||||
]),
|
// ]),
|
||||||
inventoryTransactionId: invTransaction.id,
|
// inventoryTransactionId: invTransaction.id,
|
||||||
};
|
// };
|
||||||
switch (invTransaction.direction) {
|
// switch (invTransaction.direction) {
|
||||||
case 'IN':
|
// case 'IN':
|
||||||
const inCost = this.getCost(
|
// const inCost = this.getCost(
|
||||||
invTransaction.rate,
|
// invTransaction.rate,
|
||||||
invTransaction.quantity,
|
// invTransaction.quantity,
|
||||||
);
|
// );
|
||||||
// Increases the quantity and cost in `IN` inventory transactions.
|
// // Increases the quantity and cost in `IN` inventory transactions.
|
||||||
accQuantity += invTransaction.quantity;
|
// accQuantity += invTransaction.quantity;
|
||||||
accCost += inCost;
|
// accCost += inCost;
|
||||||
|
|
||||||
costTransactions.push({
|
// costTransactions.push({
|
||||||
...commonEntry,
|
// ...commonEntry,
|
||||||
cost: inCost,
|
// cost: inCost,
|
||||||
});
|
// });
|
||||||
break;
|
// break;
|
||||||
case 'OUT':
|
// case 'OUT':
|
||||||
// Average cost = Total cost / Total quantity
|
// // Average cost = Total cost / Total quantity
|
||||||
const averageCost = accQuantity ? accCost / accQuantity : 0;
|
// const averageCost = accQuantity ? accCost / accQuantity : 0;
|
||||||
|
|
||||||
const quantity =
|
// const quantity =
|
||||||
accQuantity > 0
|
// accQuantity > 0
|
||||||
? Math.min(invTransaction.quantity, accQuantity)
|
// ? Math.min(invTransaction.quantity, accQuantity)
|
||||||
: invTransaction.quantity;
|
// : invTransaction.quantity;
|
||||||
|
|
||||||
// Cost = the transaction quantity * Average cost.
|
// // Cost = the transaction quantity * Average cost.
|
||||||
const cost = this.getCost(averageCost, quantity);
|
// const cost = this.getCost(averageCost, quantity);
|
||||||
|
|
||||||
// Revenue = transaction quanity * rate.
|
// // Revenue = transaction quanity * rate.
|
||||||
// const revenue = quantity * invTransaction.rate;
|
// // const revenue = quantity * invTransaction.rate;
|
||||||
costTransactions.push({
|
// costTransactions.push({
|
||||||
...commonEntry,
|
// ...commonEntry,
|
||||||
quantity,
|
// quantity,
|
||||||
cost,
|
// cost,
|
||||||
});
|
// });
|
||||||
accQuantity = Math.max(accQuantity - quantity, 0);
|
// accQuantity = Math.max(accQuantity - quantity, 0);
|
||||||
accCost = Math.max(accCost - cost, 0);
|
// accCost = Math.max(accCost - cost, 0);
|
||||||
|
|
||||||
if (invTransaction.quantity > quantity) {
|
// if (invTransaction.quantity > quantity) {
|
||||||
const remainingQuantity = Math.max(
|
// const remainingQuantity = Math.max(
|
||||||
invTransaction.quantity - quantity,
|
// invTransaction.quantity - quantity,
|
||||||
0,
|
// 0,
|
||||||
);
|
// );
|
||||||
const remainingIncome = remainingQuantity * invTransaction.rate;
|
// const remainingIncome = remainingQuantity * invTransaction.rate;
|
||||||
|
|
||||||
costTransactions.push({
|
// costTransactions.push({
|
||||||
...commonEntry,
|
// ...commonEntry,
|
||||||
quantity: remainingQuantity,
|
// quantity: remainingQuantity,
|
||||||
cost: 0,
|
// cost: 0,
|
||||||
});
|
// });
|
||||||
accQuantity = Math.max(accQuantity - remainingQuantity, 0);
|
// accQuantity = Math.max(accQuantity - remainingQuantity, 0);
|
||||||
accCost = Math.max(accCost - remainingIncome, 0);
|
// accCost = Math.max(accCost - remainingIncome, 0);
|
||||||
}
|
// }
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
return costTransactions;
|
// return costTransactions;
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Reverts the inventory lots `OUT` transactions.
|
// * Reverts the inventory lots `OUT` transactions.
|
||||||
* @param {Date} openingDate - Opening date.
|
// * @param {Date} openingDate - Opening date.
|
||||||
* @param {number} itemId - Item id.
|
// * @param {number} itemId - Item id.
|
||||||
* @returns {Promise<void>}
|
// * @returns {Promise<void>}
|
||||||
*/
|
// */
|
||||||
async revertTheInventoryOutLotTrans(): Promise<void> {
|
// async revertTheInventoryOutLotTrans(): Promise<void> {
|
||||||
const { InventoryCostLotTracker } = this.tenantModels;
|
// const { InventoryCostLotTracker } = this.tenantModels;
|
||||||
|
|
||||||
await InventoryCostLotTracker.query(this.trx)
|
// await InventoryCostLotTracker.query(this.trx)
|
||||||
.modify('filterDateRange', this.startingDate)
|
// .modify('filterDateRange', this.startingDate)
|
||||||
.orderBy('date', 'DESC')
|
// .orderBy('date', 'DESC')
|
||||||
.where('item_id', this.itemId)
|
// .where('item_id', this.itemId)
|
||||||
.delete();
|
// .delete();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InventoryItemCostService } from './InventoryCosts.service';
|
// import { InventoryItemCostService } from './InventoryCosts.service';
|
||||||
import { IInventoryItemCostMeta } from './types/InventoryCost.types';
|
import { IInventoryItemCostMeta } from './types/InventoryCost.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InventoryCostApplication {
|
export class InventoryCostApplication {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly inventoryCost: InventoryItemCostService,
|
// private readonly inventoryCost: InventoryItemCostService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,11 +17,11 @@ export class InventoryCostApplication {
|
|||||||
public getItemsInventoryValuationList = async (
|
public getItemsInventoryValuationList = async (
|
||||||
itemsId: number[],
|
itemsId: number[],
|
||||||
date: Date
|
date: Date
|
||||||
): Promise<IInventoryItemCostMeta[]> => {
|
): Promise<any> => {
|
||||||
const itemsMap = await this.inventoryCost.getItemsInventoryValuation(
|
// const itemsMap = await this.inventoryCost.getItemsInventoryValuation(
|
||||||
itemsId,
|
// itemsId,
|
||||||
date
|
// date
|
||||||
);
|
// );
|
||||||
return [...itemsMap.values()];
|
// return [...itemsMap.values()];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,302 +1,302 @@
|
|||||||
import { pick, chain } from 'lodash';
|
// import { pick, chain } from 'lodash';
|
||||||
import moment from 'moment';
|
// import moment from 'moment';
|
||||||
import { IInventoryLotCost, IInventoryTransaction } from "interfaces";
|
// import { IInventoryLotCost, IInventoryTransaction } from "interfaces";
|
||||||
import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod';
|
// import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod';
|
||||||
|
|
||||||
type TCostMethod = 'FIFO' | 'LIFO';
|
// type TCostMethod = 'FIFO' | 'LIFO';
|
||||||
|
|
||||||
export class InventoryCostLotTracker extends InventoryCostMethod {
|
// export class InventoryCostLotTracker extends InventoryCostMethod {
|
||||||
startingDate: Date;
|
// startingDate: Date;
|
||||||
itemId: number;
|
// itemId: number;
|
||||||
costMethod: TCostMethod;
|
// costMethod: TCostMethod;
|
||||||
itemsById: Map<number, any>;
|
// itemsById: Map<number, any>;
|
||||||
inventoryINTrans: any;
|
// inventoryINTrans: any;
|
||||||
inventoryByItem: any;
|
// inventoryByItem: any;
|
||||||
costLotsTransactions: IInventoryLotCost[];
|
// costLotsTransactions: IInventoryLotCost[];
|
||||||
inTransactions: any[];
|
// inTransactions: any[];
|
||||||
outTransactions: IInventoryTransaction[];
|
// outTransactions: IInventoryTransaction[];
|
||||||
revertJEntriesTransactions: IInventoryTransaction[];
|
// revertJEntriesTransactions: IInventoryTransaction[];
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Constructor method.
|
// * Constructor method.
|
||||||
* @param {Date} startingDate -
|
// * @param {Date} startingDate -
|
||||||
* @param {number} itemId -
|
// * @param {number} itemId -
|
||||||
* @param {string} costMethod -
|
// * @param {string} costMethod -
|
||||||
*/
|
// */
|
||||||
constructor(
|
// constructor(
|
||||||
tenantId: number,
|
// tenantId: number,
|
||||||
startingDate: Date,
|
// startingDate: Date,
|
||||||
itemId: number,
|
// itemId: number,
|
||||||
costMethod: TCostMethod = 'FIFO'
|
// costMethod: TCostMethod = 'FIFO'
|
||||||
) {
|
// ) {
|
||||||
super(tenantId, startingDate, itemId);
|
// super(tenantId, startingDate, itemId);
|
||||||
|
|
||||||
this.startingDate = startingDate;
|
// this.startingDate = startingDate;
|
||||||
this.itemId = itemId;
|
// this.itemId = itemId;
|
||||||
this.costMethod = costMethod;
|
// this.costMethod = costMethod;
|
||||||
|
|
||||||
// Collect cost lots transactions to insert them to the storage in bulk.
|
// // Collect cost lots transactions to insert them to the storage in bulk.
|
||||||
this.costLotsTransactions= [];
|
// this.costLotsTransactions= [];
|
||||||
// Collect inventory transactions by item id.
|
// // Collect inventory transactions by item id.
|
||||||
this.inventoryByItem = {};
|
// this.inventoryByItem = {};
|
||||||
// Collection `IN` inventory tranaction by transaction id.
|
// // Collection `IN` inventory tranaction by transaction id.
|
||||||
this.inventoryINTrans = {};
|
// this.inventoryINTrans = {};
|
||||||
// Collects `IN` transactions.
|
// // Collects `IN` transactions.
|
||||||
this.inTransactions = [];
|
// this.inTransactions = [];
|
||||||
// Collects `OUT` transactions.
|
// // Collects `OUT` transactions.
|
||||||
this.outTransactions = [];
|
// this.outTransactions = [];
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Computes items costs from the given date using FIFO or LIFO cost method.
|
// * Computes items costs from the given date using FIFO or LIFO cost method.
|
||||||
* --------
|
// * --------
|
||||||
* - Revert the inventory lots after the given date.
|
// * - Revert the inventory lots after the given date.
|
||||||
* - Remove all the journal entries from the inventory transactions
|
// * - Remove all the journal entries from the inventory transactions
|
||||||
* after the given date.
|
// * after the given date.
|
||||||
* - Re-tracking the inventory lots from inventory transactions.
|
// * - Re-tracking the inventory lots from inventory transactions.
|
||||||
* - Re-write the journal entries from the given inventory transactions.
|
// * - Re-write the journal entries from the given inventory transactions.
|
||||||
* @async
|
// * @async
|
||||||
* @return {void}
|
// * @return {void}
|
||||||
*/
|
// */
|
||||||
public async computeItemCost(): Promise<any> {
|
// public async computeItemCost(): Promise<any> {
|
||||||
await this.revertInventoryLots(this.startingDate);
|
// await this.revertInventoryLots(this.startingDate);
|
||||||
await this.fetchInvINTransactions();
|
// await this.fetchInvINTransactions();
|
||||||
await this.fetchInvOUTTransactions();
|
// await this.fetchInvOUTTransactions();
|
||||||
await this.fetchRevertInvJReferenceIds();
|
// await this.fetchRevertInvJReferenceIds();
|
||||||
await this.fetchItemsMapped();
|
// await this.fetchItemsMapped();
|
||||||
|
|
||||||
this.trackingInventoryINLots(this.inTransactions);
|
// this.trackingInventoryINLots(this.inTransactions);
|
||||||
this.trackingInventoryOUTLots(this.outTransactions);
|
// this.trackingInventoryOUTLots(this.outTransactions);
|
||||||
|
|
||||||
// Re-tracking the inventory `IN` and `OUT` lots costs.
|
// // Re-tracking the inventory `IN` and `OUT` lots costs.
|
||||||
const storedTrackedInvLotsOper = this.storeInventoryLotsCost(
|
// const storedTrackedInvLotsOper = this.storeInventoryLotsCost(
|
||||||
this.costLotsTransactions,
|
// this.costLotsTransactions,
|
||||||
);
|
// );
|
||||||
return Promise.all([
|
// return Promise.all([
|
||||||
storedTrackedInvLotsOper,
|
// storedTrackedInvLotsOper,
|
||||||
]);
|
// ]);
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Fetched inventory transactions that has date from the starting date and
|
// * Fetched inventory transactions that has date from the starting date and
|
||||||
* fetches available IN LOTs transactions that has remaining bigger than zero.
|
// * fetches available IN LOTs transactions that has remaining bigger than zero.
|
||||||
* @private
|
// * @private
|
||||||
*/
|
// */
|
||||||
private async fetchInvINTransactions() {
|
// private async fetchInvINTransactions() {
|
||||||
const { InventoryTransaction, InventoryLotCostTracker } = this.tenantModels;
|
// const { InventoryTransaction, InventoryLotCostTracker } = this.tenantModels;
|
||||||
|
|
||||||
const commonBuilder = (builder: any) => {
|
// const commonBuilder = (builder: any) => {
|
||||||
builder.orderBy('date', (this.costMethod === 'LIFO') ? 'DESC': 'ASC');
|
// builder.orderBy('date', (this.costMethod === 'LIFO') ? 'DESC': 'ASC');
|
||||||
builder.where('item_id', this.itemId);
|
// builder.where('item_id', this.itemId);
|
||||||
};
|
// };
|
||||||
const afterInvTransactions: IInventoryTransaction[] =
|
// const afterInvTransactions: IInventoryTransaction[] =
|
||||||
await InventoryTransaction.query()
|
// await InventoryTransaction.query()
|
||||||
.modify('filterDateRange', this.startingDate)
|
// .modify('filterDateRange', this.startingDate)
|
||||||
.orderByRaw("FIELD(direction, 'IN', 'OUT')")
|
// .orderByRaw("FIELD(direction, 'IN', 'OUT')")
|
||||||
.onBuild(commonBuilder)
|
// .onBuild(commonBuilder)
|
||||||
.orderBy('lot_number', (this.costMethod === 'LIFO') ? 'DESC' : 'ASC')
|
// .orderBy('lot_number', (this.costMethod === 'LIFO') ? 'DESC' : 'ASC')
|
||||||
.withGraphFetched('item');
|
// .withGraphFetched('item');
|
||||||
|
|
||||||
const availableINLots: IInventoryLotCost[] =
|
// const availableINLots: IInventoryLotCost[] =
|
||||||
await InventoryLotCostTracker.query()
|
// await InventoryLotCostTracker.query()
|
||||||
.modify('filterDateRange', null, this.startingDate)
|
// .modify('filterDateRange', null, this.startingDate)
|
||||||
.orderBy('date', 'ASC')
|
// .orderBy('date', 'ASC')
|
||||||
.where('direction', 'IN')
|
// .where('direction', 'IN')
|
||||||
.orderBy('lot_number', 'ASC')
|
// .orderBy('lot_number', 'ASC')
|
||||||
.onBuild(commonBuilder)
|
// .onBuild(commonBuilder)
|
||||||
.whereNot('remaining', 0);
|
// .whereNot('remaining', 0);
|
||||||
|
|
||||||
this.inTransactions = [
|
// this.inTransactions = [
|
||||||
...availableINLots.map((trans) => ({ lotTransId: trans.id, ...trans })),
|
// ...availableINLots.map((trans) => ({ lotTransId: trans.id, ...trans })),
|
||||||
...afterInvTransactions.map((trans) => ({ invTransId: trans.id, ...trans })),
|
// ...afterInvTransactions.map((trans) => ({ invTransId: trans.id, ...trans })),
|
||||||
];
|
// ];
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Fetches inventory OUT transactions that has date from the starting date.
|
// * Fetches inventory OUT transactions that has date from the starting date.
|
||||||
* @private
|
// * @private
|
||||||
*/
|
// */
|
||||||
private async fetchInvOUTTransactions() {
|
// private async fetchInvOUTTransactions() {
|
||||||
const { InventoryTransaction } = this.tenantModels;
|
// const { InventoryTransaction } = this.tenantModels;
|
||||||
|
|
||||||
const afterOUTTransactions: IInventoryTransaction[] =
|
// const afterOUTTransactions: IInventoryTransaction[] =
|
||||||
await InventoryTransaction.query()
|
// await InventoryTransaction.query()
|
||||||
.modify('filterDateRange', this.startingDate)
|
// .modify('filterDateRange', this.startingDate)
|
||||||
.orderBy('date', 'ASC')
|
// .orderBy('date', 'ASC')
|
||||||
.orderBy('lot_number', 'ASC')
|
// .orderBy('lot_number', 'ASC')
|
||||||
.where('item_id', this.itemId)
|
// .where('item_id', this.itemId)
|
||||||
.where('direction', 'OUT')
|
// .where('direction', 'OUT')
|
||||||
.withGraphFetched('item');
|
// .withGraphFetched('item');
|
||||||
|
|
||||||
this.outTransactions = [ ...afterOUTTransactions ];
|
// this.outTransactions = [ ...afterOUTTransactions ];
|
||||||
}
|
// }
|
||||||
|
|
||||||
private async fetchItemsMapped() {
|
// private async fetchItemsMapped() {
|
||||||
const itemsIds = chain(this.inTransactions).map((e) => e.itemId).uniq().value();
|
// const itemsIds = chain(this.inTransactions).map((e) => e.itemId).uniq().value();
|
||||||
const { Item } = this.tenantModels;
|
// const { Item } = this.tenantModels;
|
||||||
const storedItems = await Item.query()
|
// const storedItems = await Item.query()
|
||||||
.where('type', 'inventory')
|
// .where('type', 'inventory')
|
||||||
.whereIn('id', itemsIds);
|
// .whereIn('id', itemsIds);
|
||||||
|
|
||||||
this.itemsById = new Map(storedItems.map((item: any) => [item.id, item]));
|
// this.itemsById = new Map(storedItems.map((item: any) => [item.id, item]));
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Fetch the inventory transactions that should revert its journal entries.
|
// * Fetch the inventory transactions that should revert its journal entries.
|
||||||
* @private
|
// * @private
|
||||||
*/
|
// */
|
||||||
private async fetchRevertInvJReferenceIds() {
|
// private async fetchRevertInvJReferenceIds() {
|
||||||
const { InventoryTransaction } = this.tenantModels;
|
// const { InventoryTransaction } = this.tenantModels;
|
||||||
const revertJEntriesTransactions: IInventoryTransaction[] =
|
// const revertJEntriesTransactions: IInventoryTransaction[] =
|
||||||
await InventoryTransaction.query()
|
// await InventoryTransaction.query()
|
||||||
.select(['transactionId', 'transactionType'])
|
// .select(['transactionId', 'transactionType'])
|
||||||
.modify('filterDateRange', this.startingDate)
|
// .modify('filterDateRange', this.startingDate)
|
||||||
.where('direction', 'OUT')
|
// .where('direction', 'OUT')
|
||||||
.where('item_id', this.itemId);
|
// .where('item_id', this.itemId);
|
||||||
|
|
||||||
this.revertJEntriesTransactions = revertJEntriesTransactions;
|
// this.revertJEntriesTransactions = revertJEntriesTransactions;
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Revert the inventory lots to the given date by removing the inventory lots
|
// * Revert the inventory lots to the given date by removing the inventory lots
|
||||||
* transactions after the given date and increment the remaining that
|
// * transactions after the given date and increment the remaining that
|
||||||
* associate to lot number.
|
// * associate to lot number.
|
||||||
* @async
|
// * @async
|
||||||
* @return {Promise}
|
// * @return {Promise}
|
||||||
*/
|
// */
|
||||||
public async revertInventoryLots(startingDate: Date) {
|
// public async revertInventoryLots(startingDate: Date) {
|
||||||
const { InventoryLotCostTracker } = this.tenantModels;
|
// const { InventoryLotCostTracker } = this.tenantModels;
|
||||||
const asyncOpers: any[] = [];
|
// const asyncOpers: any[] = [];
|
||||||
const inventoryLotsTrans = await InventoryLotCostTracker.query()
|
// const inventoryLotsTrans = await InventoryLotCostTracker.query()
|
||||||
.modify('filterDateRange', this.startingDate)
|
// .modify('filterDateRange', this.startingDate)
|
||||||
.orderBy('date', 'DESC')
|
// .orderBy('date', 'DESC')
|
||||||
.where('item_id', this.itemId)
|
// .where('item_id', this.itemId)
|
||||||
.where('direction', 'OUT');
|
// .where('direction', 'OUT');
|
||||||
|
|
||||||
const deleteInvLotsTrans = InventoryLotCostTracker.query()
|
// const deleteInvLotsTrans = InventoryLotCostTracker.query()
|
||||||
.modify('filterDateRange', this.startingDate)
|
// .modify('filterDateRange', this.startingDate)
|
||||||
.where('item_id', this.itemId)
|
// .where('item_id', this.itemId)
|
||||||
.delete();
|
// .delete();
|
||||||
|
|
||||||
inventoryLotsTrans.forEach((inventoryLot: IInventoryLotCost) => {
|
// inventoryLotsTrans.forEach((inventoryLot: IInventoryLotCost) => {
|
||||||
if (!inventoryLot.lotNumber) { return; }
|
// if (!inventoryLot.lotNumber) { return; }
|
||||||
|
|
||||||
const incrementOper = InventoryLotCostTracker.query()
|
// const incrementOper = InventoryLotCostTracker.query()
|
||||||
.where('lot_number', inventoryLot.lotNumber)
|
// .where('lot_number', inventoryLot.lotNumber)
|
||||||
.where('direction', 'IN')
|
// .where('direction', 'IN')
|
||||||
.increment('remaining', inventoryLot.quantity);
|
// .increment('remaining', inventoryLot.quantity);
|
||||||
|
|
||||||
asyncOpers.push(incrementOper);
|
// asyncOpers.push(incrementOper);
|
||||||
});
|
// });
|
||||||
return Promise.all([deleteInvLotsTrans, ...asyncOpers]);
|
// return Promise.all([deleteInvLotsTrans, ...asyncOpers]);
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Tracking inventory `IN` lots transactions.
|
// * Tracking inventory `IN` lots transactions.
|
||||||
* @public
|
// * @public
|
||||||
* @param {IInventoryTransaction[]} inventoryTransactions -
|
// * @param {IInventoryTransaction[]} inventoryTransactions -
|
||||||
* @return {void}
|
// * @return {void}
|
||||||
*/
|
// */
|
||||||
public trackingInventoryINLots(
|
// public trackingInventoryINLots(
|
||||||
inventoryTransactions: IInventoryTransaction[],
|
// inventoryTransactions: IInventoryTransaction[],
|
||||||
) {
|
// ) {
|
||||||
inventoryTransactions.forEach((transaction: IInventoryTransaction) => {
|
// inventoryTransactions.forEach((transaction: IInventoryTransaction) => {
|
||||||
const { itemId, id } = transaction;
|
// const { itemId, id } = transaction;
|
||||||
(this.inventoryByItem[itemId] || (this.inventoryByItem[itemId] = []));
|
// (this.inventoryByItem[itemId] || (this.inventoryByItem[itemId] = []));
|
||||||
|
|
||||||
const commonLotTransaction: IInventoryLotCost = {
|
// const commonLotTransaction: IInventoryLotCost = {
|
||||||
...pick(transaction, [
|
// ...pick(transaction, [
|
||||||
'date', 'rate', 'itemId', 'quantity', 'invTransId', 'lotTransId',
|
// 'date', 'rate', 'itemId', 'quantity', 'invTransId', 'lotTransId',
|
||||||
'direction', 'transactionType', 'transactionId', 'lotNumber', 'remaining'
|
// 'direction', 'transactionType', 'transactionId', 'lotNumber', 'remaining'
|
||||||
]),
|
// ]),
|
||||||
};
|
// };
|
||||||
this.inventoryByItem[itemId].push(id);
|
// this.inventoryByItem[itemId].push(id);
|
||||||
this.inventoryINTrans[id] = {
|
// this.inventoryINTrans[id] = {
|
||||||
...commonLotTransaction,
|
// ...commonLotTransaction,
|
||||||
decrement: 0,
|
// decrement: 0,
|
||||||
remaining: commonLotTransaction.remaining || commonLotTransaction.quantity,
|
// remaining: commonLotTransaction.remaining || commonLotTransaction.quantity,
|
||||||
};
|
// };
|
||||||
this.costLotsTransactions.push(this.inventoryINTrans[id]);
|
// this.costLotsTransactions.push(this.inventoryINTrans[id]);
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Tracking inventory `OUT` lots transactions.
|
// * Tracking inventory `OUT` lots transactions.
|
||||||
* @public
|
// * @public
|
||||||
* @param {IInventoryTransaction[]} inventoryTransactions -
|
// * @param {IInventoryTransaction[]} inventoryTransactions -
|
||||||
* @return {void}
|
// * @return {void}
|
||||||
*/
|
// */
|
||||||
public trackingInventoryOUTLots(
|
// public trackingInventoryOUTLots(
|
||||||
inventoryTransactions: IInventoryTransaction[],
|
// inventoryTransactions: IInventoryTransaction[],
|
||||||
) {
|
// ) {
|
||||||
inventoryTransactions.forEach((transaction: IInventoryTransaction) => {
|
// inventoryTransactions.forEach((transaction: IInventoryTransaction) => {
|
||||||
const { itemId, id } = transaction;
|
// const { itemId, id } = transaction;
|
||||||
(this.inventoryByItem[itemId] || (this.inventoryByItem[itemId] = []));
|
// (this.inventoryByItem[itemId] || (this.inventoryByItem[itemId] = []));
|
||||||
|
|
||||||
const commonLotTransaction: IInventoryLotCost = {
|
// const commonLotTransaction: IInventoryLotCost = {
|
||||||
...pick(transaction, [
|
// ...pick(transaction, [
|
||||||
'date', 'rate', 'itemId', 'quantity', 'invTransId', 'lotTransId', 'entryId',
|
// 'date', 'rate', 'itemId', 'quantity', 'invTransId', 'lotTransId', 'entryId',
|
||||||
'direction', 'transactionType', 'transactionId', 'lotNumber', 'remaining'
|
// 'direction', 'transactionType', 'transactionId', 'lotNumber', 'remaining'
|
||||||
]),
|
// ]),
|
||||||
};
|
// };
|
||||||
let invRemaining = transaction.quantity;
|
// let invRemaining = transaction.quantity;
|
||||||
const idsShouldDel: number[] = [];
|
// const idsShouldDel: number[] = [];
|
||||||
|
|
||||||
this.inventoryByItem?.[itemId]?.some((_invTransactionId: number) => {
|
// this.inventoryByItem?.[itemId]?.some((_invTransactionId: number) => {
|
||||||
const _invINTransaction = this.inventoryINTrans[_invTransactionId];
|
// const _invINTransaction = this.inventoryINTrans[_invTransactionId];
|
||||||
|
|
||||||
// Can't continue if the IN transaction remaining equals zero.
|
// // Can't continue if the IN transaction remaining equals zero.
|
||||||
if (invRemaining <= 0) { return true; }
|
// if (invRemaining <= 0) { return true; }
|
||||||
|
|
||||||
// Can't continue if the IN transaction date is after the current transaction date.
|
// // Can't continue if the IN transaction date is after the current transaction date.
|
||||||
if (moment(_invINTransaction.date).isAfter(transaction.date)) {
|
// if (moment(_invINTransaction.date).isAfter(transaction.date)) {
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
// Detarmines the 'OUT' lot tranasctions whether bigger than 'IN' remaining transaction.
|
// // Detarmines the 'OUT' lot tranasctions whether bigger than 'IN' remaining transaction.
|
||||||
const biggerThanRemaining = (_invINTransaction.remaining - transaction.quantity) > 0;
|
// const biggerThanRemaining = (_invINTransaction.remaining - transaction.quantity) > 0;
|
||||||
const decrement = (biggerThanRemaining) ? transaction.quantity : _invINTransaction.remaining;
|
// const decrement = (biggerThanRemaining) ? transaction.quantity : _invINTransaction.remaining;
|
||||||
const maxDecrement = Math.min(decrement, invRemaining);
|
// const maxDecrement = Math.min(decrement, invRemaining);
|
||||||
const cost = maxDecrement * _invINTransaction.rate;
|
// const cost = maxDecrement * _invINTransaction.rate;
|
||||||
|
|
||||||
_invINTransaction.decrement += maxDecrement;
|
// _invINTransaction.decrement += maxDecrement;
|
||||||
_invINTransaction.remaining = Math.max(
|
// _invINTransaction.remaining = Math.max(
|
||||||
_invINTransaction.remaining - maxDecrement,
|
// _invINTransaction.remaining - maxDecrement,
|
||||||
0,
|
// 0,
|
||||||
);
|
// );
|
||||||
invRemaining = Math.max(invRemaining - maxDecrement, 0);
|
// invRemaining = Math.max(invRemaining - maxDecrement, 0);
|
||||||
|
|
||||||
this.costLotsTransactions.push({
|
// this.costLotsTransactions.push({
|
||||||
...commonLotTransaction,
|
// ...commonLotTransaction,
|
||||||
cost,
|
// cost,
|
||||||
quantity: maxDecrement,
|
// quantity: maxDecrement,
|
||||||
lotNumber: _invINTransaction.lotNumber,
|
// lotNumber: _invINTransaction.lotNumber,
|
||||||
});
|
// });
|
||||||
// Pop the 'IN' lots that has zero remaining.
|
// // Pop the 'IN' lots that has zero remaining.
|
||||||
if (_invINTransaction.remaining === 0) {
|
// if (_invINTransaction.remaining === 0) {
|
||||||
idsShouldDel.push(_invTransactionId);
|
// idsShouldDel.push(_invTransactionId);
|
||||||
}
|
// }
|
||||||
return false;
|
// return false;
|
||||||
});
|
// });
|
||||||
if (invRemaining > 0) {
|
// if (invRemaining > 0) {
|
||||||
this.costLotsTransactions.push({
|
// this.costLotsTransactions.push({
|
||||||
...commonLotTransaction,
|
// ...commonLotTransaction,
|
||||||
quantity: invRemaining,
|
// quantity: invRemaining,
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
this.removeInventoryItems(itemId, idsShouldDel);
|
// this.removeInventoryItems(itemId, idsShouldDel);
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Remove inventory transactions for specific item id.
|
// * Remove inventory transactions for specific item id.
|
||||||
* @private
|
// * @private
|
||||||
* @param {number} itemId
|
// * @param {number} itemId
|
||||||
* @param {number[]} idsShouldDel
|
// * @param {number[]} idsShouldDel
|
||||||
* @return {void}
|
// * @return {void}
|
||||||
*/
|
// */
|
||||||
private removeInventoryItems(itemId: number, idsShouldDel: number[]) {
|
// private removeInventoryItems(itemId: number, idsShouldDel: number[]) {
|
||||||
// Remove the IN transactions that has zero remaining amount.
|
// // Remove the IN transactions that has zero remaining amount.
|
||||||
this.inventoryByItem[itemId] = this.inventoryByItem?.[itemId]
|
// this.inventoryByItem[itemId] = this.inventoryByItem?.[itemId]
|
||||||
?.filter((transId: number) => idsShouldDel.indexOf(transId) === -1);
|
// ?.filter((transId: number) => idsShouldDel.indexOf(transId) === -1);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
@@ -1,149 +1,149 @@
|
|||||||
import { keyBy, get } from 'lodash';
|
// import { keyBy, get } from 'lodash';
|
||||||
import { Knex } from 'knex';
|
// import { Knex } from 'knex';
|
||||||
import * as R from 'ramda';
|
// import * as R from 'ramda';
|
||||||
import { IInventoryItemCostMeta } from './types/InventoryCost.types';
|
// import { IInventoryItemCostMeta } from './types/InventoryCost.types';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
// import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { InventoryTransaction } from './models/InventoryTransaction';
|
// import { InventoryTransaction } from './models/InventoryTransaction';
|
||||||
import { InventoryCostLotTracker } from './models/InventoryCostLotTracker';
|
// import { InventoryCostLotTracker } from './models/InventoryCostLotTracker';
|
||||||
import { Item } from '../Items/models/Item';
|
// import { Item } from '../Items/models/Item';
|
||||||
|
|
||||||
@Injectable()
|
// @Injectable()
|
||||||
export class InventoryItemCostService {
|
// export class InventoryItemCostService {
|
||||||
constructor(
|
// constructor(
|
||||||
@Inject(InventoryTransaction.name)
|
// @Inject(InventoryTransaction.name)
|
||||||
private readonly inventoryTransactionModel: typeof InventoryTransaction,
|
// private readonly inventoryTransactionModel: typeof InventoryTransaction,
|
||||||
|
|
||||||
@Inject(InventoryCostLotTracker.name)
|
// @Inject(InventoryCostLotTracker.name)
|
||||||
private readonly inventoryCostLotTrackerModel: typeof InventoryCostLotTracker,
|
// private readonly inventoryCostLotTrackerModel: typeof InventoryCostLotTracker,
|
||||||
|
|
||||||
@Inject(Item.name)
|
// @Inject(Item.name)
|
||||||
private readonly itemModel: typeof Item,
|
// private readonly itemModel: typeof Item,
|
||||||
) {}
|
// ) {}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Common query of items inventory valuation.
|
// * Common query of items inventory valuation.
|
||||||
* @param {number[]} itemsIds -
|
// * @param {number[]} itemsIds -
|
||||||
* @param {Date} date -
|
// * @param {Date} date -
|
||||||
* @param {Knex.QueryBuilder} builder -
|
// * @param {Knex.QueryBuilder} builder -
|
||||||
*/
|
// */
|
||||||
private itemsInventoryValuationCommonQuery = R.curry(
|
// private itemsInventoryValuationCommonQuery = R.curry(
|
||||||
(itemsIds: number[], date: Date, builder: Knex.QueryBuilder) => {
|
// (itemsIds: number[], date: Date, builder: Knex.QueryBuilder) => {
|
||||||
if (date) {
|
// if (date) {
|
||||||
builder.where('date', '<', date);
|
// builder.where('date', '<', date);
|
||||||
}
|
// }
|
||||||
builder.whereIn('item_id', itemsIds);
|
// builder.whereIn('item_id', itemsIds);
|
||||||
builder.sum('rate as rate');
|
// builder.sum('rate as rate');
|
||||||
builder.sum('quantity as quantity');
|
// builder.sum('quantity as quantity');
|
||||||
builder.sum('cost as cost');
|
// builder.sum('cost as cost');
|
||||||
|
|
||||||
builder.groupBy('item_id');
|
// builder.groupBy('item_id');
|
||||||
builder.select(['item_id']);
|
// builder.select(['item_id']);
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
*
|
// *
|
||||||
* @param {} INValuationMap -
|
// * @param {} INValuationMap -
|
||||||
* @param {} OUTValuationMap -
|
// * @param {} OUTValuationMap -
|
||||||
* @param {number} itemId
|
// * @param {number} itemId
|
||||||
*/
|
// */
|
||||||
private getItemInventoryMeta = R.curry(
|
// private getItemInventoryMeta = R.curry(
|
||||||
(
|
// (
|
||||||
INValuationMap,
|
// INValuationMap,
|
||||||
OUTValuationMap,
|
// OUTValuationMap,
|
||||||
itemId: number
|
// itemId: number
|
||||||
): IInventoryItemCostMeta => {
|
// ): IInventoryItemCostMeta => {
|
||||||
const INCost = get(INValuationMap, `[${itemId}].cost`, 0);
|
// const INCost = get(INValuationMap, `[${itemId}].cost`, 0);
|
||||||
const INQuantity = get(INValuationMap, `[${itemId}].quantity`, 0);
|
// const INQuantity = get(INValuationMap, `[${itemId}].quantity`, 0);
|
||||||
|
|
||||||
const OUTCost = get(OUTValuationMap, `[${itemId}].cost`, 0);
|
// const OUTCost = get(OUTValuationMap, `[${itemId}].cost`, 0);
|
||||||
const OUTQuantity = get(OUTValuationMap, `[${itemId}].quantity`, 0);
|
// const OUTQuantity = get(OUTValuationMap, `[${itemId}].quantity`, 0);
|
||||||
|
|
||||||
const valuation = INCost - OUTCost;
|
// const valuation = INCost - OUTCost;
|
||||||
const quantity = INQuantity - OUTQuantity;
|
// const quantity = INQuantity - OUTQuantity;
|
||||||
const average = quantity ? valuation / quantity : 0;
|
// const average = quantity ? valuation / quantity : 0;
|
||||||
|
|
||||||
return { itemId, valuation, quantity, average };
|
// return { itemId, valuation, quantity, average };
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
*
|
// *
|
||||||
* @param {number} tenantId
|
// * @param {number} tenantId
|
||||||
* @param {number} itemsId
|
// * @param {number} itemsId
|
||||||
* @param {Date} date
|
// * @param {Date} date
|
||||||
* @returns
|
// * @returns
|
||||||
*/
|
// */
|
||||||
private getItemsInventoryINAndOutAggregated = (
|
// private getItemsInventoryINAndOutAggregated = (
|
||||||
itemsId: number[],
|
// itemsId: number[],
|
||||||
date: Date
|
// date: Date
|
||||||
): Promise<any> => {
|
// ): Promise<any> => {
|
||||||
|
|
||||||
const commonBuilder = this.itemsInventoryValuationCommonQuery(
|
// const commonBuilder = this.itemsInventoryValuationCommonQuery(
|
||||||
itemsId,
|
// itemsId,
|
||||||
date
|
// date
|
||||||
);
|
// );
|
||||||
const INValuationOper = this.inventoryCostLotTrackerModel.query()
|
// const INValuationOper = this.inventoryCostLotTrackerModel.query()
|
||||||
.onBuild(commonBuilder)
|
// .onBuild(commonBuilder)
|
||||||
.where('direction', 'IN');
|
// .where('direction', 'IN');
|
||||||
|
|
||||||
const OUTValuationOper = this.inventoryCostLotTrackerModel.query()
|
// const OUTValuationOper = this.inventoryCostLotTrackerModel.query()
|
||||||
.onBuild(commonBuilder)
|
// .onBuild(commonBuilder)
|
||||||
.where('direction', 'OUT');
|
// .where('direction', 'OUT');
|
||||||
|
|
||||||
return Promise.all([OUTValuationOper, INValuationOper]);
|
// return Promise.all([OUTValuationOper, INValuationOper]);
|
||||||
};
|
// };
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
*
|
// *
|
||||||
* @param {number} tenantId -
|
// * @param {number} tenantId -
|
||||||
* @param {number[]} itemsIds -
|
// * @param {number[]} itemsIds -
|
||||||
* @param {Date} date -
|
// * @param {Date} date -
|
||||||
*/
|
// */
|
||||||
private getItemsInventoryInOutMap = async (
|
// private getItemsInventoryInOutMap = async (
|
||||||
itemsId: number[],
|
// itemsId: number[],
|
||||||
date: Date
|
// date: Date
|
||||||
) => {
|
// ) => {
|
||||||
const [OUTValuation, INValuation] =
|
// const [OUTValuation, INValuation] =
|
||||||
await this.getItemsInventoryINAndOutAggregated(itemsId, date);
|
// await this.getItemsInventoryINAndOutAggregated(itemsId, date);
|
||||||
|
|
||||||
const OUTValuationMap = keyBy(OUTValuation, 'itemId');
|
// const OUTValuationMap = keyBy(OUTValuation, 'itemId');
|
||||||
const INValuationMap = keyBy(INValuation, 'itemId');
|
// const INValuationMap = keyBy(INValuation, 'itemId');
|
||||||
|
|
||||||
return [OUTValuationMap, INValuationMap];
|
// return [OUTValuationMap, INValuationMap];
|
||||||
};
|
// };
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
*
|
// *
|
||||||
* @param {number} tenantId
|
// * @param {number} tenantId
|
||||||
* @param {number} itemId
|
// * @param {number} itemId
|
||||||
* @param {Date} date
|
// * @param {Date} date
|
||||||
* @returns {Promise<Map<number, IInventoryItemCostMeta>>}
|
// * @returns {Promise<Map<number, IInventoryItemCostMeta>>}
|
||||||
*/
|
// */
|
||||||
public getItemsInventoryValuation = async (
|
// public getItemsInventoryValuation = async (
|
||||||
itemsId: number[],
|
// itemsId: number[],
|
||||||
date: Date
|
// date: Date
|
||||||
): Promise<Map<number, IInventoryItemCostMeta>> => {
|
// ): Promise<Map<number, IInventoryItemCostMeta>> => {
|
||||||
// Retrieves the inventory items.
|
// // Retrieves the inventory items.
|
||||||
const items = await this.itemModel.query()
|
// const items = await this.itemModel.query()
|
||||||
.whereIn('id', itemsId)
|
// .whereIn('id', itemsId)
|
||||||
.where('type', 'inventory');
|
// .where('type', 'inventory');
|
||||||
|
|
||||||
// Retrieves the inventory items ids.
|
// // Retrieves the inventory items ids.
|
||||||
const inventoryItemsIds: number[] = items.map((item) => item.id);
|
// const inventoryItemsIds: number[] = items.map((item) => item.id);
|
||||||
|
|
||||||
// Retreives the items inventory IN/OUT map.
|
// // Retreives the items inventory IN/OUT map.
|
||||||
const [OUTValuationMap, INValuationMap] =
|
// const [OUTValuationMap, INValuationMap] =
|
||||||
await this.getItemsInventoryInOutMap(itemsId, date);
|
// await this.getItemsInventoryInOutMap(itemsId, date);
|
||||||
|
|
||||||
const getItemValuation = this.getItemInventoryMeta(
|
// const getItemValuation = this.getItemInventoryMeta(
|
||||||
INValuationMap,
|
// INValuationMap,
|
||||||
OUTValuationMap
|
// OUTValuationMap
|
||||||
);
|
// );
|
||||||
const itemsValuations = inventoryItemsIds.map(getItemValuation);
|
// const itemsValuations = inventoryItemsIds.map(getItemValuation);
|
||||||
const itemsValuationsMap = new Map(
|
// const itemsValuationsMap = new Map(
|
||||||
itemsValuations.map((i) => [i.itemId, i])
|
// itemsValuations.map((i) => [i.itemId, i])
|
||||||
);
|
// );
|
||||||
return itemsValuationsMap;
|
// return itemsValuationsMap;
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { chain } from 'lodash';
|
import { chain } from 'lodash';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
import { IItemEntryTransactionType } from '../TransactionItemEntry/ItemEntry.types';
|
||||||
|
import { TInventoryTransactionDirection } from './types/InventoryCost.types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Grpups by transaction type and id the inventory transactions.
|
* Grpups by transaction type and id the inventory transactions.
|
||||||
@@ -6,10 +9,48 @@ import { chain } from 'lodash';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function groupInventoryTransactionsByTypeId(
|
export function groupInventoryTransactionsByTypeId(
|
||||||
transactions: { transactionType: string; transactionId: number }[]
|
transactions: { transactionType: string; transactionId: number }[],
|
||||||
): { transactionType: string; transactionId: number }[][] {
|
): { transactionType: string; transactionId: number }[][] {
|
||||||
return chain(transactions)
|
return chain(transactions)
|
||||||
.groupBy((t) => `${t.transactionType}-${t.transactionId}`)
|
.groupBy((t) => `${t.transactionType}-${t.transactionId}`)
|
||||||
.values()
|
.values()
|
||||||
.value();
|
.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the items entries to inventory transactions.
|
||||||
|
*/
|
||||||
|
export function transformItemEntriesToInventory(transaction: {
|
||||||
|
transactionId: number;
|
||||||
|
transactionType: IItemEntryTransactionType;
|
||||||
|
transactionNumber?: string;
|
||||||
|
|
||||||
|
exchangeRate?: number;
|
||||||
|
|
||||||
|
warehouseId: number | null;
|
||||||
|
|
||||||
|
date: Date | string;
|
||||||
|
direction: TInventoryTransactionDirection;
|
||||||
|
entries: IItemEntry[];
|
||||||
|
createdAt: Date;
|
||||||
|
}): IInventoryTransaction[] {
|
||||||
|
const exchangeRate = transaction.exchangeRate || 1;
|
||||||
|
|
||||||
|
return transaction.entries.map((entry: IItemEntry) => ({
|
||||||
|
...pick(entry, ['itemId', 'quantity']),
|
||||||
|
rate: entry.rate * exchangeRate,
|
||||||
|
transactionType: transaction.transactionType,
|
||||||
|
transactionId: transaction.transactionId,
|
||||||
|
direction: transaction.direction,
|
||||||
|
date: transaction.date,
|
||||||
|
entryId: entry.id,
|
||||||
|
createdAt: transaction.createdAt,
|
||||||
|
costAccountId: entry.costAccountId,
|
||||||
|
|
||||||
|
warehouseId: entry.warehouseId || transaction.warehouseId,
|
||||||
|
meta: {
|
||||||
|
transactionNumber: transaction.transactionNumber,
|
||||||
|
description: entry.description,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { InventoryService } from '@/modules/InventoryCost/Inventory';
|
import { InventoryTransactionsService } from '@/modules/InventoryCost/InventoryTransactions.service';
|
||||||
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
|
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
@@ -8,7 +8,7 @@ import { SaleInvoice } from '../../models/SaleInvoice';
|
|||||||
export class InvoiceInventoryTransactions {
|
export class InvoiceInventoryTransactions {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly itemsEntriesService: ItemsEntriesService,
|
private readonly itemsEntriesService: ItemsEntriesService,
|
||||||
private readonly inventoryService: InventoryService,
|
private readonly inventoryService: InventoryTransactionsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { VendorCredit } from '../models/VendorCredit';
|
import { VendorCredit } from '../models/VendorCredit';
|
||||||
import { InventoryService } from '@/modules/InventoryCost/Inventory';
|
import { InventoryTransactionsService } from '@/modules/InventoryCost/InventoryTransactions.service';
|
||||||
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
|
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VendorCreditInventoryTransactions {
|
export class VendorCreditInventoryTransactions {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly inventoryService: InventoryService,
|
private readonly inventoryService: InventoryTransactionsService,
|
||||||
private readonly itemsEntriesService: ItemsEntriesService
|
private readonly itemsEntriesService: ItemsEntriesService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import { VendorValidators } from './commands/VendorValidators';
|
|||||||
import { VendorsApplication } from './VendorsApplication.service';
|
import { VendorsApplication } from './VendorsApplication.service';
|
||||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||||
import { VendorsController } from './Vendors.controller';
|
import { VendorsController } from './Vendors.controller';
|
||||||
|
import { GetVendorsService } from './queries/GetVendors.service';
|
||||||
|
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TenancyDatabaseModule],
|
imports: [TenancyDatabaseModule, DynamicListModule],
|
||||||
controllers: [VendorsController],
|
controllers: [VendorsController],
|
||||||
providers: [
|
providers: [
|
||||||
ActivateVendorService,
|
ActivateVendorService,
|
||||||
@@ -23,6 +25,7 @@ import { VendorsController } from './Vendors.controller';
|
|||||||
EditVendorService,
|
EditVendorService,
|
||||||
EditOpeningBalanceVendorService,
|
EditOpeningBalanceVendorService,
|
||||||
GetVendorService,
|
GetVendorService,
|
||||||
|
GetVendorsService,
|
||||||
VendorValidators,
|
VendorValidators,
|
||||||
DeleteVendorService,
|
DeleteVendorService,
|
||||||
VendorsApplication,
|
VendorsApplication,
|
||||||
|
|||||||
Reference in New Issue
Block a user