From 922da73323231e588d768386ff9537b645a3af2d Mon Sep 17 00:00:00 2001 From: AbleKSaju <126228406+AbleKSaju@users.noreply.github.com> Date: Sat, 7 Dec 2024 14:20:59 +0530 Subject: [PATCH] feat: alert when available quantity is exceeded in POS --- models/helpers.ts | 37 +++++++- .../POS/Classic/SelectedItemRow.vue | 26 ++++++ src/pages/POS/KeyboardModal.vue | 15 ++++ src/pages/POS/POS.vue | 89 ++++++++++--------- 4 files changed, 122 insertions(+), 45 deletions(-) diff --git a/models/helpers.ts b/models/helpers.ts index e7e71bef..058f7571 100644 --- a/models/helpers.ts +++ b/models/helpers.ts @@ -28,6 +28,7 @@ import { Money } from 'pesa'; import { Party } from './baseModels/Party/Party'; import { PricingRule } from './baseModels/PricingRule/PricingRule'; import { Router } from 'vue-router'; +import { Item } from 'models/baseModels/Item/Item'; import { SalesInvoice } from './baseModels/SalesInvoice/SalesInvoice'; import { SalesQuote } from './baseModels/SalesQuote/SalesQuote'; import { StockMovement } from './inventory/StockMovement'; @@ -39,7 +40,7 @@ import { safeParseFloat } from 'utils/index'; import { PriceList } from './baseModels/PriceList/PriceList'; import { InvoiceItem } from './baseModels/InvoiceItem/InvoiceItem'; import { SalesInvoiceItem } from './baseModels/SalesInvoiceItem/SalesInvoiceItem'; -import { ItemQtyMap } from 'src/components/POS/types'; +import { ItemQtyMap, POSItem } from 'src/components/POS/types'; import { ValuationMethod } from './inventory/types'; import { getRawStockLedgerEntries, @@ -838,6 +839,40 @@ export async function removeLoyaltyPoint(doc: Doc) { await party.updateLoyaltyPoints(); } +export async function validateQty( + sinvDoc: SalesInvoice, + item: POSItem | Item | undefined, + existingItems: InvoiceItem[] +) { + if (!item) { + return; + } + + let itemName = item.name as string; + const itemQtyMap = await getItemQtyMap(sinvDoc); + + if (item instanceof SalesInvoiceItem) { + itemName = item.item as string; + } + + if (!itemQtyMap[itemName] || itemQtyMap[itemName].availableQty === 0) { + throw new ValidationError(t`Item ${itemName} has Zero Quantity`); + } + + if ( + (existingItems && !itemQtyMap[itemName]) || + itemQtyMap[itemName].availableQty < (existingItems[0]?.quantity as number) + ) { + existingItems[0].quantity = itemQtyMap[itemName].availableQty; + + throw new ValidationError( + t`Item ${itemName} only has ${itemQtyMap[itemName].availableQty} Quantity` + ); + } + + return; +} + export async function getPricingRulesOfCoupons( doc: SalesInvoice, couponName?: string, diff --git a/src/components/POS/Classic/SelectedItemRow.vue b/src/components/POS/Classic/SelectedItemRow.vue index 818d4cb5..6b6de66e 100644 --- a/src/components/POS/Classic/SelectedItemRow.vue +++ b/src/components/POS/Classic/SelectedItemRow.vue @@ -275,6 +275,10 @@ import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoic import { Money } from 'pesa'; import { DiscountType } from '../types'; import { validateSerialNumberCount } from 'src/utils/pos'; +import { validateQty } from 'models/helpers'; +import { InvoiceItem } from 'models/baseModels/InvoiceItem/InvoiceItem'; +import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice'; +import { showToast } from 'src/utils/interactive'; export default defineComponent({ name: 'SelectedItemRow', @@ -359,6 +363,28 @@ export default defineComponent({ async setQuantity(quantity: number) { this.row.set('quantity', quantity); + const existingItems = + (this.row.parentdoc as SalesInvoice).items?.filter( + (invoiceItem: InvoiceItem) => + invoiceItem.item === this.row.item && !invoiceItem.isFreeItem + ) ?? []; + + try { + await validateQty( + this.row.parentdoc as SalesInvoice, + this.row, + existingItems + ); + } catch (error) { + this.row.set('quantity', existingItems[0].stockNotTransferred); + + return showToast({ + type: 'error', + message: this.t`${error as string}`, + duration: 'short', + }); + } + if (!this.row.isFreeItem) { this.$emit('applyPricingRule'); this.$emit('runSinvFormulas'); diff --git a/src/pages/POS/KeyboardModal.vue b/src/pages/POS/KeyboardModal.vue index eb9d705b..66ddf268 100644 --- a/src/pages/POS/KeyboardModal.vue +++ b/src/pages/POS/KeyboardModal.vue @@ -320,6 +320,8 @@ import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice'; import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem'; import { ValidationError } from 'fyo/utils/errors'; import { showToast } from 'src/utils/interactive'; +import { validateQty } from 'models/helpers'; +import { InvoiceItem } from 'models/baseModels/InvoiceItem/InvoiceItem'; export default defineComponent({ name: 'KeyboardModal', @@ -459,6 +461,19 @@ export default defineComponent({ } if (this.selectedItemField === 'quantity') { + const existingItems = + this.sinvDoc.items?.filter( + (invoiceItem: InvoiceItem) => + invoiceItem.item === this.selectedItemRow?.item && + !invoiceItem.isFreeItem + ) ?? []; + + await validateQty( + this.sinvDoc, + this.selectedItemRow, + existingItems + ); + this.$emit('applyPricingRule'); } } diff --git a/src/pages/POS/POS.vue b/src/pages/POS/POS.vue index 5dc2e706..f5527d6e 100644 --- a/src/pages/POS/POS.vue +++ b/src/pages/POS/POS.vue @@ -112,6 +112,7 @@ import { Shipment } from 'models/inventory/Shipment'; import { routeTo, toggleSidebar } from 'src/utils/ui'; import PageHeader from 'src/components/PageHeader.vue'; import { Payment } from 'models/baseModels/Payment/Payment'; +import { InvoiceItem } from 'models/baseModels/InvoiceItem/InvoiceItem'; import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice'; import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem'; import { AppliedCouponCodes } from 'models/baseModels/AppliedCouponCodes/AppliedCouponCodes'; @@ -124,11 +125,12 @@ import { validateIsPosSettingsSet, } from 'src/utils/pos'; import { + validateQty, + getItemQtyMap, getPricingRule, removeFreeItems, getAddedLPWithGrandTotal, getItemRateFromPriceList, - getItemQtyMap, } from 'models/helpers'; import { POSItem, @@ -398,68 +400,67 @@ export default defineComponent({ return; } - if ( - !this.itemQtyMap[item.name as string] || - this.itemQtyMap[item.name as string].availableQty === 0 - ) { - showToast({ - type: 'error', - message: t`Item ${item.name as string} has Zero Quantity`, - duration: 'short', - }); - return; - } - const existingItems = this.sinvDoc.items?.filter( (invoiceItem) => invoiceItem.item === item.name && !invoiceItem.isFreeItem ) ?? []; - if (item.hasBatch) { - for (const invItem of existingItems) { - const itemQty = invItem.quantity ?? 0; - const qtyInBatch = - this.itemQtyMap[invItem.item as string][invItem.batch as string] ?? - 0; + try { + if (item.hasBatch) { + for (const invItem of existingItems) { + const itemQty = invItem.quantity ?? 0; + const qtyInBatch = + this.itemQtyMap[invItem.item as string][ + invItem.batch as string + ] ?? 0; - if (itemQty < qtyInBatch) { - invItem.quantity = (invItem.quantity as number) + 1; - invItem.rate = item.rate as Money; + if (itemQty < qtyInBatch) { + invItem.quantity = (invItem.quantity as number) + 1; + invItem.rate = item.rate as Money; - await this.applyPricingRule(); - await this.sinvDoc.runFormulas(); + await this.applyPricingRule(); + await this.sinvDoc.runFormulas(); + await validateQty( + this.sinvDoc as SalesInvoice, + item, + existingItems as InvoiceItem[] + ); - return; + return; + } } - } - try { await this.sinvDoc.append('items', { rate: item.rate as Money, item: item.name, }); - } catch (error) { - showToast({ - type: 'error', - message: t`${error as string}`, - }); + + return; } - return; - } + if (existingItems.length) { + if (!this.sinvDoc.priceList) { + existingItems[0].rate = item.rate as Money; + } - if (existingItems.length) { - if (!this.sinvDoc.priceList) { - existingItems[0].rate = item.rate as Money; + existingItems[0].quantity = (existingItems[0].quantity as number) + 1; + + await this.applyPricingRule(); + await this.sinvDoc.runFormulas(); + await validateQty( + this.sinvDoc as SalesInvoice, + item, + existingItems as InvoiceItem[] + ); + + return; } - - existingItems[0].quantity = (existingItems[0].quantity as number) + 1; - - await this.applyPricingRule(); - await this.sinvDoc.runFormulas(); - - return; + } catch (error) { + return showToast({ + type: 'error', + message: t`${error as string}`, + }); } await this.sinvDoc.append('items', {