From dec4db430801abddb6c35177f5e1edeb8cb33755 Mon Sep 17 00:00:00 2001 From: MohamedAliSmk Date: Mon, 30 Mar 2026 14:41:03 +0200 Subject: [PATCH] Refactor UOM handling in EditItemDialog component - Removed unnecessary UOM change handler call in SelectInput. - Introduced asynchronous fetching of UOM rates from the backend. - Added logic to handle stale responses when changing UOM. - Improved UOM conversion factor calculation and state management for item pricing. - Enhanced initialization logic for item details to ensure proper loading state. --- POS/src/components/sale/EditItemDialog.vue | 82 ++++++++++++++++++++-- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/POS/src/components/sale/EditItemDialog.vue b/POS/src/components/sale/EditItemDialog.vue index 480d2a7d..f0d17b86 100644 --- a/POS/src/components/sale/EditItemDialog.vue +++ b/POS/src/components/sale/EditItemDialog.vue @@ -160,7 +160,7 @@
{{ localUom }}
- + @@ -287,7 +287,7 @@ import { usePOSSettingsStore } from "@/stores/posSettings" import { useSerialNumberStore } from "@/stores/serialNumber" import { getItemStock } from "@/utils/stockValidator" import { formatCurrency as formatCurrencyUtil, getCurrencySymbol, roundCurrency } from "@/utils/currency" -import { Button, FeatherIcon } from "frappe-ui" +import { Button, FeatherIcon, createResource } from "frappe-ui" import { computed, ref, watch } from "vue" import SelectInput from "@/components/common/SelectInput.vue" @@ -323,11 +323,18 @@ const calculatedDiscount = ref(0) const calculatedTotal = ref(0) const hasStock = ref(true) const isCheckingStock = ref(false) +const isInitializingItem = ref(false) +const uomRateRequestId = ref(0) const localSerials = ref([]) // List of serial numbers for this item const removedSerials = ref([]) // Track serials removed during this edit session const originalSerials = ref([]) // Original serials when dialog opened const originalPriceListRate = ref(0) // Original price_list_rate when dialog opened (for rate edit validation) +const getItemDetailsResource = createResource({ + url: "pos_next.api.items.get_item_details", + auto: false, +}) + const show = computed({ get: () => props.modelValue, set: (val) => emit("update:modelValue", val), @@ -398,6 +405,7 @@ watch( () => props.item, (newItem) => { if (newItem) { + isInitializingItem.value = true localItem.value = { ...newItem } localQuantity.value = newItem.quantity || 1 localUom.value = newItem.uom || newItem.stock_uom || __("Nos") @@ -438,11 +446,17 @@ watch( isCheckingStock.value = false calculateTotals() + isInitializingItem.value = false } }, { immediate: true }, ) +watch(localUom, (newUom, oldUom) => { + if (!newUom || newUom === oldUom || isInitializingItem.value) return + handleUomChange(newUom) +}) + /** * Intelligently determine the step size based on current quantity * - Whole numbers (1, 2, 3): step by 1 @@ -515,9 +529,67 @@ function handleQuantityBlur() { calculateTotals() } -function handleUomChange() { - // When UOM changes, we need to fetch new rate from server - // For now, we'll just recalculate with current rate +function getConversionFactorForUom(uom) { + if (!localItem.value) return 1 + if (uom === localItem.value.stock_uom) return 1 + const uomData = localItem.value.item_uoms?.find((itemUom) => itemUom.uom === uom) + return uomData?.conversion_factor || 1 +} + +async function getRateForUom(uom) { + if (!localItem.value) return 0 + + // Primary source: fetch exact price_list_rate for selected UOM from backend. + const posProfile = settingsStore.settings?.pos_profile || localItem.value.pos_profile + if (localItem.value.item_code && posProfile) { + try { + const itemDetails = await getItemDetailsResource.submit({ + item_code: localItem.value.item_code, + pos_profile: posProfile, + qty: localQuantity.value || 1, + uom, + }) + const serverRate = Number(itemDetails?.price_list_rate ?? itemDetails?.rate) + if (!isNaN(serverRate) && serverRate > 0) { + return serverRate + } + } catch (error) { + console.error("Error fetching UOM item price rate:", error) + } + } + + // Secondary source: preloaded UOM prices on item payload. + if (localItem.value.uom_prices?.[uom] !== undefined) { + return Number(localItem.value.uom_prices[uom]) || 0 + } + + // Final fallback: keep current known item rate (no conversion-based pricing). + return Number(localItem.value.price_list_rate || localItem.value.rate || localRate.value || 0) +} + +async function handleUomChange(newUom) { + const selectedUom = newUom || localUom.value + if (!localItem.value || !selectedUom) { + calculateTotals() + return + } + + const requestId = ++uomRateRequestId.value + const fetchedRate = await getRateForUom(selectedUom) + // Ignore stale responses if user changes UOM repeatedly. + if (requestId !== uomRateRequestId.value) return + + const newRate = roundCurrency(fetchedRate) + const newConversionFactor = getConversionFactorForUom(selectedUom) + + // Keep local state consistent so update payload has correct UOM pricing metadata. + localRate.value = newRate + originalPriceListRate.value = newRate + localItem.value.uom = selectedUom + localItem.value.conversion_factor = newConversionFactor + localItem.value.rate = newRate + localItem.value.price_list_rate = newRate + calculateTotals() }