| @@ -550,24 +550,25 @@ const fetchSuggestionsWithStatus = async (pickOrderLineId: number) => { | |||
| } | |||
| }; | |||
| // Update the existing function to use the non-auto-assign endpoint | |||
| export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Promise<any[]> => { | |||
| try { | |||
| console.log("🔍 Fetching all pick order line lot details for userId:", userId); | |||
| // ✅ 使用 serverFetchJson 而不是直接的 fetch | |||
| // ✅ Use the non-auto-assign endpoint | |||
| const data = await serverFetchJson<any[]>( | |||
| `${BASE_API_URL}/pickOrder/all-lots-with-details/${userId}`, | |||
| `${BASE_API_URL}/pickOrder/all-lots-with-details-no-auto-assign/${userId}`, | |||
| { | |||
| method: 'GET', | |||
| next: { tags: ["pickorder"] }, | |||
| } | |||
| ); | |||
| console.log("✅ API Response:", data); | |||
| console.log("✅ Fetched lot details:", data); | |||
| return data; | |||
| } catch (error) { | |||
| console.error("❌ Error fetching all pick order line lot details:", error); | |||
| throw error; | |||
| console.error("❌ Error fetching lot details:", error); | |||
| return []; | |||
| } | |||
| }); | |||
| export const fetchAllPickOrderDetails = cache(async (userId?: number) => { | |||
| @@ -156,7 +156,10 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => { | |||
| if (res && res.records) { | |||
| console.log("Records received:", res.records.length); | |||
| console.log("First record:", res.records[0]); | |||
| console.log("First record targetDate:", res.records[0]?.targetDate); | |||
| console.log("First record targetDate type:", typeof res.records[0]?.targetDate); | |||
| console.log("First record targetDate parsed:", new Date(res.records[0]?.targetDate)); | |||
| console.log("First record targetDate formatted:", new Date(res.records[0]?.targetDate).toLocaleDateString()); | |||
| // 新增:在前端也过滤掉 "assigned" 状态的项目 | |||
| const filteredRecords = res.records.filter((item: any) => item.status !== "assigned"); | |||
| @@ -496,12 +499,14 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => { | |||
| {/* Target Date - 只在第一个项目显示 */} | |||
| <TableCell> | |||
| {index === 0 ? ( | |||
| arrayToDayjs(item.targetDate) | |||
| .add(-1, "month") | |||
| .format(OUTPUT_DATE_FORMAT) | |||
| ) : null} | |||
| </TableCell> | |||
| {index === 0 ? ( | |||
| (() => { | |||
| console.log("targetDate:", item.targetDate); | |||
| console.log("formatted:", arrayToDayjs(item.targetDate).format(OUTPUT_DATE_FORMAT)); | |||
| return arrayToDayjs(item.targetDate).format(OUTPUT_DATE_FORMAT); | |||
| })() | |||
| ) : null} | |||
| </TableCell> | |||
| {/* Pick Order Status - 只在第一个项目显示 */} | |||
| <TableCell> | |||
| @@ -24,8 +24,10 @@ import NewCreateItem from "./newcreatitem"; | |||
| import AssignAndRelease from "./AssignAndRelease"; | |||
| import AssignTo from "./assignTo"; | |||
| import { fetchAllItemsInClient, ItemCombo } from "@/app/api/settings/item/actions"; | |||
| import { fetchPickOrderClient } from "@/app/api/pickOrder/actions"; | |||
| import { fetchPickOrderClient, autoAssignAndReleasePickOrder } from "@/app/api/pickOrder/actions"; | |||
| import Jobcreatitem from "./Jobcreatitem"; | |||
| import { useSession } from "next-auth/react"; | |||
| import { SessionWithTokens } from "@/config/authConfig"; | |||
| interface Props { | |||
| pickOrders: PickOrderResult[]; | |||
| @@ -39,6 +41,8 @@ type SearchParamNames = keyof SearchQuery; | |||
| const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| const { t } = useTranslation("pickOrder"); | |||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | |||
| const [isOpenCreateModal, setIsOpenCreateModal] = useState(false) | |||
| const [items, setItems] = useState<ItemCombo[]>([]) | |||
| @@ -47,6 +51,37 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| const [searchQuery, setSearchQuery] = useState<Record<string, any>>({}); | |||
| const [tabIndex, setTabIndex] = useState(0); | |||
| const [totalCount, setTotalCount] = useState<number>(); | |||
| const [isAssigning, setIsAssigning] = useState(false); | |||
| // ✅ Manual assignment handler - uses the action function | |||
| const handleManualAssign = useCallback(async () => { | |||
| if (!currentUserId || isAssigning) return; | |||
| setIsAssigning(true); | |||
| try { | |||
| console.log("🎯 Manual assignment triggered for user:", currentUserId); | |||
| // ✅ Use the action function instead of direct fetch | |||
| const result = await autoAssignAndReleasePickOrder(currentUserId); | |||
| console.log("✅ Manual assignment result:", result); | |||
| if (result.code === "SUCCESS") { | |||
| console.log("✅ Successfully assigned pick order manually"); | |||
| // Trigger refresh of the PickExecution component | |||
| window.dispatchEvent(new CustomEvent('pickOrderAssigned')); | |||
| } else if (result.code === "EXISTS") { | |||
| console.log("ℹ️ User already has active pick orders"); | |||
| // Still trigger refresh to show existing orders | |||
| window.dispatchEvent(new CustomEvent('pickOrderAssigned')); | |||
| } else { | |||
| console.log("ℹ️ No available pick orders or other status:", result.message); | |||
| } | |||
| } catch (error) { | |||
| console.error("❌ Error in manual assignment:", error); | |||
| } finally { | |||
| setIsAssigning(false); | |||
| } | |||
| }, [currentUserId, isAssigning]); | |||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||
| (_e, newValue) => { | |||
| @@ -266,13 +301,31 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| </Stack> | |||
| </Box> | |||
| {/* Tabs section */} | |||
| {/* Tabs section - ✅ Move the click handler here */} | |||
| <Box sx={{ | |||
| borderBottom: '1px solid #e0e0e0' | |||
| }}> | |||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | |||
| <Tab label={t("Pick Execution")} iconPosition="end" /> | |||
| <Tab | |||
| label={t("Pick Execution")} | |||
| iconPosition="end" | |||
| onClick={handleManualAssign} | |||
| sx={{ | |||
| cursor: 'pointer', | |||
| '&:hover': { | |||
| color: 'primary.main', | |||
| } | |||
| }} | |||
| title={t("Click to assign a new pick order")} | |||
| /> | |||
| </Tabs> | |||
| {isAssigning && ( | |||
| <Box sx={{ p: 1, textAlign: 'center' }}> | |||
| <Typography variant="caption" color="primary"> | |||
| {t("Assigning pick order...")} | |||
| </Typography> | |||
| </Box> | |||
| )} | |||
| </Box> | |||
| {/* Content section - NO overflow: 'auto' here */} | |||
| @@ -285,4 +338,4 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||
| ); | |||
| }; | |||
| export default PickOrderSearch; | |||
| export default PickOrderSearch; | |||
| @@ -415,7 +415,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| return; | |||
| } | |||
| // ✅ 使用新的API路径,后端会自动处理分配逻辑 | |||
| // ✅ Use the non-auto-assign endpoint - this only fetches existing data | |||
| const allLotDetails = await fetchALLPickOrderLineLotDetails(userIdToUse); | |||
| console.log("✅ All combined lot details:", allLotDetails); | |||
| setCombinedLotData(allLotDetails); | |||
| @@ -429,19 +429,30 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| } | |||
| }, [currentUserId]); | |||
| // ✅ 简化初始化逻辑,移除前端的自动分配检查 | |||
| // ✅ Only fetch existing data when session is ready, no auto-assignment | |||
| useEffect(() => { | |||
| if (session && currentUserId && !initializationRef.current) { | |||
| console.log("✅ Session loaded, initializing pick order..."); | |||
| initializationRef.current = true; | |||
| // ✅ 直接获取数据,后端会自动处理分配逻辑 | |||
| // ✅ Only fetch existing data, no auto-assignment | |||
| fetchAllCombinedLotData(); | |||
| } | |||
| }, [session, currentUserId, fetchAllCombinedLotData]); | |||
| // ✅ 移除前端的自动分配逻辑,因为后端已经处理了 | |||
| // const handleAutoAssignAndRelease = useCallback(async () => { ... }); // 删除这个函数 | |||
| // ✅ Add event listener for manual assignment | |||
| useEffect(() => { | |||
| const handlePickOrderAssigned = () => { | |||
| console.log("🔄 Pick order assigned event received, refreshing data..."); | |||
| fetchAllCombinedLotData(); | |||
| }; | |||
| window.addEventListener('pickOrderAssigned', handlePickOrderAssigned); | |||
| return () => { | |||
| window.removeEventListener('pickOrderAssigned', handlePickOrderAssigned); | |||
| }; | |||
| }, [fetchAllCombinedLotData]); | |||
| // ✅ Handle QR code submission for matched lot (external scanning) | |||
| // ✅ Handle QR code submission for matched lot (external scanning) | |||
| @@ -925,13 +936,15 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| <FormProvider {...formProps}> | |||
| <Stack spacing={2}> | |||
| {/* Search Box */} | |||
| <Box> | |||
| {/* | |||
| <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}> | |||
| <Typography variant="h6" gutterBottom sx={{ mb: 0 }}> | |||
| {t("FG Pick Orders")} | |||
| </Typography> | |||
| </Box> | |||
| */} | |||
| {fgPickOrdersLoading ? ( | |||
| <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}> | |||
| <CircularProgress /> | |||
| @@ -980,11 +993,11 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| <TableCell>{t("Item Name")}</TableCell> | |||
| <TableCell>{t("Lot#")}</TableCell> | |||
| <TableCell>{t("Target Date")}</TableCell> | |||
| <TableCell>{t("Lot Location")}</TableCell> | |||
| {/* <TableCell>{t("Lot Location")}</TableCell> */} | |||
| <TableCell align="right">{t("Lot Required Pick Qty")}</TableCell> | |||
| <TableCell align="right">{t("Original Available Qty")}</TableCell> | |||
| <TableCell align="center">{t("Lot Actual Pick Qty")}</TableCell> | |||
| <TableCell align="right">{t("Remaining Available Qty")}</TableCell> | |||
| {/* <TableCell align="right">{t("Remaining Available Qty")}</TableCell> */} | |||
| <TableCell align="center">{t("Action")}</TableCell> | |||
| </TableRow> | |||
| </TableHead> | |||
| @@ -1033,7 +1046,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| </Box> | |||
| </TableCell> | |||
| <TableCell>{lot.pickOrderTargetDate}</TableCell> | |||
| <TableCell>{lot.location}</TableCell> | |||
| {/* <TableCell>{lot.location}</TableCell> */} | |||
| <TableCell align="right">{calculateRemainingRequiredQty(lot).toLocaleString()}</TableCell> | |||
| <TableCell align="right"> | |||
| {(() => { | |||
| @@ -1123,14 +1136,14 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||
| </Stack> | |||
| )} | |||
| </TableCell> | |||
| <TableCell align="right"> | |||
| {/* <TableCell align="right"> | |||
| {(() => { | |||
| const inQty = lot.inQty || 0; | |||
| const outQty = lot.outQty || 0; | |||
| const result = inQty - outQty; | |||
| return result.toLocaleString(); | |||
| })()} | |||
| </TableCell> | |||
| </TableCell> */} | |||
| <TableCell align="center"> | |||
| <Stack direction="column" spacing={1} alignItems="center"> | |||
| <Button | |||
| @@ -470,7 +470,7 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => { | |||
| </TableCell> | |||
| {/* Group Name */} | |||
| <TableCell> | |||
| {index === 0 ? (item.groupName || "No Group") : null} | |||
| {index === 0 ? (item.groupName || t("No Group")) : null} | |||
| </TableCell> | |||
| {/* Item Code */} | |||
| <TableCell>{item.itemCode}</TableCell> | |||
| @@ -607,8 +607,8 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => { | |||
| {...params} | |||
| label={t("Assign To")} | |||
| error={!!errors.assignTo} | |||
| helperText={errors.assignTo?.message} | |||
| required | |||
| //helperText={errors.assignTo?.message} | |||
| //required | |||
| /> | |||
| )} | |||
| /> | |||
| @@ -616,7 +616,7 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => { | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| <Typography variant="body2" color="warning.main"> | |||
| {t("Select an action for the assigned pick orders.")} | |||
| {t("Please assgin/release the pickorders to picker")} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| @@ -595,7 +595,7 @@ const handleQtyBlur = useCallback((itemId: number) => { | |||
| try { | |||
| // 获取组的名称和目标日期 | |||
| const group = groups.find(g => g.id === Number(groupId)); | |||
| const groupName = group?.name || 'No Group'; | |||
| const groupName = group?.name || t('No Group'); | |||
| // Use the group's target date, fallback to item's target date, then form's target date | |||
| let groupTargetDate = group?.targetDate; | |||
| @@ -913,6 +913,12 @@ const handleQtyBlur = useCallback((itemId: number) => { | |||
| {groups.map((group) => ( | |||
| <MenuItem key={group.id} value={group.id.toString()}> | |||
| {group.name} | |||
| if(group.name === t("No Group")){ | |||
| <em>{t("No Group")}</em> | |||
| } else { | |||
| group.name | |||
| } | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| @@ -189,7 +189,7 @@ | |||
| "Location": "位置", | |||
| "All Pick Order Lots": "所有提料單批次", | |||
| "Completed": "已完成", | |||
| "Finished Good Order": "成品訂單", | |||
| "Finished Good Order": "成品出倉", | |||
| "Assign and Release": "分派並放單", | |||
| "Original Available Qty": "原可用數", | |||
| "Remaining Available Qty": "剩餘", | |||
| @@ -215,5 +215,11 @@ | |||
| "QR Scan Result:": "QR 掃描結果:", | |||
| "Action": "操作", | |||
| "Please finish pick order.": "請完成提料。", | |||
| "Lot": "批號" | |||
| "Lot": "批號", | |||
| "Assign Pick Orders": "分派提料單", | |||
| "Selected Pick Orders": "已選擇提料單數量", | |||
| "Please assgin/release the pickorders to picker": "請分派/放單提料單給提料員。", | |||
| "Assign To": "分派給", | |||
| "No Group": "沒有分組", | |||
| "Selected items will join above created group": "已選擇的貨品將加入以上建立的分組" | |||
| } | |||