mirror of
https://github.com/frappe/books.git
synced 2026-08-24 02:24:17 -05:00
fix: auto-fetch serial numbers in POS
This commit is contained in:
@@ -400,12 +400,7 @@ export async function generateSerialNumbersForItem(
|
||||
((seriesDoc.current as number) || (seriesDoc.start as number)) - 1;
|
||||
}
|
||||
|
||||
let generatedCount = 0;
|
||||
let attempts = 0;
|
||||
const maxAttempts = quantity * 10;
|
||||
|
||||
while (generatedCount < quantity && attempts < maxAttempts) {
|
||||
attempts++;
|
||||
while (serialNumbers.length < quantity) {
|
||||
currentValue++;
|
||||
|
||||
const serialNumber = getPaddedName(seriesName, currentValue, padZeros);
|
||||
@@ -417,13 +412,9 @@ export async function generateSerialNumbersForItem(
|
||||
|
||||
if (!snExists) {
|
||||
serialNumbers.push(serialNumber);
|
||||
generatedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (generatedCount < quantity) {
|
||||
}
|
||||
|
||||
if (serialNumbers.length > 0) {
|
||||
await seriesDoc.set('current', currentValue);
|
||||
await seriesDoc.sync();
|
||||
|
||||
@@ -267,7 +267,7 @@
|
||||
fieldtype: 'Text',
|
||||
fieldname: 'serialNumber',
|
||||
}"
|
||||
:value="row.serialNumber"
|
||||
:value="itemSerialNumbers[row.item as string] || row.serialNumber"
|
||||
:show-label="true"
|
||||
:border="true"
|
||||
:required="hasSerialNumber"
|
||||
@@ -297,6 +297,7 @@ import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||
import { showToast } from 'src/utils/interactive';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import AutoComplete from 'src/components/Controls/AutoComplete.vue';
|
||||
import { getExistingActiveSerialNumbersForItem } from 'models/inventory/helpers';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SelectedItemRow',
|
||||
@@ -324,6 +325,9 @@ export default defineComponent({
|
||||
profileDiscountSetting: null as boolean | null,
|
||||
profileRateSetting: null as boolean | null,
|
||||
transferUnitOptions: [] as Array<{ label: string; value: string }>,
|
||||
isMounted: false,
|
||||
pendingTransferUnitChange: false,
|
||||
transferUnitChangeOldQty: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -346,6 +350,66 @@ export default defineComponent({
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
'row.quantity': {
|
||||
async handler(newQuantity, oldQuantity) {
|
||||
if (
|
||||
this.hasSerialNumber &&
|
||||
newQuantity &&
|
||||
newQuantity > 0 &&
|
||||
this.isMounted &&
|
||||
newQuantity !== oldQuantity
|
||||
) {
|
||||
await this.fetchSerialNumbers(false, true);
|
||||
}
|
||||
},
|
||||
immediate: false,
|
||||
},
|
||||
'row.transferQuantity': {
|
||||
async handler(newTransferQuantity, oldTransferQuantity) {
|
||||
if (
|
||||
this.pendingTransferUnitChange &&
|
||||
newTransferQuantity !== this.transferUnitChangeOldQty
|
||||
) {
|
||||
this.pendingTransferUnitChange = false;
|
||||
this.transferUnitChangeOldQty = 0;
|
||||
|
||||
await this.fetchSerialNumbers(true, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.isUOMConversionEnabled &&
|
||||
this.hasSerialNumber &&
|
||||
newTransferQuantity &&
|
||||
newTransferQuantity > 0 &&
|
||||
this.isMounted &&
|
||||
newTransferQuantity !== oldTransferQuantity &&
|
||||
!this.pendingTransferUnitChange
|
||||
) {
|
||||
await this.fetchSerialNumbers(false, false);
|
||||
}
|
||||
},
|
||||
immediate: false,
|
||||
},
|
||||
'row.transferUnit': {
|
||||
async handler(newTransferUnit, oldTransferUnit) {
|
||||
if (
|
||||
this.isUOMConversionEnabled &&
|
||||
this.hasSerialNumber &&
|
||||
newTransferUnit &&
|
||||
oldTransferUnit &&
|
||||
newTransferUnit !== oldTransferUnit &&
|
||||
this.isMounted
|
||||
) {
|
||||
delete this.itemSerialNumbers[this.row.item as string];
|
||||
await this.row.set('serialNumber', '');
|
||||
|
||||
this.pendingTransferUnitChange = true;
|
||||
this.transferUnitChangeOldQty = this.row.transferQuantity ?? 0;
|
||||
}
|
||||
},
|
||||
immediate: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isUOMConversionEnabled(): boolean {
|
||||
@@ -391,6 +455,14 @@ export default defineComponent({
|
||||
this.profileRateSetting = !!this.fyo.singles.POSSettings?.canChangeRate;
|
||||
this.itemVisibility = await getItemVisibility(this.fyo);
|
||||
}
|
||||
|
||||
await this.$nextTick();
|
||||
|
||||
this.isMounted = true;
|
||||
|
||||
if (this.hasSerialNumber) {
|
||||
await this.fetchSerialNumbers();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -483,6 +555,8 @@ export default defineComponent({
|
||||
if (!serialNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.row.set('serialNumber', serialNumber);
|
||||
this.itemSerialNumbers[this.row.item as string] = serialNumber;
|
||||
|
||||
validateSerialNumberCount(
|
||||
@@ -491,6 +565,52 @@ export default defineComponent({
|
||||
this.row.item!
|
||||
);
|
||||
},
|
||||
async fetchSerialNumbers(forceRefetch = false, useDirectQuantity = false) {
|
||||
if (!this.hasSerialNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
let quantity = 0;
|
||||
if (useDirectQuantity) {
|
||||
quantity = Math.abs(this.row.quantity ?? 0);
|
||||
} else if (this.isUOMConversionEnabled && this.row.transferQuantity) {
|
||||
quantity = Math.abs(this.row.transferQuantity);
|
||||
} else if (this.row.quantity) {
|
||||
quantity = Math.abs(this.row.quantity);
|
||||
}
|
||||
|
||||
if (quantity <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingSerialNumbers =
|
||||
this.itemSerialNumbers[this.row.item as string];
|
||||
|
||||
if (existingSerialNumbers && !forceRefetch) {
|
||||
const existingCount = existingSerialNumbers
|
||||
.split('\n')
|
||||
.filter((s) => s.trim()).length;
|
||||
|
||||
if (existingCount === quantity) {
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const serialNumbers = await getExistingActiveSerialNumbersForItem(
|
||||
this.fyo,
|
||||
this.row.item as string,
|
||||
quantity
|
||||
);
|
||||
|
||||
if (serialNumbers) {
|
||||
await this.row.set('serialNumber', serialNumbers);
|
||||
this.itemSerialNumbers[this.row.item as string] = serialNumbers;
|
||||
} else {
|
||||
}
|
||||
} catch (error) {}
|
||||
},
|
||||
isRateReadOnly() {
|
||||
const canChangeRate = this.profileRateSetting;
|
||||
return this.row.isFreeItem || !canChangeRate;
|
||||
@@ -514,6 +634,7 @@ export default defineComponent({
|
||||
!hasManualDiscount && this.row.itemDiscountPercent !== 0;
|
||||
const manualDiscountAmount = this.row.itemDiscountAmount;
|
||||
const manualDiscountPercent = this.row.itemDiscountPercent;
|
||||
|
||||
if (!this.row.isReturn && quantity <= 0) {
|
||||
showToast({
|
||||
type: 'error',
|
||||
|
||||
+105
-25
@@ -173,6 +173,7 @@ import {
|
||||
ItemSerialNumbers,
|
||||
} from 'src/components/POS/types';
|
||||
import { ValidationError } from 'fyo/utils/errors';
|
||||
import { getExistingActiveSerialNumbersForItem } from 'models/inventory/helpers';
|
||||
|
||||
const COMPONENT_NAME = 'POS';
|
||||
|
||||
@@ -852,26 +853,27 @@ export default defineComponent({
|
||||
return;
|
||||
}
|
||||
|
||||
const itemName = item.name;
|
||||
|
||||
if (item.hasBatch) {
|
||||
this.selectedItemForBatch = item.name;
|
||||
this.selectedItemForBatch = itemName;
|
||||
this.pendingBatchItem = { item, quantity: quantity ?? 1 };
|
||||
|
||||
this.toggleModal('BatchSelection', true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const isInventoryItem = await this.fyo.getValue(
|
||||
ModelNameEnum.Item,
|
||||
item.name,
|
||||
itemName,
|
||||
'trackItem'
|
||||
);
|
||||
|
||||
if (isInventoryItem) {
|
||||
const availableQty = this.itemQtyMap[item.name]?.availableQty ?? 0;
|
||||
const availableQty = this.itemQtyMap[itemName]?.availableQty ?? 0;
|
||||
if (availableQty <= 0) {
|
||||
throw new ValidationError(
|
||||
t`Item is out of stock (quantity is zero)`
|
||||
t`Item ${itemName} is out of stock (quantity is zero)`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -879,7 +881,7 @@ export default defineComponent({
|
||||
const existingItems =
|
||||
this.sinvDoc.items?.filter(
|
||||
(invoiceItem) =>
|
||||
invoiceItem.item === item.name && !invoiceItem.isFreeItem
|
||||
invoiceItem.item === itemName && !invoiceItem.isFreeItem
|
||||
) ?? [];
|
||||
|
||||
await validateQty(
|
||||
@@ -890,7 +892,7 @@ export default defineComponent({
|
||||
|
||||
const itemsHsncode = (await this.fyo.getValue(
|
||||
'Item',
|
||||
item?.name,
|
||||
itemName,
|
||||
'hsnCode'
|
||||
)) as number;
|
||||
|
||||
@@ -898,21 +900,38 @@ export default defineComponent({
|
||||
const addQty = quantity ?? 1;
|
||||
|
||||
if (existingItems.length > 0) {
|
||||
for (let item of existingItems) {
|
||||
const availableQty = await fyo.db.getStockQuantity(
|
||||
item.item as string,
|
||||
for (let existingItem of existingItems) {
|
||||
const availableQty = await this.fyo.db.getStockQuantity(
|
||||
existingItem.item as string,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
item.batch
|
||||
existingItem.batch
|
||||
);
|
||||
if (
|
||||
item.batch != null &&
|
||||
existingItem.batch != null &&
|
||||
availableQty != null &&
|
||||
availableQty > (item.quantity as number)
|
||||
availableQty > (existingItem.quantity as number)
|
||||
) {
|
||||
const currentQty = item.quantity ?? 0;
|
||||
await item.set('quantity', currentQty + addQty);
|
||||
const currentQty = existingItem.quantity ?? 0;
|
||||
await existingItem.set('quantity', currentQty + addQty);
|
||||
|
||||
if (item.hasSerialNumber) {
|
||||
const qty = currentQty + addQty;
|
||||
|
||||
const serialNumbers =
|
||||
await getExistingActiveSerialNumbersForItem(
|
||||
this.fyo,
|
||||
itemName,
|
||||
qty
|
||||
);
|
||||
|
||||
if (serialNumbers) {
|
||||
this.itemSerialNumbers[itemName] = serialNumbers;
|
||||
await existingItem.set('serialNumber', serialNumbers);
|
||||
}
|
||||
}
|
||||
|
||||
await this.applyPricingRule();
|
||||
await this.sinvDoc.runFormulas();
|
||||
return;
|
||||
@@ -922,10 +941,32 @@ export default defineComponent({
|
||||
|
||||
await this.sinvDoc.append('items', {
|
||||
rate: item.rate,
|
||||
item: item.name,
|
||||
item: itemName,
|
||||
quantity: addQty,
|
||||
hsnCode: itemsHsncode,
|
||||
});
|
||||
|
||||
if (item.hasSerialNumber) {
|
||||
const serialNumbers = await getExistingActiveSerialNumbersForItem(
|
||||
this.fyo,
|
||||
itemName,
|
||||
addQty
|
||||
);
|
||||
|
||||
if (serialNumbers) {
|
||||
this.itemSerialNumbers[itemName] = serialNumbers;
|
||||
|
||||
const newItemRows = this.sinvDoc.items?.filter(
|
||||
(row) => row.item === itemName && !row.isFreeItem
|
||||
);
|
||||
|
||||
if (newItemRows && newItemRows.length > 0) {
|
||||
const newRow = newItemRows[newItemRows.length - 1];
|
||||
await newRow.set('serialNumber', serialNumbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.applyPricingRule();
|
||||
await this.sinvDoc.runFormulas();
|
||||
return;
|
||||
@@ -939,15 +980,30 @@ export default defineComponent({
|
||||
const currentQty = existingItems[0].quantity ?? 0;
|
||||
const addQty = quantity ?? 1;
|
||||
if (isInventoryItem) {
|
||||
const availableQty = this.itemQtyMap[item.name]?.availableQty ?? 0;
|
||||
const availableQty = this.itemQtyMap[itemName]?.availableQty ?? 0;
|
||||
if (currentQty + addQty > availableQty) {
|
||||
throw new ValidationError(
|
||||
'Cannot add more than the available quantity'
|
||||
`Cannot add more than the available quantity for ${itemName}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await existingItems[0].set('quantity', currentQty + addQty);
|
||||
if (item.hasSerialNumber) {
|
||||
const qty = currentQty + addQty;
|
||||
|
||||
const serialNumbers = await getExistingActiveSerialNumbersForItem(
|
||||
this.fyo,
|
||||
itemName,
|
||||
qty
|
||||
);
|
||||
|
||||
if (serialNumbers) {
|
||||
this.itemSerialNumbers[itemName] = serialNumbers;
|
||||
await existingItems[0].set('serialNumber', serialNumbers);
|
||||
}
|
||||
}
|
||||
|
||||
await this.applyPricingRule();
|
||||
await this.sinvDoc.runFormulas();
|
||||
if (isInventoryItem) {
|
||||
@@ -962,24 +1018,48 @@ export default defineComponent({
|
||||
|
||||
await this.sinvDoc.append('items', {
|
||||
rate: item.rate,
|
||||
item: item.name,
|
||||
item: itemName,
|
||||
quantity: quantity ? quantity : 1,
|
||||
hsnCode: itemsHsncode,
|
||||
});
|
||||
|
||||
if (this.sinvDoc.priceList) {
|
||||
let itemData = this.sinvDoc.items?.filter(
|
||||
(val) => val.item == item.name
|
||||
const itemData = this.sinvDoc.items?.filter(
|
||||
(val) => val.item == itemName
|
||||
) as SalesInvoiceItem[];
|
||||
|
||||
itemData[0].rate = await getItemRateFromPriceList(
|
||||
itemData[0],
|
||||
this.sinvDoc.priceList
|
||||
if (itemData.length > 0) {
|
||||
itemData[0].rate = await getItemRateFromPriceList(
|
||||
itemData[0],
|
||||
this.sinvDoc.priceList
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.hasSerialNumber) {
|
||||
const qty = quantity ?? 1;
|
||||
|
||||
const serialNumbers = await getExistingActiveSerialNumbersForItem(
|
||||
this.fyo,
|
||||
itemName,
|
||||
qty
|
||||
);
|
||||
|
||||
if (serialNumbers) {
|
||||
this.itemSerialNumbers[itemName] = serialNumbers;
|
||||
|
||||
const newItemRows = this.sinvDoc.items?.filter(
|
||||
(row) => row.item === itemName && !row.isFreeItem
|
||||
);
|
||||
|
||||
if (newItemRows && newItemRows.length > 0) {
|
||||
const newRow = newItemRows[newItemRows.length - 1];
|
||||
await newRow.set('serialNumber', serialNumbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.applyPricingRule();
|
||||
|
||||
await this.sinvDoc.runFormulas();
|
||||
} catch (error) {
|
||||
return showToast({
|
||||
|
||||
Reference in New Issue
Block a user