diff --git a/src/components/InventorySearch/InventorySearch.tsx b/src/components/InventorySearch/InventorySearch.tsx index 6dbabd3..7c60972 100644 --- a/src/components/InventorySearch/InventorySearch.tsx +++ b/src/components/InventorySearch/InventorySearch.tsx @@ -16,7 +16,7 @@ import { fetchInventoryLotLines, } from '@/app/api/inventory/actions'; import { PrinterCombo } from '@/app/api/settings/printer'; -import { ItemCombo, fetchItemsWithDetails } from '@/app/api/settings/item/actions'; +import { ItemCombo, fetchItemsWithDetails, ItemWithDetails } from '@/app/api/settings/item/actions'; import { Button, Dialog, @@ -59,6 +59,38 @@ type SearchParamNames = keyof SearchQuery; const InventorySearch: React.FC = ({ inventories, printerCombo }) => { const { t } = useTranslation(['inventory', 'common', 'item']); + const buildSyntheticInventory = useCallback( + (item: ItemWithDetails): InventoryResult => ({ + id: 0, + itemId: item.id, + itemCode: item.code, + itemName: item.name, + itemType: 'Material', + onHandQty: 0, + onHoldQty: 0, + unavailableQty: 0, + availableQty: 0, + uomCode: item.uom, + uomUdfudesc: item.uomDesc, + uomShortDesc: item.uom, + qtyPerSmallestUnit: 1, + baseUom: item.uom, + price: 0, + currencyName: '', + status: 'active', + latestMarketUnitPrice: undefined, + latestMupUpdatedDate: undefined, + }), + [], + ); + + const getFirstItemRecord = useCallback((res: any): ItemWithDetails | null => { + if (!res) return null; + if (Array.isArray(res)) return (res[0] as ItemWithDetails) ?? null; + if (Array.isArray(res?.records)) return (res.records[0] as ItemWithDetails) ?? null; + return null; + }, []); + // Inventory const [filteredInventories, setFilteredInventories] = useState([]); @@ -260,21 +292,48 @@ const InventorySearch: React.FC = ({ inventories, printerCombo }) => { // On Search const onSearch = useCallback( - (query: Record) => { + async (query: Record) => { setLotNoFilter(''); setScannedItemId(null); setScanUiMode('idle'); setScanHoverCancel(false); qrScanner.stopScan(); qrScanner.resetScan(); - refetchInventoryData(query, 'search', defaultPagingController, ''); - refetchInventoryLotLineData(null, 'search', defaultPagingController); + const invRes = await refetchInventoryData(query, 'search', defaultPagingController, ''); + await refetchInventoryLotLineData(null, 'search', defaultPagingController); setInputs(() => query); setInventoriesPagingController(() => defaultPagingController); setInventoryLotLinesPagingController(() => defaultPagingController); + + // If there are no inventory rows, render a synthetic inventory so the "Stock Adjustment" chip can be used. + if (invRes?.records?.length === 0) { + try { + const code = query.itemCode?.trim?.(); + const name = query.itemName?.trim?.(); + const lookupParams = code ? { code } : name ? { name } : null; + + if (lookupParams) { + const itemRes = await fetchItemsWithDetails(lookupParams); + const firstItem = getFirstItemRecord(itemRes); + if (firstItem) { + setSelectedInventory(buildSyntheticInventory(firstItem)); + setFilteredInventoryLotLines([]); + setInventoryLotLinesPagingController(() => defaultPagingController); + } + } + } catch (e) { + console.error('Failed to build synthetic inventory:', e); + } + } }, - [qrScanner, refetchInventoryData, refetchInventoryLotLineData], + [ + qrScanner, + refetchInventoryData, + refetchInventoryLotLineData, + buildSyntheticInventory, + getFirstItemRecord, + ], ); const startLotScan = useCallback(() => { @@ -319,7 +378,16 @@ const InventorySearch: React.FC = ({ inventories, printerCombo }) => { onInventoryRowClick(target); } else { refetchInventoryLotLineData(null, 'search', defaultPagingController); - setSelectedInventory(null); + // No inventory rows for this scanned item => show synthetic inventory with the existing chip workflow. + const itemRes = await fetchItemsWithDetails({ code: res?.itemCode }); + const firstItem = getFirstItemRecord(itemRes); + if (firstItem) { + setSelectedInventory(buildSyntheticInventory(firstItem)); + setFilteredInventoryLotLines([]); + setInventoryLotLinesPagingController(() => defaultPagingController); + } else { + setSelectedInventory(null); + } } setInventoriesPagingController(() => defaultPagingController); @@ -338,6 +406,8 @@ const InventorySearch: React.FC = ({ inventories, printerCombo }) => { qrScanner.result, refetchInventoryData, refetchInventoryLotLineData, + buildSyntheticInventory, + getFirstItemRecord, scanUiMode, ]); @@ -471,6 +541,7 @@ const InventorySearch: React.FC = ({ inventories, printerCombo }) => { variant="outlined" color="secondary" onClick={handleOpenOpeningInventoryModal} + sx={{ display: 'none' }} > {t('Add entry for items without inventory')} @@ -499,13 +570,31 @@ const InventorySearch: React.FC = ({ inventories, printerCombo }) => { inventoryLotLinesPagingController, ) } - onStockAdjustmentSuccess={() => - refetchInventoryLotLineData( - selectedInventory?.itemId ?? null, + onStockAdjustmentSuccess={async () => { + const itemId = selectedInventory?.itemId ?? null; + + // Refresh both blocks: + // - middle: InventoryTable (inventories list) + // - bottom: InventoryLotLineTable (lot lines for selected item) + const invRes = await refetchInventoryData( + inputs, + 'search', + inventoriesPagingController, + lotNoFilter, + ); + + await refetchInventoryLotLineData( + itemId, 'search', inventoryLotLinesPagingController, - ) - } + ); + + // If inventory becomes available again after OPEN/ADJ, sync selected row. + if (itemId != null && invRes?.records?.length) { + const target = invRes.records.find((r) => r.itemId === itemId); + if (target) setSelectedInventory(target); + } + }} />