From 8a4e1e4f41a0f482e755f128df899ac00733728f Mon Sep 17 00:00:00 2001 From: akshayitzme Date: Sat, 14 Jan 2023 16:58:16 +0530 Subject: [PATCH] feat: BatchNumber schema & batch-wise validation --- backend/database/bespoke.ts | 9 +++++- fyo/core/dbHandler.ts | 6 ++-- models/baseModels/BatchNumber/BatchNumber.ts | 13 ++++++++ models/baseModels/Item/Item.ts | 2 +- models/index.ts | 2 ++ models/inventory/StockManager.ts | 31 +++++++++++--------- models/inventory/StockMovementItem.ts | 2 +- schemas/app/BatchNumber.json | 29 ++++++++++++++++++ schemas/app/InvoiceItem.json | 3 +- schemas/app/inventory/StockMovementItem.json | 12 ++++++-- schemas/schemas.ts | 3 ++ src/utils/sidebarConfig.ts | 5 ++++ 12 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 models/baseModels/BatchNumber/BatchNumber.ts create mode 100644 schemas/app/BatchNumber.json diff --git a/backend/database/bespoke.ts b/backend/database/bespoke.ts index ee830635..82f6bcd9 100644 --- a/backend/database/bespoke.ts +++ b/backend/database/bespoke.ts @@ -137,7 +137,8 @@ export class BespokeQueries { item: string, location?: string, fromDate?: string, - toDate?: string + toDate?: string, + batchNumber?: string ): Promise { const query = db.knex!(ModelNameEnum.StockLedgerEntry) .sum('quantity') @@ -147,6 +148,10 @@ export class BespokeQueries { query.andWhere('location', location); } + if (batchNumber) { + query.andWhere('batchNumber', batchNumber); + } + if (fromDate) { query.andWhereRaw('datetime(date) > datetime(?)', [fromDate]); } @@ -163,3 +168,5 @@ export class BespokeQueries { return value[0][Object.keys(value[0])[0]]; } } + +// \ No newline at end of file diff --git a/fyo/core/dbHandler.ts b/fyo/core/dbHandler.ts index c02d826e..83a74d90 100644 --- a/fyo/core/dbHandler.ts +++ b/fyo/core/dbHandler.ts @@ -312,14 +312,16 @@ export class DatabaseHandler extends DatabaseBase { item: string, location?: string, fromDate?: string, - toDate?: string + toDate?: string, + batchNumber?: string ): Promise { return (await this.#demux.callBespoke( 'getStockQuantity', item, location, fromDate, - toDate + toDate, + batchNumber )) as number | null; } diff --git a/models/baseModels/BatchNumber/BatchNumber.ts b/models/baseModels/BatchNumber/BatchNumber.ts new file mode 100644 index 00000000..0df86c78 --- /dev/null +++ b/models/baseModels/BatchNumber/BatchNumber.ts @@ -0,0 +1,13 @@ +import { Doc } from 'fyo/model/doc'; +import { + ListViewSettings, +} from 'fyo/model/types'; + +export class BatchNumber extends Doc { + static getListViewSettings(): ListViewSettings { + return { + columns: ["name", "expiryDate", "manufactureDate"], + }; + } + +} diff --git a/models/baseModels/Item/Item.ts b/models/baseModels/Item/Item.ts index fe82fc4e..c053c1aa 100644 --- a/models/baseModels/Item/Item.ts +++ b/models/baseModels/Item/Item.ts @@ -19,7 +19,6 @@ export class Item extends Doc { itemType?: 'Product' | 'Service'; for?: 'Purchases' | 'Sales' | 'Both'; hasBatchNumber?: boolean; - batchNumber?:string; formulas: FormulaMap = { incomeAccount: { @@ -122,6 +121,7 @@ export class Item extends Doc { !this.fyo.singles.AccountingSettings?.enableInventory || this.itemType !== 'Product' || (this.inserted && !this.trackItem), + batchNumber: () => (!this.hasBatchNumber), }; readOnly: ReadOnlyMap = { diff --git a/models/index.ts b/models/index.ts index 1396de44..c12eb802 100644 --- a/models/index.ts +++ b/models/index.ts @@ -26,12 +26,14 @@ import { ShipmentItem } from './inventory/ShipmentItem'; import { StockLedgerEntry } from './inventory/StockLedgerEntry'; import { StockMovement } from './inventory/StockMovement'; import { StockMovementItem } from './inventory/StockMovementItem'; +import { BatchNumber } from './baseModels/BatchNumber/BatchNumber'; export const models = { Account, AccountingLedgerEntry, AccountingSettings, Address, + BatchNumber, Defaults, Item, JournalEntry, diff --git a/models/inventory/StockManager.ts b/models/inventory/StockManager.ts index 503ba735..f0279888 100644 --- a/models/inventory/StockManager.ts +++ b/models/inventory/StockManager.ts @@ -82,7 +82,7 @@ export class StockManager { #getSMIDetails(transferDetails: SMTransferDetails): SMIDetails { return Object.assign({}, this.details, transferDetails); } - + // flag async #validate(details: SMIDetails) { this.#validateRate(details); this.#validateQuantity(details); @@ -125,7 +125,7 @@ export class StockManager { throw new ValidationError(t`Both From and To Location cannot be undefined`); } - + // flag async #validateStockAvailability(details: SMIDetails) { if (!details.fromLocation) { return; @@ -137,8 +137,11 @@ export class StockManager { details.item, details.fromLocation, undefined, - date + date, + details.batchNumber )) ?? 0; + console.log(quantityBefore); + const formattedDate = this.fyo.format(details.date, 'Datetime'); @@ -150,11 +153,9 @@ export class StockManager { 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') ); } @@ -162,7 +163,9 @@ export class StockManager { const quantityAfter = await this.fyo.db.getStockQuantity( details.item, details.fromLocation, - details.date.toISOString() + details.date.toISOString(), + undefined, + details.batchNumber ); if (quantityAfter === null) { // No future transactions @@ -175,17 +178,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/models/inventory/StockMovementItem.ts b/models/inventory/StockMovementItem.ts index b8ae74cf..7ce8742e 100644 --- a/models/inventory/StockMovementItem.ts +++ b/models/inventory/StockMovementItem.ts @@ -19,7 +19,7 @@ export class StockMovementItem extends Doc { rate?: Money; amount?: Money; parentdoc?: StockMovement; - batchNumber? : string; + batchNumber?: string; get isIssue() { return this.parentdoc?.movementType === MovementType.MaterialIssue; diff --git a/schemas/app/BatchNumber.json b/schemas/app/BatchNumber.json new file mode 100644 index 00000000..247547f8 --- /dev/null +++ b/schemas/app/BatchNumber.json @@ -0,0 +1,29 @@ +{ + "name": "BatchNumber", + "label": "Batch Number", + "naming": "manual", + "fields": [ + { + "fieldname": "name", + "fieldtype": "Data", + "create": true, + "label": "Batch Number", + "required": true + }, + { + "fieldname": "expiryDate", + "fieldtype": "Date", + "label": "Expiry Date", + "create": true, + "required": false + }, + { + "fieldname": "manufactureDate", + "fieldtype": "Date", + "label": "Manufacture Date", + "create": true, + "required": false + } + ], + "quickEditFields": ["batchNumber", "expiryDate", "manufactureDate"] +} \ No newline at end of file diff --git a/schemas/app/InvoiceItem.json b/schemas/app/InvoiceItem.json index 68d0abfb..082ac3e0 100644 --- a/schemas/app/InvoiceItem.json +++ b/schemas/app/InvoiceItem.json @@ -97,8 +97,9 @@ { "fieldname": "batchNumber", "label": "Batch No", - "fieldtype": "Data", + "fieldtype": "Link", "create": true, + "target": "BatchNumber", "placeholder": "Batch No" } ], diff --git a/schemas/app/inventory/StockMovementItem.json b/schemas/app/inventory/StockMovementItem.json index 2ba7a6a3..46545c08 100644 --- a/schemas/app/inventory/StockMovementItem.json +++ b/schemas/app/inventory/StockMovementItem.json @@ -26,6 +26,13 @@ "target": "Location", "create": true }, + { + "fieldname": "batchNumber", + "label": "Batch No", + "fieldtype": "Link", + "target": "BatchNumber", + "create": true + }, { "fieldname": "quantity", "label": "Quantity", @@ -46,12 +53,13 @@ "readOnly": true } ], - "tableFields": ["item", "fromLocation", "toLocation", "quantity", "rate"], - "keywordFields": ["item"], + "tableFields": ["item", "fromLocation", "toLocation", "batchNumber", "quantity", "rate"], + "keywordFields": ["item", "batchNumber"], "quickEditFields": [ "item", "fromLocation", "toLocation", + "batchNumber", "quantity", "rate", "amount" diff --git a/schemas/schemas.ts b/schemas/schemas.ts index afde4c48..7760145f 100644 --- a/schemas/schemas.ts +++ b/schemas/schemas.ts @@ -46,6 +46,7 @@ import submittable from './meta/submittable.json'; import tree from './meta/tree.json'; import { Schema, SchemaStub } from './types'; import InventorySettings from './app/inventory/InventorySettings.json'; +import BatchNumber from './app/BatchNumber.json' export const coreSchemas: Schema[] = [ PatchRun as Schema, @@ -112,4 +113,6 @@ export const appSchemas: Schema[] | SchemaStub[] = [ ShipmentItem as Schema, PurchaseReceipt as Schema, PurchaseReceiptItem as Schema, + + BatchNumber as Schema ]; diff --git a/src/utils/sidebarConfig.ts b/src/utils/sidebarConfig.ts index 0d5105a0..c4427d9f 100644 --- a/src/utils/sidebarConfig.ts +++ b/src/utils/sidebarConfig.ts @@ -96,6 +96,11 @@ async function getInventorySidebar(): Promise { name: 'stock-balance', route: '/report/StockBalance', }, + { + label: t`Batch`, + name: 'batch-number', + route: '/list/BatchNumber', + } ], }, ];