mirror of
https://github.com/frappe/books.git
synced 2024-12-22 02:49:03 +00:00
Merge pull request #1043 from AbleKSaju/feat-quantity-alerts
feat: alert for exceeding available quantity
This commit is contained in:
commit
6361df6e50
@ -843,7 +843,7 @@ export abstract class Invoice extends Transactional {
|
||||
loyaltyProgram: () => !this.loyaltyProgram,
|
||||
loyaltyPoints: () => !this.redeemLoyaltyPoints || this.isReturn,
|
||||
redeemLoyaltyPoints: () => !this.loyaltyProgram || this.isReturn,
|
||||
coupons: () => !this.coupons?.length,
|
||||
coupons: () => this.isSubmitted && !this.coupons?.length,
|
||||
priceList: () =>
|
||||
!this.fyo.singles.AccountingSettings?.enablePriceList ||
|
||||
(!this.canEdit && !this.priceList),
|
||||
|
@ -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,
|
||||
|
@ -35,7 +35,7 @@
|
||||
>
|
||||
<Row
|
||||
v-if="items"
|
||||
v-for="row in items as any"
|
||||
v-for="row in items as POSItem[]"
|
||||
:ratio="ratio"
|
||||
:border="true"
|
||||
class="
|
||||
|
@ -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,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', {
|
||||
|
@ -33,7 +33,7 @@
|
||||
style="height: 65vh; width: 60vh"
|
||||
>
|
||||
<Row
|
||||
v-for="row in savedInvoices as any"
|
||||
v-for="row in savedInvoices as SalesInvoice[]"
|
||||
:key="row.name"
|
||||
:ratio="ratio"
|
||||
:border="true"
|
||||
|
Loading…
Reference in New Issue
Block a user