Files
ERP-core/Frontend/erp-system/hooks/use-allowed-uoms.ts
T
ImanThiyanga 8d5a05a419 Add smoke tests for UOM conversions and enhance frontend UOM management
- Implemented smoke tests for UOM directionality, ensuring conversions are one-directional and correctly validated.
- Added tests for receiving and selling items in different UOMs, verifying correct quantity handling and error responses.
- Created a UOM conversions panel in the frontend to allow users to manage UOM conversions for items.
- Introduced hooks for allowed UOMs to optimize fetching and caching of UOM data for document line forms.
- Developed utility functions for consistent UOM formatting and conversion handling across the application.
2026-08-10 15:12:51 +05:30

74 lines
2.4 KiB
TypeScript

"use client"
// Per-item allowed-UOM lookup for document line forms.
//
// Every line on a document can reference a different item, so this caches by itemId and
// fetches each one once. A naive refetch on every line edit would cost one request per line
// on a 20-line invoice; the cache keeps it to one request per distinct item.
import { useCallback, useEffect, useRef, useState } from "react"
import { itemsApi } from "@/lib/api/items"
import { AllowedUom } from "@/types/master-data"
export interface AllowedUomLookup {
/** Allowed units for an item, or `undefined` while it is still loading / unknown. */
get: (itemId: number | null | undefined) => AllowedUom[] | undefined
/** Ensure an item's units are loaded. Safe to call repeatedly; in-flight requests are shared. */
load: (itemId: number | null | undefined) => void
}
export function useAllowedUoms(): AllowedUomLookup {
const [cache, setCache] = useState<Record<number, AllowedUom[]>>({})
// Tracks in-flight and failed ids so a repeated render never re-issues the same request.
const pending = useRef<Set<number>>(new Set())
const load = useCallback((itemId: number | null | undefined) => {
if (!itemId || pending.current.has(itemId)) return
pending.current.add(itemId)
itemsApi
.allowedUoms(itemId)
.then((uoms) => setCache((prev) => ({ ...prev, [itemId]: uoms })))
.catch(() => {
// Leave the id marked so we don't hammer a failing endpoint. The picker falls back
// to the global UOM list (see `pickerOptions`), and the server still rejects an
// invalid unit on save — this is a degraded experience, not a correctness hole.
})
}, [])
const get = useCallback(
(itemId: number | null | undefined) => (itemId ? cache[itemId] : undefined),
[cache],
)
return { get, load }
}
/**
* Single-item variant for screens with one item in scope (the conversion editor, a stock
* enquiry filtered to one item).
*/
export function useItemAllowedUoms(itemId: number | null | undefined): AllowedUom[] {
const [uoms, setUoms] = useState<AllowedUom[]>([])
useEffect(() => {
if (!itemId) {
setUoms([])
return
}
let cancelled = false
itemsApi
.allowedUoms(itemId)
.then((res) => {
if (!cancelled) setUoms(res)
})
.catch(() => {
if (!cancelled) setUoms([])
})
return () => {
cancelled = true
}
}, [itemId])
return uoms
}