2
0
mirror of https://github.com/frappe/books.git synced 2024-12-22 19:09:01 +00:00

fix: batch-wise stock validations

This commit is contained in:
akshayitzme 2023-01-18 17:37:31 +05:30
parent 20279c5a12
commit a8d5d9d7ef
3 changed files with 90 additions and 39 deletions

View File

@ -82,12 +82,13 @@ export class StockManager {
#getSMIDetails(transferDetails: SMTransferDetails): SMIDetails { #getSMIDetails(transferDetails: SMTransferDetails): SMIDetails {
return Object.assign({}, this.details, transferDetails); return Object.assign({}, this.details, transferDetails);
} }
// flag
async #validate(details: SMIDetails) { async #validate(details: SMIDetails) {
this.#validateRate(details); this.#validateRate(details);
this.#validateQuantity(details); this.#validateQuantity(details);
this.#validateLocation(details); this.#validateLocation(details);
await this.#validateStockAvailability(details); await this.#validateStockAvailability(details);
await this.#validateBatchWiseStockAvailability(details);
} }
#validateQuantity(details: SMIDetails) { #validateQuantity(details: SMIDetails) {
@ -125,7 +126,59 @@ export class StockManager {
throw new ValidationError(t`Both From and To Location cannot be undefined`); 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) { async #validateStockAvailability(details: SMIDetails) {
if (!details.fromLocation) { if (!details.fromLocation) {
return; return;
@ -141,44 +194,21 @@ export class StockManager {
undefined undefined
)) ?? 0; )) ?? 0;
const formattedDate = this.fyo.format(details.date, 'Datetime'); const formattedDate = this.fyo.format(details.date, 'Datetime');
if (this.isCancelled) { if (this.isCancelled) {
quantityBefore += details.quantity; 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) { if (quantityBefore < details.quantity) {
throw new ValidationError( throw new ValidationError(
[ [
t`Insufficient Quantity.`, t`Insufficient Quantity.`,
t`Additional quantity (${details.quantity - quantityBefore t`Additional quantity (${
}) required to make outward transfer of item ${details.item} from ${details.fromLocation details.quantity - quantityBefore
} on ${formattedDate}`, }) required to make outward transfer of item ${details.item} from ${
details.fromLocation
} on ${formattedDate}`,
].join('\n') ].join('\n')
); );
} }
@ -201,17 +231,17 @@ export class StockManager {
[ [
t`Insufficient Quantity.`, t`Insufficient Quantity.`,
t`Transfer will cause future entries to have negative stock.`, t`Transfer will cause future entries to have negative stock.`,
t`Additional quantity (${quantityAfter - quantityRemaining t`Additional quantity (${
}) required to make outward transfer of item ${details.item} from ${details.fromLocation quantityAfter - quantityRemaining
} on ${formattedDate}`, }) required to make outward transfer of item ${details.item} from ${
details.fromLocation
} on ${formattedDate}`,
].join('\n') ].join('\n')
); );
} }
} }
} }
//
class StockManagerItem { class StockManagerItem {
/** /**
* The Stock Manager Item is used to move stock to and from a location. It * The Stock Manager Item is used to move stock to and from a location. It

View File

@ -260,6 +260,11 @@ export class StockLedger extends Report {
label: 'Location', label: 'Location',
fieldtype: 'Link', fieldtype: 'Link',
}, },
{
fieldname: 'batchNumber',
label: 'Batch No.',
fieldtype: 'Link',
},
{ {
fieldname: 'quantity', fieldname: 'quantity',
label: 'Quantity', label: 'Quantity',

View File

@ -17,7 +17,7 @@ export async function getRawStockLedgerEntries(fyo: Fyo) {
'name', 'name',
'date', 'date',
'item', 'item',
"batchNumber", 'batchNumber',
'rate', 'rate',
'quantity', 'quantity',
'location', 'location',
@ -43,7 +43,14 @@ export function getStockLedgerEntries(
const name = safeParseInt(sle.name); const name = safeParseInt(sle.name);
const date = new Date(sle.date); const date = new Date(sle.date);
const rate = safeParseFloat(sle.rate); const rate = safeParseFloat(sle.rate);
const { item, location, batchNumber, quantity, referenceName, referenceType } = sle; const {
item,
location,
batchNumber,
quantity,
referenceName,
referenceType,
} = sle;
if (quantity === 0) { if (quantity === 0) {
continue; continue;
@ -126,7 +133,11 @@ export function getStockBalanceEntries(
} }
sbeMap[sle.item] ??= {}; 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(); const date = sle.date.valueOf();
if (fromDate && date < fromDate) { if (fromDate && date < fromDate) {
@ -148,13 +159,18 @@ export function getStockBalanceEntries(
.flat(); .flat();
} }
function getSBE(item: string, location: string): StockBalanceEntry { function getSBE(
item: string,
location: string,
batchNumber: string
): StockBalanceEntry {
return { return {
name: 0, name: 0,
item, item,
location, location,
batchNumber,
balanceQuantity: 0, balanceQuantity: 0,
balanceValue: 0, balanceValue: 0,