mirror of
https://github.com/frappe/books.git
synced 2026-08-24 10:04:45 -05:00
feat: add auto-generation for serial numbers
This commit is contained in:
@@ -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.',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user