2022-11-09 18:42:00 +00:00
|
|
|
import { Fyo } from 'fyo';
|
|
|
|
import { StockQueue } from 'models/inventory/stockQueue';
|
2022-11-21 09:57:59 +00:00
|
|
|
import { ValuationMethod } from 'models/inventory/types';
|
2022-11-09 18:42:00 +00:00
|
|
|
import { ModelNameEnum } from 'models/types';
|
|
|
|
import { safeParseFloat, safeParseInt } from 'utils/index';
|
2022-11-25 09:21:58 +00:00
|
|
|
import {
|
|
|
|
ComputedStockLedgerEntry,
|
|
|
|
RawStockLedgerEntry,
|
|
|
|
StockBalanceEntry,
|
|
|
|
} from './types';
|
|
|
|
|
|
|
|
type Item = string;
|
|
|
|
type Location = string;
|
2023-02-28 06:01:04 +00:00
|
|
|
type Batch = string;
|
2022-11-09 18:42:00 +00:00
|
|
|
|
|
|
|
export async function getRawStockLedgerEntries(fyo: Fyo) {
|
|
|
|
const fieldnames = [
|
|
|
|
'name',
|
|
|
|
'date',
|
|
|
|
'item',
|
2023-02-28 06:01:04 +00:00
|
|
|
'batch',
|
2023-04-25 07:07:29 +00:00
|
|
|
'serialNo',
|
2022-11-09 18:42:00 +00:00
|
|
|
'rate',
|
|
|
|
'quantity',
|
|
|
|
'location',
|
|
|
|
'referenceName',
|
|
|
|
'referenceType',
|
|
|
|
];
|
|
|
|
|
|
|
|
return (await fyo.db.getAllRaw(ModelNameEnum.StockLedgerEntry, {
|
|
|
|
fields: fieldnames,
|
2023-01-05 05:44:05 +00:00
|
|
|
orderBy: ['date', 'created', 'name'],
|
2022-11-09 18:42:00 +00:00
|
|
|
order: 'asc',
|
|
|
|
})) as RawStockLedgerEntry[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getStockLedgerEntries(
|
2022-11-21 09:57:59 +00:00
|
|
|
rawSLEs: RawStockLedgerEntry[],
|
|
|
|
valuationMethod: ValuationMethod
|
2022-11-09 18:42:00 +00:00
|
|
|
): ComputedStockLedgerEntry[] {
|
|
|
|
const computedSLEs: ComputedStockLedgerEntry[] = [];
|
2023-02-27 13:24:49 +00:00
|
|
|
const stockQueues: Record<
|
|
|
|
Item,
|
2023-02-28 06:01:04 +00:00
|
|
|
Record<Location, Record<Batch, StockQueue>>
|
2023-02-27 13:24:49 +00:00
|
|
|
> = {};
|
2022-11-09 18:42:00 +00:00
|
|
|
|
|
|
|
for (const sle of rawSLEs) {
|
|
|
|
const name = safeParseInt(sle.name);
|
|
|
|
const date = new Date(sle.date);
|
|
|
|
const rate = safeParseFloat(sle.rate);
|
2023-02-27 13:24:49 +00:00
|
|
|
const { item, location, quantity, referenceName, referenceType } = sle;
|
2023-02-28 06:01:04 +00:00
|
|
|
const batch = sle.batch ?? '';
|
2023-04-25 07:07:29 +00:00
|
|
|
const serialNo = sle.serialNo ?? '';
|
2022-11-09 18:42:00 +00:00
|
|
|
|
|
|
|
if (quantity === 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
stockQueues[item] ??= {};
|
2023-02-27 13:24:49 +00:00
|
|
|
stockQueues[item][location] ??= {};
|
2023-02-28 06:01:04 +00:00
|
|
|
stockQueues[item][location][batch] ??= new StockQueue();
|
2022-11-09 18:42:00 +00:00
|
|
|
|
2023-02-28 06:01:04 +00:00
|
|
|
const q = stockQueues[item][location][batch];
|
2022-11-09 18:42:00 +00:00
|
|
|
const initialValue = q.value;
|
|
|
|
|
|
|
|
let incomingRate: number | null;
|
|
|
|
if (quantity > 0) {
|
|
|
|
incomingRate = q.inward(rate, quantity);
|
|
|
|
} else {
|
|
|
|
incomingRate = q.outward(-quantity);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (incomingRate === null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const balanceQuantity = q.quantity;
|
2022-11-21 09:57:59 +00:00
|
|
|
let valuationRate = q.fifo;
|
|
|
|
if (valuationMethod === ValuationMethod.MovingAverage) {
|
|
|
|
valuationRate = q.movingAverage;
|
|
|
|
}
|
|
|
|
|
2022-11-09 18:42:00 +00:00
|
|
|
const balanceValue = q.value;
|
|
|
|
const valueChange = balanceValue - initialValue;
|
|
|
|
|
|
|
|
const csle: ComputedStockLedgerEntry = {
|
|
|
|
name,
|
|
|
|
date,
|
|
|
|
|
|
|
|
item,
|
|
|
|
location,
|
2023-02-28 06:01:04 +00:00
|
|
|
batch,
|
2023-04-25 07:07:29 +00:00
|
|
|
serialNo,
|
2023-02-17 05:23:16 +00:00
|
|
|
|
2022-11-09 18:42:00 +00:00
|
|
|
quantity,
|
|
|
|
balanceQuantity,
|
|
|
|
|
|
|
|
incomingRate,
|
|
|
|
valuationRate,
|
|
|
|
|
|
|
|
balanceValue,
|
|
|
|
valueChange,
|
|
|
|
|
|
|
|
referenceName,
|
|
|
|
referenceType,
|
|
|
|
};
|
|
|
|
|
|
|
|
computedSLEs.push(csle);
|
|
|
|
}
|
|
|
|
|
|
|
|
return computedSLEs;
|
|
|
|
}
|
2022-11-25 09:21:58 +00:00
|
|
|
|
|
|
|
export function getStockBalanceEntries(
|
|
|
|
computedSLEs: ComputedStockLedgerEntry[],
|
|
|
|
filters: {
|
|
|
|
item?: string;
|
|
|
|
location?: string;
|
|
|
|
fromDate?: string;
|
|
|
|
toDate?: string;
|
2023-02-28 06:12:59 +00:00
|
|
|
batch?: string;
|
2022-11-25 09:21:58 +00:00
|
|
|
}
|
|
|
|
): StockBalanceEntry[] {
|
2023-02-27 14:11:39 +00:00
|
|
|
const sbeMap: Record<
|
|
|
|
Item,
|
2023-02-28 06:01:04 +00:00
|
|
|
Record<Location, Record<Batch, StockBalanceEntry>>
|
2023-02-27 14:11:39 +00:00
|
|
|
> = {};
|
2022-11-25 09:21:58 +00:00
|
|
|
|
|
|
|
const fromDate = filters.fromDate ? Date.parse(filters.fromDate) : null;
|
|
|
|
const toDate = filters.toDate ? Date.parse(filters.toDate) : null;
|
|
|
|
|
|
|
|
for (const sle of computedSLEs) {
|
|
|
|
if (filters.item && sle.item !== filters.item) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (filters.location && sle.location !== filters.location) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-02-28 06:12:59 +00:00
|
|
|
if (filters.batch && sle.batch !== filters.batch) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-02-28 06:01:04 +00:00
|
|
|
const batch = sle.batch || '';
|
2023-02-27 14:11:39 +00:00
|
|
|
|
2022-11-25 09:21:58 +00:00
|
|
|
sbeMap[sle.item] ??= {};
|
2023-02-27 14:11:39 +00:00
|
|
|
sbeMap[sle.item][sle.location] ??= {};
|
2023-02-28 06:01:04 +00:00
|
|
|
sbeMap[sle.item][sle.location][batch] ??= getSBE(
|
2023-01-18 12:07:31 +00:00
|
|
|
sle.item,
|
|
|
|
sle.location,
|
2023-02-28 06:01:04 +00:00
|
|
|
batch
|
2023-01-18 12:07:31 +00:00
|
|
|
);
|
2022-11-25 09:21:58 +00:00
|
|
|
const date = sle.date.valueOf();
|
|
|
|
|
|
|
|
if (fromDate && date < fromDate) {
|
2023-02-28 06:01:04 +00:00
|
|
|
const sbe = sbeMap[sle.item][sle.location][batch];
|
2022-11-25 09:21:58 +00:00
|
|
|
updateOpeningBalances(sbe, sle);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (toDate && date > toDate) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-02-28 06:01:04 +00:00
|
|
|
const sbe = sbeMap[sle.item][sle.location][batch];
|
2022-11-25 09:21:58 +00:00
|
|
|
updateCurrentBalances(sbe, sle);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Object.values(sbeMap)
|
2023-02-27 14:11:39 +00:00
|
|
|
.map((sbeBatched) =>
|
|
|
|
Object.values(sbeBatched).map((sbes) => Object.values(sbes))
|
|
|
|
)
|
|
|
|
.flat(2);
|
2022-11-25 09:21:58 +00:00
|
|
|
}
|
|
|
|
|
2023-01-18 12:07:31 +00:00
|
|
|
function getSBE(
|
|
|
|
item: string,
|
|
|
|
location: string,
|
2023-02-28 06:01:04 +00:00
|
|
|
batch: string
|
2023-01-18 12:07:31 +00:00
|
|
|
): StockBalanceEntry {
|
2022-11-25 09:21:58 +00:00
|
|
|
return {
|
|
|
|
name: 0,
|
|
|
|
|
|
|
|
item,
|
|
|
|
location,
|
2023-02-28 06:01:04 +00:00
|
|
|
batch,
|
2022-11-25 09:21:58 +00:00
|
|
|
|
|
|
|
balanceQuantity: 0,
|
|
|
|
balanceValue: 0,
|
|
|
|
|
|
|
|
openingQuantity: 0,
|
|
|
|
openingValue: 0,
|
|
|
|
|
|
|
|
incomingQuantity: 0,
|
|
|
|
incomingValue: 0,
|
|
|
|
|
|
|
|
outgoingQuantity: 0,
|
|
|
|
outgoingValue: 0,
|
|
|
|
|
|
|
|
valuationRate: 0,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateOpeningBalances(
|
|
|
|
sbe: StockBalanceEntry,
|
|
|
|
sle: ComputedStockLedgerEntry
|
|
|
|
) {
|
|
|
|
sbe.openingQuantity += sle.quantity;
|
|
|
|
sbe.openingValue += sle.valueChange;
|
|
|
|
|
|
|
|
sbe.balanceQuantity += sle.quantity;
|
|
|
|
sbe.balanceValue += sle.valueChange;
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateCurrentBalances(
|
|
|
|
sbe: StockBalanceEntry,
|
|
|
|
sle: ComputedStockLedgerEntry
|
|
|
|
) {
|
|
|
|
sbe.balanceQuantity += sle.quantity;
|
|
|
|
sbe.balanceValue += sle.valueChange;
|
|
|
|
|
|
|
|
if (sle.quantity > 0) {
|
|
|
|
sbe.incomingQuantity += sle.quantity;
|
|
|
|
sbe.incomingValue += sle.valueChange;
|
|
|
|
} else {
|
|
|
|
sbe.outgoingQuantity -= sle.quantity;
|
|
|
|
sbe.outgoingValue -= sle.valueChange;
|
|
|
|
}
|
|
|
|
|
|
|
|
sbe.valuationRate = sle.valuationRate;
|
|
|
|
}
|