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:
parent
614d8931f5
commit
922da73323
@ -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,
|
||||||
|
@ -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');
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user