mirror of
https://github.com/frappe/books.git
synced 2024-12-22 10:58:59 +00:00
feat: alert when available quantity is exceeded in POS
This commit is contained in:
parent
614d8931f5
commit
922da73323
@ -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,
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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,30 +400,20 @@ 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
|
||||
) ?? [];
|
||||
|
||||
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;
|
||||
this.itemQtyMap[invItem.item as string][
|
||||
invItem.batch as string
|
||||
] ?? 0;
|
||||
|
||||
if (itemQty < qtyInBatch) {
|
||||
invItem.quantity = (invItem.quantity as number) + 1;
|
||||
@ -429,22 +421,20 @@ export default defineComponent({
|
||||
|
||||
await this.applyPricingRule();
|
||||
await this.sinvDoc.runFormulas();
|
||||
await validateQty(
|
||||
this.sinvDoc as SalesInvoice,
|
||||
item,
|
||||
existingItems as InvoiceItem[]
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
@ -458,9 +448,20 @@ export default defineComponent({
|
||||
|
||||
await this.applyPricingRule();
|
||||
await this.sinvDoc.runFormulas();
|
||||
await validateQty(
|
||||
this.sinvDoc as SalesInvoice,
|
||||
item,
|
||||
existingItems as InvoiceItem[]
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
return showToast({
|
||||
type: 'error',
|
||||
message: t`${error as string}`,
|
||||
});
|
||||
}
|
||||
|
||||
await this.sinvDoc.append('items', {
|
||||
rate: item.rate as Money,
|
||||
|
Loading…
Reference in New Issue
Block a user