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;
|
2022-11-09 18:42:00 +00:00
|
|
|
|
|
|
|
export async function getRawStockLedgerEntries(fyo: Fyo) {
|
|
|
|
const fieldnames = [
|
|
|
|
'name',
|
|
|
|
'date',
|
|
|
|
'item',
|
|
|
|
'rate',
|
|
|
|
'quantity',
|
|
|
|
'location',
|
|
|
|
'referenceName',
|
|
|
|
'referenceType',
|
|
|
|
];
|
|
|
|
|
|
|
|
return (await fyo.db.getAllRaw(ModelNameEnum.StockLedgerEntry, {
|
|
|
|
fields: fieldnames,
|
|
|
|
orderBy: 'date',
|
|
|
|
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[] = [];
|
|
|
|
const stockQueues: Record<Item, Record<Location, StockQueue>> = {};
|
|
|
|
|
|
|
|
for (const sle of rawSLEs) {
|
|
|
|
const name = safeParseInt(sle.name);
|
|
|
|
const date = new Date(sle.date);
|
|
|
|
const rate = safeParseFloat(sle.rate);
|
|
|
|
const { item, location, quantity, referenceName, referenceType } = sle;
|
|
|
|
|
|
|
|
if (quantity === 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
stockQueues[item] ??= {};
|
|
|
|
stockQueues[item][location] ??= new StockQueue();
|
|
|
|
|
|
|
|
const q = stockQueues[item][location];
|
|
|
|
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,
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
): StockBalanceEntry[] {
|
|
|
|
const sbeMap: Record<Item, Record<Location, StockBalanceEntry>> = {};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
sbeMap[sle.item] ??= {};
|
|
|
|
sbeMap[sle.item][sle.location] ??= getSBE(sle.item, sle.location);
|
|
|
|
const date = sle.date.valueOf();
|
|
|
|
|
|
|
|
if (fromDate && date < fromDate) {
|
|
|
|
const sbe = sbeMap[sle.item][sle.location]!;
|
|
|
|
updateOpeningBalances(sbe, sle);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (toDate && date > toDate) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const sbe = sbeMap[sle.item][sle.location]!;
|
|
|
|
updateCurrentBalances(sbe, sle);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Object.values(sbeMap)
|
|
|
|
.map((sbes) => Object.values(sbes))
|
|
|
|
.flat();
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSBE(item: string, location: string): StockBalanceEntry {
|
|
|
|
return {
|
|
|
|
name: 0,
|
|
|
|
|
|
|
|
item,
|
|
|
|
location,
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|