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

feat: alert when available quantity is exceeded in POS

This commit is contained in:
AbleKSaju 2024-12-07 14:20:59 +05:30
parent 614d8931f5
commit 922da73323
4 changed files with 122 additions and 45 deletions

View File

@ -28,6 +28,7 @@ import { Money } from 'pesa';
import { Party } from './baseModels/Party/Party'; import { Party } from './baseModels/Party/Party';
import { PricingRule } from './baseModels/PricingRule/PricingRule'; import { PricingRule } from './baseModels/PricingRule/PricingRule';
import { Router } from 'vue-router'; import { Router } from 'vue-router';
import { Item } from 'models/baseModels/Item/Item';
import { SalesInvoice } from './baseModels/SalesInvoice/SalesInvoice'; import { SalesInvoice } from './baseModels/SalesInvoice/SalesInvoice';
import { SalesQuote } from './baseModels/SalesQuote/SalesQuote'; import { SalesQuote } from './baseModels/SalesQuote/SalesQuote';
import { StockMovement } from './inventory/StockMovement'; import { StockMovement } from './inventory/StockMovement';
@ -39,7 +40,7 @@ import { safeParseFloat } from 'utils/index';
import { PriceList } from './baseModels/PriceList/PriceList'; import { PriceList } from './baseModels/PriceList/PriceList';
import { InvoiceItem } from './baseModels/InvoiceItem/InvoiceItem'; import { InvoiceItem } from './baseModels/InvoiceItem/InvoiceItem';
import { SalesInvoiceItem } from './baseModels/SalesInvoiceItem/SalesInvoiceItem'; 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 { ValuationMethod } from './inventory/types';
import { import {
getRawStockLedgerEntries, getRawStockLedgerEntries,
@ -838,6 +839,40 @@ export async function removeLoyaltyPoint(doc: Doc) {
await party.updateLoyaltyPoints(); 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( export async function getPricingRulesOfCoupons(
doc: SalesInvoice, doc: SalesInvoice,
couponName?: string, couponName?: string,

View File

@ -275,6 +275,10 @@ import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoic
import { Money } from 'pesa'; import { Money } from 'pesa';
import { DiscountType } from '../types'; import { DiscountType } from '../types';
import { validateSerialNumberCount } from 'src/utils/pos'; 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({ export default defineComponent({
name: 'SelectedItemRow', name: 'SelectedItemRow',
@ -359,6 +363,28 @@ export default defineComponent({
async setQuantity(quantity: number) { async setQuantity(quantity: number) {
this.row.set('quantity', quantity); 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) { if (!this.row.isFreeItem) {
this.$emit('applyPricingRule'); this.$emit('applyPricingRule');
this.$emit('runSinvFormulas'); this.$emit('runSinvFormulas');

View File

@ -320,6 +320,8 @@ import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem'; import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
import { ValidationError } from 'fyo/utils/errors'; import { ValidationError } from 'fyo/utils/errors';
import { showToast } from 'src/utils/interactive'; import { showToast } from 'src/utils/interactive';
import { validateQty } from 'models/helpers';
import { InvoiceItem } from 'models/baseModels/InvoiceItem/InvoiceItem';
export default defineComponent({ export default defineComponent({
name: 'KeyboardModal', name: 'KeyboardModal',
@ -459,6 +461,19 @@ export default defineComponent({
} }
if (this.selectedItemField === 'quantity') { 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'); this.$emit('applyPricingRule');
} }
} }

View File

@ -112,6 +112,7 @@ import { Shipment } from 'models/inventory/Shipment';
import { routeTo, toggleSidebar } from 'src/utils/ui'; import { routeTo, toggleSidebar } from 'src/utils/ui';
import PageHeader from 'src/components/PageHeader.vue'; import PageHeader from 'src/components/PageHeader.vue';
import { Payment } from 'models/baseModels/Payment/Payment'; import { Payment } from 'models/baseModels/Payment/Payment';
import { InvoiceItem } from 'models/baseModels/InvoiceItem/InvoiceItem';
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice'; import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem'; import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
import { AppliedCouponCodes } from 'models/baseModels/AppliedCouponCodes/AppliedCouponCodes'; import { AppliedCouponCodes } from 'models/baseModels/AppliedCouponCodes/AppliedCouponCodes';
@ -124,11 +125,12 @@ import {
validateIsPosSettingsSet, validateIsPosSettingsSet,
} from 'src/utils/pos'; } from 'src/utils/pos';
import { import {
validateQty,
getItemQtyMap,
getPricingRule, getPricingRule,
removeFreeItems, removeFreeItems,
getAddedLPWithGrandTotal, getAddedLPWithGrandTotal,
getItemRateFromPriceList, getItemRateFromPriceList,
getItemQtyMap,
} from 'models/helpers'; } from 'models/helpers';
import { import {
POSItem, POSItem,
@ -398,30 +400,20 @@ export default defineComponent({
return; 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 = const existingItems =
this.sinvDoc.items?.filter( this.sinvDoc.items?.filter(
(invoiceItem) => (invoiceItem) =>
invoiceItem.item === item.name && !invoiceItem.isFreeItem invoiceItem.item === item.name && !invoiceItem.isFreeItem
) ?? []; ) ?? [];
try {
if (item.hasBatch) { if (item.hasBatch) {
for (const invItem of existingItems) { for (const invItem of existingItems) {
const itemQty = invItem.quantity ?? 0; const itemQty = invItem.quantity ?? 0;
const qtyInBatch = const qtyInBatch =
this.itemQtyMap[invItem.item as string][invItem.batch as string] ?? this.itemQtyMap[invItem.item as string][
0; invItem.batch as string
] ?? 0;
if (itemQty < qtyInBatch) { if (itemQty < qtyInBatch) {
invItem.quantity = (invItem.quantity as number) + 1; invItem.quantity = (invItem.quantity as number) + 1;
@ -429,22 +421,20 @@ export default defineComponent({
await this.applyPricingRule(); await this.applyPricingRule();
await this.sinvDoc.runFormulas(); await this.sinvDoc.runFormulas();
await validateQty(
this.sinvDoc as SalesInvoice,
item,
existingItems as InvoiceItem[]
);
return; return;
} }
} }
try {
await this.sinvDoc.append('items', { await this.sinvDoc.append('items', {
rate: item.rate as Money, rate: item.rate as Money,
item: item.name, item: item.name,
}); });
} catch (error) {
showToast({
type: 'error',
message: t`${error as string}`,
});
}
return; return;
} }
@ -458,9 +448,20 @@ export default defineComponent({
await this.applyPricingRule(); await this.applyPricingRule();
await this.sinvDoc.runFormulas(); await this.sinvDoc.runFormulas();
await validateQty(
this.sinvDoc as SalesInvoice,
item,
existingItems as InvoiceItem[]
);
return; return;
} }
} catch (error) {
return showToast({
type: 'error',
message: t`${error as string}`,
});
}
await this.sinvDoc.append('items', { await this.sinvDoc.append('items', {
rate: item.rate as Money, rate: item.rate as Money,