From a8d5d9d7ef8fabce0a4954d40b81cd177c8babe9 Mon Sep 17 00:00:00 2001 From: akshayitzme Date: Wed, 18 Jan 2023 17:37:31 +0530 Subject: [PATCH] fix: batch-wise stock validations --- models/inventory/StockManager.ts | 100 ++++++++++++++++++++----------- reports/inventory/StockLedger.ts | 5 ++ reports/inventory/helpers.ts | 24 ++++++-- 3 files changed, 90 insertions(+), 39 deletions(-) diff --git a/models/inventory/StockManager.ts b/models/inventory/StockManager.ts index feaa7add..a9bec91d 100644 --- a/models/inventory/StockManager.ts +++ b/models/inventory/StockManager.ts @@ -82,12 +82,13 @@ export class StockManager { #getSMIDetails(transferDetails: SMTransferDetails): SMIDetails { return Object.assign({}, this.details, transferDetails); } - // flag + async #validate(details: SMIDetails) { this.#validateRate(details); this.#validateQuantity(details); this.#validateLocation(details); await this.#validateStockAvailability(details); + await this.#validateBatchWiseStockAvailability(details); } #validateQuantity(details: SMIDetails) { @@ -125,7 +126,59 @@ export class StockManager { throw new ValidationError(t`Both From and To Location cannot be undefined`); } - // flag + + async #validateBatchWiseStockAvailability(details: SMIDetails) { + /* + * Checks if hasBatchNumber is enabled in the item + * If user has not entered batchNumber raises a ValidationError + * If entered quantity is greater than the available quantity in the batch, raises a ValidationError + */ + + if (!details.fromLocation) { + return; + } + + const ifItemHasBatchNumber = await this.fyo.getValue( + 'Item', + details.item, + 'hasBatchNumber' + ); + const date = details.date.toISOString(); + const formattedDate = this.fyo.format(details.date, 'Datetime'); + + if (ifItemHasBatchNumber) { + if (!details.batchNumber) { + throw new ValidationError( + t`Please enter Batch Number for ${details.item}` + ); + } + + const itemsInBatch = + (await this.fyo.db.getStockQuantity( + details.item, + details.fromLocation, + undefined, + date, + details.batchNumber + )) ?? 0; + + if (details.quantity > itemsInBatch) { + throw new ValidationError( + [ + t`Insufficient Quantity in Batch ${details.batchNumber}`, + t`Additional quantity (${ + details.quantity - itemsInBatch + }) is required in batch ${ + details.batchNumber + } to make the outward transfer of item ${details.item} from ${ + details.fromLocation + } on ${formattedDate}`, + ].join('\n') + ); + } + } + } + async #validateStockAvailability(details: SMIDetails) { if (!details.fromLocation) { return; @@ -141,44 +194,21 @@ export class StockManager { undefined )) ?? 0; - const formattedDate = this.fyo.format(details.date, 'Datetime'); if (this.isCancelled) { quantityBefore += details.quantity; } - // batch-wise stock validation - let itemsInBatch = - (await this.fyo.db.getStockQuantity( - details.item, - details.fromLocation, - undefined, - date, - details.batchNumber - )) ?? 0; - - - let ifItemHasBatchNumber= await this.fyo.getValue('Item', details.item, 'hasBatchNumber'); - - if ((ifItemHasBatchNumber && details.batchNumber) && details.quantity > itemsInBatch) { - throw new ValidationError( - [ - t`Insufficient Quantity in Batch ${details.batchNumber}`, - t`Additional quantity (${details.quantity - itemsInBatch - }) is required in batch ${details.batchNumber} to make the outward transfer of item ${details.item} from ${details.fromLocation - } on ${formattedDate}`, - ].join('\n') - ); - } - if (quantityBefore < details.quantity) { throw new ValidationError( [ t`Insufficient Quantity.`, - t`Additional quantity (${details.quantity - quantityBefore - }) required to make outward transfer of item ${details.item} from ${details.fromLocation - } on ${formattedDate}`, + t`Additional quantity (${ + details.quantity - quantityBefore + }) required to make outward transfer of item ${details.item} from ${ + details.fromLocation + } on ${formattedDate}`, ].join('\n') ); } @@ -201,17 +231,17 @@ export class StockManager { [ t`Insufficient Quantity.`, t`Transfer will cause future entries to have negative stock.`, - t`Additional quantity (${quantityAfter - quantityRemaining - }) required to make outward transfer of item ${details.item} from ${details.fromLocation - } on ${formattedDate}`, + t`Additional quantity (${ + quantityAfter - quantityRemaining + }) required to make outward transfer of item ${details.item} from ${ + details.fromLocation + } on ${formattedDate}`, ].join('\n') ); } } } -// - class StockManagerItem { /** * The Stock Manager Item is used to move stock to and from a location. It diff --git a/reports/inventory/StockLedger.ts b/reports/inventory/StockLedger.ts index 6bd81ecf..2150ed7a 100644 --- a/reports/inventory/StockLedger.ts +++ b/reports/inventory/StockLedger.ts @@ -260,6 +260,11 @@ export class StockLedger extends Report { label: 'Location', fieldtype: 'Link', }, + { + fieldname: 'batchNumber', + label: 'Batch No.', + fieldtype: 'Link', + }, { fieldname: 'quantity', label: 'Quantity', diff --git a/reports/inventory/helpers.ts b/reports/inventory/helpers.ts index 6da6ca65..3f30f360 100644 --- a/reports/inventory/helpers.ts +++ b/reports/inventory/helpers.ts @@ -17,7 +17,7 @@ export async function getRawStockLedgerEntries(fyo: Fyo) { 'name', 'date', 'item', - "batchNumber", + 'batchNumber', 'rate', 'quantity', 'location', @@ -43,7 +43,14 @@ export function getStockLedgerEntries( const name = safeParseInt(sle.name); const date = new Date(sle.date); const rate = safeParseFloat(sle.rate); - const { item, location, batchNumber, quantity, referenceName, referenceType } = sle; + const { + item, + location, + batchNumber, + quantity, + referenceName, + referenceType, + } = sle; if (quantity === 0) { continue; @@ -126,7 +133,11 @@ export function getStockBalanceEntries( } sbeMap[sle.item] ??= {}; - sbeMap[sle.item][sle.location] ??= getSBE(sle.item, sle.location); + sbeMap[sle.item][sle.location] ??= getSBE( + sle.item, + sle.location, + sle.batchNumber + ); const date = sle.date.valueOf(); if (fromDate && date < fromDate) { @@ -148,13 +159,18 @@ export function getStockBalanceEntries( .flat(); } -function getSBE(item: string, location: string): StockBalanceEntry { +function getSBE( + item: string, + location: string, + batchNumber: string +): StockBalanceEntry { return { name: 0, item, location, + batchNumber, balanceQuantity: 0, balanceValue: 0,