feat: add auto-generation for serial numbers

This commit is contained in:
Gadha2311
2026-01-30 11:00:14 +05:30
parent 69604d011c
commit 2301e201c8
15 changed files with 639 additions and 21 deletions
+53 -7
View File
@@ -6,7 +6,7 @@ import { ColumnField, ReportData } from 'reports/types';
import { Field } from 'schemas/types';
import { getStockBalanceEntries } from './helpers';
import { StockLedger } from './StockLedger';
import { ReferenceType } from './types';
import { ReferenceType, SerialNumberStatus } from './types';
export class StockBalance extends StockLedger {
static title = t`Stock Balance`;
@@ -17,6 +17,9 @@ export class StockBalance extends StockLedger {
override referenceType: ReferenceType = 'All';
override referenceName = '';
showSerialNumbers = false;
serialNumberFilter: SerialNumberStatus = 'All';
override async _getReportData(force?: boolean): Promise<ReportData> {
if (this.shouldRefresh || force || !this._rawData?.length) {
await this._setRawData();
@@ -30,7 +33,12 @@ export class StockBalance extends StockLedger {
toDate: this.toDate,
};
const rawData = getStockBalanceEntries(this._rawData ?? [], filters);
const rawData = getStockBalanceEntries(
this._rawData ?? [],
filters,
this.showSerialNumbers,
this.serialNumberFilter
);
return rawData.map((sbe, i) => {
const row = { ...sbe, name: i + 1 } as RawValueMap;
@@ -81,12 +89,53 @@ export class StockBalance extends StockLedger {
label: t`To Date`,
fieldname: 'toDate',
},
{
fieldtype: 'Check',
placeholder: t`Serial Number`,
label: t`Serial Number`,
fieldname: 'showSerialNumbers',
},
...(this.showSerialNumbers
? ([
{
fieldtype: 'Select',
options: [
{ label: t`All`, value: 'All' },
{ label: t`In stock`, value: 'In stock' },
{ label: t`Out stock`, value: 'Out stock' },
],
placeholder: t`Serial Number Status`,
label: t`Serial Number Status`,
fieldname: 'serialNumberFilter',
default: 'All',
},
] as Field[])
: []),
] as Field[];
return filters;
}
getColumns(): ColumnField[] {
const batch: ColumnField[] = [];
const serialNumber: ColumnField[] = [];
if (this.hasBatches) {
batch.push({
fieldname: 'batch',
label: 'Batch',
fieldtype: 'Link',
});
}
if (this.showSerialNumbers && this.hasSerialNumbers) {
serialNumber.push({
fieldname: 'serialNumber',
label: 'Serial Number',
fieldtype: 'Data',
});
}
return [
{
fieldname: 'name',
@@ -104,11 +153,8 @@ export class StockBalance extends StockLedger {
label: 'Location',
fieldtype: 'Link',
},
...(this.hasBatches
? ([
{ fieldname: 'batch', label: 'Batch', fieldtype: 'Link' },
] as ColumnField[])
: []),
...batch,
...serialNumber,
{
fieldname: 'balanceQuantity',
label: 'Balance Qty.',
+36 -10
View File
@@ -6,6 +6,7 @@ import { safeParseFloat, safeParseInt } from 'utils/index';
import type {
ComputedStockLedgerEntry,
RawStockLedgerEntry,
SerialNumberStatus,
StockBalanceEntry,
} from './types';
import type { QueryFilter } from 'utils/db/types';
@@ -192,11 +193,13 @@ export function getStockBalanceEntries(
fromDate?: string;
toDate?: string;
batch?: string;
}
},
showSerialNumbers = false,
serialNumberFilter: SerialNumberStatus = 'All'
): StockBalanceEntry[] {
const sbeMap: Record<
Item,
Record<Location, Record<Batch, StockBalanceEntry>>
Record<Location, Record<Batch, Record<string, StockBalanceEntry>>>
> = {};
const fromDate = filters.fromDate ? Date.parse(filters.fromDate) : null;
@@ -215,19 +218,29 @@ export function getStockBalanceEntries(
continue;
}
if (
showSerialNumbers &&
(!sle.serialNumber || sle.serialNumber.trim() === '')
) {
continue;
}
const batch = sle.batch || '';
const serialNumber = showSerialNumbers ? sle.serialNumber : '';
sbeMap[sle.item] ??= {};
sbeMap[sle.item][sle.location] ??= {};
sbeMap[sle.item][sle.location][batch] ??= getSBE(
sbeMap[sle.item][sle.location][batch] ??= {};
sbeMap[sle.item][sle.location][batch][serialNumber] ??= getSBE(
sle.item,
sle.location,
batch
batch,
serialNumber
);
const date = sle.date.valueOf();
if (fromDate && date < fromDate) {
const sbe = sbeMap[sle.item][sle.location][batch];
const sbe = sbeMap[sle.item][sle.location][batch][serialNumber];
updateOpeningBalances(sbe, sle);
continue;
}
@@ -236,21 +249,33 @@ export function getStockBalanceEntries(
continue;
}
const sbe = sbeMap[sle.item][sle.location][batch];
const sbe = sbeMap[sle.item][sle.location][batch][serialNumber];
updateCurrentBalances(sbe, sle);
}
return Object.values(sbeMap)
let entries = Object.values(sbeMap)
.map((sbeBatched) =>
Object.values(sbeBatched).map((sbes) => Object.values(sbes))
Object.values(sbeBatched).map((sbes) =>
Object.values(sbes).map((sbeWithSN) => Object.values(sbeWithSN))
)
)
.flat(2);
.flat(3);
// Filter by serial number status
if (serialNumberFilter === 'In stock') {
entries = entries.filter((entry) => entry.balanceQuantity > 0);
} else if (serialNumberFilter === 'Out stock') {
entries = entries.filter((entry) => entry.balanceQuantity <= 0);
}
return entries;
}
function getSBE(
item: string,
location: string,
batch: string
batch: string,
serialNumber: string
): StockBalanceEntry {
return {
name: 0,
@@ -258,6 +283,7 @@ function getSBE(
item,
location,
batch,
serialNumber,
balanceQuantity: 0,
balanceValue: 0,
+7 -3
View File
@@ -44,16 +44,17 @@ export interface StockBalanceEntry{
item: string;
location:string;
batch: string;
serialNumber: string;
balanceQuantity: number;
balanceValue: number;
openingQuantity: number;
openingValue:number;
incomingQuantity:number;
incomingValue:number;
outgoingQuantity:number;
outgoingValue:number;
@@ -65,3 +66,6 @@ export type ReferenceType =
| ModelNameEnum.Shipment
| ModelNameEnum.PurchaseReceipt
| 'All';
export type SerialNumberStatus = 'All' | 'In stock' | 'Out stock';