fix: auto-fetch serial numbers in POS

This commit is contained in:
Gadha2311
2026-01-30 11:00:14 +05:30
parent 2301e201c8
commit f9bcf03229
3 changed files with 228 additions and 36 deletions
+1 -10
View File
@@ -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();
+122 -1
View File
@@ -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
View File
@@ -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({