|
|
@@ -32,7 +32,7 @@ import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; |
|
|
|
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; |
|
|
|
import dayjs from "dayjs"; |
|
|
|
import { Check, Search, RestartAlt } from "@mui/icons-material"; |
|
|
|
import { ItemCombo, fetchAllItemsInClient } from "@/app/api/settings/item/actions"; |
|
|
|
import { ItemCombo, fetchAllItemsInClient,fetchItemsWithDetails } from "@/app/api/settings/item/actions"; |
|
|
|
import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; |
|
|
|
import SearchResults, { Column } from "../SearchResults/SearchResults"; |
|
|
|
import { fetchJobOrderDetailByCode } from "@/app/api/jo/actions"; |
|
|
@@ -40,6 +40,7 @@ import SearchBox, { Criterion } from "../SearchBox"; |
|
|
|
import VerticalSearchBox from "./VerticalSearchBox"; |
|
|
|
import SearchResultsTable from './SearchResultsTable'; |
|
|
|
import CreatedItemsTable from './CreatedItemsTable'; |
|
|
|
|
|
|
|
type Props = { |
|
|
|
filterArgs?: Record<string, any>; |
|
|
|
searchQuery?: Record<string, any>; |
|
|
@@ -133,9 +134,22 @@ const NewCreateItem: React.FC<Props> = ({ filterArgs, searchQuery, onPickOrderCr |
|
|
|
useEffect(() => { |
|
|
|
const loadItems = async () => { |
|
|
|
try { |
|
|
|
const itemsData = await fetchAllItemsInClient(); |
|
|
|
// Change from fetchAllItemsInClient to fetchItemsWithDetails |
|
|
|
const itemsData = await fetchItemsWithDetails(); |
|
|
|
console.log("Loaded items:", itemsData); |
|
|
|
setItems(itemsData); |
|
|
|
|
|
|
|
// Transform the data to match the expected ItemCombo format |
|
|
|
// Fix: Access the records property correctly |
|
|
|
const transformedItems: ItemCombo[] = (itemsData as any).records?.map((item: any) => ({ |
|
|
|
id: item.id, |
|
|
|
label: `${item.code} - ${item.name}`, |
|
|
|
uomId: item.uomId, |
|
|
|
uom: item.uom, |
|
|
|
uomDesc: item.uomDesc, |
|
|
|
currentStockBalance: item.currentStockBalance |
|
|
|
})) || []; |
|
|
|
|
|
|
|
setItems(transformedItems); |
|
|
|
setFilteredItems([]); |
|
|
|
} catch (error) { |
|
|
|
console.error("Error loading items:", error); |
|
|
@@ -159,9 +173,10 @@ const NewCreateItem: React.FC<Props> = ({ filterArgs, searchQuery, onPickOrderCr |
|
|
|
qty: item.reqQty, |
|
|
|
uom: item.uom, |
|
|
|
uomId: 0, |
|
|
|
uomDesc: item.uomDesc, // Add missing uomDesc |
|
|
|
uomDesc: item.uomDesc || "", // Add missing uomDesc |
|
|
|
jobOrderCode: jobOrderDetail.code, |
|
|
|
jobOrderId: jobOrderDetail.id, |
|
|
|
currentStockBalance: 0, // Add default value |
|
|
|
})); |
|
|
|
|
|
|
|
setFilteredItems(convertedItems); |
|
|
@@ -221,46 +236,8 @@ const NewCreateItem: React.FC<Props> = ({ filterArgs, searchQuery, onPickOrderCr |
|
|
|
[formProps], |
|
|
|
); |
|
|
|
|
|
|
|
const handleSearch = useCallback(() => { |
|
|
|
|
|
|
|
if (!searchCode && !searchName) { |
|
|
|
alert(t("Please enter at least code or name")); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
setIsLoading(true); |
|
|
|
setHasSearched(true); |
|
|
|
|
|
|
|
console.log("Searching with:", { type, searchCode, searchName, targetDate, itemsCount: items.length }); |
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
let filtered = items; |
|
|
|
|
|
|
|
if (searchCode && searchCode.trim()) { |
|
|
|
filtered = filtered.filter(item => |
|
|
|
item.label.toLowerCase().includes(searchCode.toLowerCase()) |
|
|
|
); |
|
|
|
console.log("After code filter:", filtered.length); |
|
|
|
} |
|
|
|
|
|
|
|
if (searchName && searchName.trim()) { |
|
|
|
filtered = filtered.filter(item => |
|
|
|
item.label.toLowerCase().includes(searchName.toLowerCase()) |
|
|
|
); |
|
|
|
console.log("After name filter:", filtered.length); |
|
|
|
} |
|
|
|
|
|
|
|
// Convert to SearchItemWithQty with default qty = null and include targetDate |
|
|
|
const filteredWithQty = filtered.slice(0, 100).map(item => ({ |
|
|
|
...item, |
|
|
|
qty: null, |
|
|
|
targetDate: targetDate, // Add target date to each item |
|
|
|
})); |
|
|
|
console.log("Final filtered results:", filteredWithQty.length); |
|
|
|
setFilteredItems(filteredWithQty); |
|
|
|
setIsLoading(false); |
|
|
|
}, 500); |
|
|
|
}, [type, searchCode, searchName, targetDate, items, t]); // Add targetDate back to dependencies |
|
|
|
// Remove the duplicate handleSearch function and keep only this one |
|
|
|
|
|
|
|
|
|
|
|
// Handle quantity change in search results |
|
|
|
const handleSearchQtyChange = useCallback((itemId: number, newQty: number | null) => { |
|
|
@@ -508,33 +485,78 @@ const handleQtyBlur = useCallback((itemId: number) => { |
|
|
|
}, 0); |
|
|
|
}, [groups, checkAndAutoAddItem]); |
|
|
|
// 5) 选中新增的待选项:依然按“当前 Group”赋 groupId + targetDate(新加入的应随 Group) |
|
|
|
const handleSecondSearchItemSelect = useCallback((itemId: number, isSelected: boolean) => { |
|
|
|
if (!isSelected) return; |
|
|
|
const item = secondSearchResults.find(i => i.id === itemId); |
|
|
|
if (!item) return; |
|
|
|
const exists = createdItems.find(c => c.itemId === item.id); |
|
|
|
if (exists) { alert(t("Item already exists in created items")); return; } |
|
|
|
|
|
|
|
// 找到项目所属的组,使用该组的 targetDate |
|
|
|
const itemGroup = groups.find(g => g.id === item.groupId); |
|
|
|
const itemTargetDate = itemGroup?.targetDate || item.targetDate || targetDate; |
|
|
|
|
|
|
|
const newCreatedItem: CreatedItem = { |
|
|
|
itemId: item.id, |
|
|
|
itemName: item.label, |
|
|
|
itemCode: item.label, |
|
|
|
qty: item.qty || 1, |
|
|
|
uom: item.uom || "", |
|
|
|
uomId: item.uomId || 0, |
|
|
|
uomDesc: item.uomDesc || "", |
|
|
|
isSelected: true, |
|
|
|
currentStockBalance: item.currentStockBalance, |
|
|
|
targetDate: itemTargetDate, // 使用项目所属组的 targetDate |
|
|
|
groupId: item.groupId || undefined, // 使用项目自身的 groupId |
|
|
|
}; |
|
|
|
setCreatedItems(prev => [...prev, newCreatedItem]); |
|
|
|
}, [secondSearchResults, createdItems, groups, targetDate, t]); |
|
|
|
|
|
|
|
const handleSearch = useCallback(() => { |
|
|
|
if (!searchCode && !searchName) { |
|
|
|
alert(t("Please enter at least code or name")); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
setIsLoading(true); |
|
|
|
setHasSearched(true); |
|
|
|
|
|
|
|
console.log("Searching with:", { type, searchCode, searchName, targetDate }); |
|
|
|
|
|
|
|
// Use the new API with search parameters |
|
|
|
const searchParams: Record<string, any> = {}; |
|
|
|
|
|
|
|
if (searchCode && searchCode.trim()) { |
|
|
|
searchParams.code = searchCode.trim(); |
|
|
|
} |
|
|
|
|
|
|
|
if (searchName && searchName.trim()) { |
|
|
|
searchParams.name = searchName.trim(); |
|
|
|
} |
|
|
|
|
|
|
|
if (type && type.trim()) { |
|
|
|
searchParams.type = type.trim(); |
|
|
|
} |
|
|
|
|
|
|
|
// Add pagination parameters |
|
|
|
searchParams.pageSize = 100; |
|
|
|
searchParams.pageNum = 1; |
|
|
|
|
|
|
|
// Fetch items using the new API |
|
|
|
fetchItemsWithDetails(searchParams) |
|
|
|
.then(response => { |
|
|
|
try { |
|
|
|
// Fix: Handle the response type correctly and safely |
|
|
|
let itemsToTransform: any[] = []; |
|
|
|
|
|
|
|
// Safely check and extract data from response |
|
|
|
if (response && typeof response === 'object') { |
|
|
|
if ('records' in response && Array.isArray((response as any).records)) { |
|
|
|
itemsToTransform = (response as any).records; |
|
|
|
} else if (Array.isArray(response)) { |
|
|
|
itemsToTransform = response; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const transformedItems: SearchItemWithQty[] = itemsToTransform.map((item: any) => ({ |
|
|
|
id: item.id, |
|
|
|
label: `${item.code} - ${item.name}`, |
|
|
|
uomId: item.uomId, |
|
|
|
uom: item.uom, |
|
|
|
uomDesc: item.uomDesc, |
|
|
|
currentStockBalance: item.currentStockBalance, |
|
|
|
qty: null, |
|
|
|
targetDate: targetDate, |
|
|
|
})); |
|
|
|
|
|
|
|
console.log("Search results:", transformedItems.length); |
|
|
|
setFilteredItems(transformedItems); |
|
|
|
} catch (error) { |
|
|
|
console.error("Error processing response:", error); |
|
|
|
setFilteredItems([]); |
|
|
|
} finally { |
|
|
|
setIsLoading(false); |
|
|
|
} |
|
|
|
}) |
|
|
|
.catch(error => { |
|
|
|
console.error("Error searching items:", error); |
|
|
|
setFilteredItems([]); |
|
|
|
setIsLoading(false); |
|
|
|
}); |
|
|
|
}, [type, searchCode, searchName, targetDate, t]); |
|
|
|
// 修改提交函数,按组分别创建提料单 |
|
|
|
const onSubmit = useCallback<SubmitHandler<SearchFormData>>( |
|
|
|
async (data, event) => { |
|
|
@@ -1052,8 +1074,6 @@ const handleQtyBlur = useCallback((itemId: number) => { |
|
|
|
// 修改搜索条件为3行,每行一个 - 确保SearchBox组件能正确处理 |
|
|
|
const pickOrderSearchCriteria: Criterion<any>[] = useMemo( |
|
|
|
() => [ |
|
|
|
|
|
|
|
|
|
|
|
{ |
|
|
|
label: t("Item Code"), |
|
|
|
paramName: "code", |
|
|
@@ -1258,7 +1278,6 @@ const handleQtyBlur = useCallback((itemId: number) => { |
|
|
|
|
|
|
|
// Sync second search box info to form - ensure type value is correct |
|
|
|
if (query.type) { |
|
|
|
// Ensure type value matches backend enum format |
|
|
|
let correctType = query.type; |
|
|
|
if (query.type === "consumable") { |
|
|
|
correctType = "Consumable"; |
|
|
@@ -1270,39 +1289,68 @@ const handleQtyBlur = useCallback((itemId: number) => { |
|
|
|
formProps.setValue("type", correctType); |
|
|
|
} |
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
let filtered = items; |
|
|
|
|
|
|
|
// Same filtering logic as first search |
|
|
|
if (query.code && query.code.trim()) { |
|
|
|
filtered = filtered.filter(item => |
|
|
|
item.label.toLowerCase().includes(query.code.toLowerCase()) |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
if (query.name && query.name.trim()) { |
|
|
|
filtered = filtered.filter(item => |
|
|
|
item.label.toLowerCase().includes(query.name.toLowerCase()) |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
if (query.type && query.type !== "All") { |
|
|
|
// Filter by type if needed |
|
|
|
// Build search parameters for the new API |
|
|
|
const searchParams: Record<string, any> = {}; |
|
|
|
|
|
|
|
if (query.code && query.code.trim()) { |
|
|
|
searchParams.code = query.code.trim(); |
|
|
|
} |
|
|
|
|
|
|
|
if (query.name && query.name.trim()) { |
|
|
|
searchParams.name = query.name.trim(); |
|
|
|
} |
|
|
|
|
|
|
|
if (query.type && query.type !== "All") { |
|
|
|
searchParams.type = query.type; |
|
|
|
} |
|
|
|
|
|
|
|
// Add pagination parameters |
|
|
|
searchParams.pageSize = 100; |
|
|
|
searchParams.pageNum = 1; |
|
|
|
|
|
|
|
// Use the new API |
|
|
|
fetchItemsWithDetails(searchParams) |
|
|
|
.then(response => { |
|
|
|
try { |
|
|
|
// Fix: Handle the response type correctly and safely |
|
|
|
let itemsToTransform: any[] = []; |
|
|
|
|
|
|
|
// Safely check and extract data from response |
|
|
|
if (response && typeof response === 'object') { |
|
|
|
if ('records' in response && Array.isArray((response as any).records)) { |
|
|
|
itemsToTransform = (response as any).records; |
|
|
|
} else if (Array.isArray(response)) { |
|
|
|
itemsToTransform = response; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const transformedItems: SearchItemWithQty[] = itemsToTransform.map((item: any) => ({ |
|
|
|
id: item.id, |
|
|
|
label: `${item.code} - ${item.name}`, |
|
|
|
uomId: item.uomId, |
|
|
|
uom: item.uom, |
|
|
|
uomDesc: item.uomDesc, |
|
|
|
currentStockBalance: item.currentStockBalance, |
|
|
|
qty: null, |
|
|
|
targetDate: undefined, |
|
|
|
groupId: undefined, |
|
|
|
})); |
|
|
|
|
|
|
|
setSecondSearchResults(transformedItems); |
|
|
|
setHasSearchedSecond(true); |
|
|
|
} catch (error) { |
|
|
|
console.error("Error processing response:", error); |
|
|
|
setSecondSearchResults([]); |
|
|
|
} finally { |
|
|
|
setIsLoadingSecondSearch(false); |
|
|
|
} |
|
|
|
|
|
|
|
// Convert to SearchItemWithQty with NO group/targetDate initially |
|
|
|
const filteredWithQty = filtered.slice(0, 100).map(item => ({ |
|
|
|
...item, |
|
|
|
qty: null, |
|
|
|
targetDate: undefined, // No target date initially |
|
|
|
groupId: undefined, // No group initially |
|
|
|
})); |
|
|
|
|
|
|
|
setSecondSearchResults(filteredWithQty); |
|
|
|
setHasSearchedSecond(true); |
|
|
|
}) |
|
|
|
.catch(error => { |
|
|
|
console.error("Error in second search:", error); |
|
|
|
setSecondSearchResults([]); |
|
|
|
setIsLoadingSecondSearch(false); |
|
|
|
}, 500); |
|
|
|
}, [items, formProps]); |
|
|
|
}); |
|
|
|
}, [formProps, t]); |
|
|
|
/* |
|
|
|
// Create a custom search box component that displays fields vertically |
|
|
|
const VerticalSearchBox = ({ criteria, onSearch, onReset }: { |
|
|
|