| @@ -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[]> => { | export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Promise<any[]> => { | ||||
| try { | try { | ||||
| console.log("🔍 Fetching all pick order line lot details for userId:", userId); | 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[]>( | 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', | method: 'GET', | ||||
| next: { tags: ["pickorder"] }, | next: { tags: ["pickorder"] }, | ||||
| } | } | ||||
| ); | ); | ||||
| console.log("✅ API Response:", data); | |||||
| console.log("✅ Fetched lot details:", data); | |||||
| return data; | return data; | ||||
| } catch (error) { | } 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) => { | export const fetchAllPickOrderDetails = cache(async (userId?: number) => { | ||||
| @@ -156,7 +156,10 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => { | |||||
| if (res && res.records) { | if (res && res.records) { | ||||
| console.log("Records received:", res.records.length); | console.log("Records received:", res.records.length); | ||||
| console.log("First record:", res.records[0]); | 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" 状态的项目 | // 新增:在前端也过滤掉 "assigned" 状态的项目 | ||||
| const filteredRecords = res.records.filter((item: any) => item.status !== "assigned"); | const filteredRecords = res.records.filter((item: any) => item.status !== "assigned"); | ||||
| @@ -496,12 +499,14 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => { | |||||
| {/* Target Date - 只在第一个项目显示 */} | {/* Target Date - 只在第一个项目显示 */} | ||||
| <TableCell> | <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 - 只在第一个项目显示 */} | {/* Pick Order Status - 只在第一个项目显示 */} | ||||
| <TableCell> | <TableCell> | ||||
| @@ -24,8 +24,10 @@ import NewCreateItem from "./newcreatitem"; | |||||
| import AssignAndRelease from "./AssignAndRelease"; | import AssignAndRelease from "./AssignAndRelease"; | ||||
| import AssignTo from "./assignTo"; | import AssignTo from "./assignTo"; | ||||
| import { fetchAllItemsInClient, ItemCombo } from "@/app/api/settings/item/actions"; | 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 Jobcreatitem from "./Jobcreatitem"; | ||||
| import { useSession } from "next-auth/react"; | |||||
| import { SessionWithTokens } from "@/config/authConfig"; | |||||
| interface Props { | interface Props { | ||||
| pickOrders: PickOrderResult[]; | pickOrders: PickOrderResult[]; | ||||
| @@ -39,6 +41,8 @@ type SearchParamNames = keyof SearchQuery; | |||||
| const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | ||||
| const { t } = useTranslation("pickOrder"); | 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 [isOpenCreateModal, setIsOpenCreateModal] = useState(false) | ||||
| const [items, setItems] = useState<ItemCombo[]>([]) | const [items, setItems] = useState<ItemCombo[]>([]) | ||||
| @@ -47,6 +51,37 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| const [searchQuery, setSearchQuery] = useState<Record<string, any>>({}); | const [searchQuery, setSearchQuery] = useState<Record<string, any>>({}); | ||||
| const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
| const [totalCount, setTotalCount] = useState<number>(); | 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"]>>( | const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | ||||
| (_e, newValue) => { | (_e, newValue) => { | ||||
| @@ -266,13 +301,31 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| </Stack> | </Stack> | ||||
| </Box> | </Box> | ||||
| {/* Tabs section */} | |||||
| {/* Tabs section - ✅ Move the click handler here */} | |||||
| <Box sx={{ | <Box sx={{ | ||||
| borderBottom: '1px solid #e0e0e0' | borderBottom: '1px solid #e0e0e0' | ||||
| }}> | }}> | ||||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | <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> | </Tabs> | ||||
| {isAssigning && ( | |||||
| <Box sx={{ p: 1, textAlign: 'center' }}> | |||||
| <Typography variant="caption" color="primary"> | |||||
| {t("Assigning pick order...")} | |||||
| </Typography> | |||||
| </Box> | |||||
| )} | |||||
| </Box> | </Box> | ||||
| {/* Content section - NO overflow: 'auto' here */} | {/* 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; | return; | ||||
| } | } | ||||
| // ✅ 使用新的API路径,后端会自动处理分配逻辑 | |||||
| // ✅ Use the non-auto-assign endpoint - this only fetches existing data | |||||
| const allLotDetails = await fetchALLPickOrderLineLotDetails(userIdToUse); | const allLotDetails = await fetchALLPickOrderLineLotDetails(userIdToUse); | ||||
| console.log("✅ All combined lot details:", allLotDetails); | console.log("✅ All combined lot details:", allLotDetails); | ||||
| setCombinedLotData(allLotDetails); | setCombinedLotData(allLotDetails); | ||||
| @@ -429,19 +429,30 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| } | } | ||||
| }, [currentUserId]); | }, [currentUserId]); | ||||
| // ✅ 简化初始化逻辑,移除前端的自动分配检查 | |||||
| // ✅ Only fetch existing data when session is ready, no auto-assignment | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (session && currentUserId && !initializationRef.current) { | if (session && currentUserId && !initializationRef.current) { | ||||
| console.log("✅ Session loaded, initializing pick order..."); | console.log("✅ Session loaded, initializing pick order..."); | ||||
| initializationRef.current = true; | initializationRef.current = true; | ||||
| // ✅ 直接获取数据,后端会自动处理分配逻辑 | |||||
| // ✅ Only fetch existing data, no auto-assignment | |||||
| fetchAllCombinedLotData(); | fetchAllCombinedLotData(); | ||||
| } | } | ||||
| }, [session, currentUserId, 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) | ||||
| // ✅ 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}> | <FormProvider {...formProps}> | ||||
| <Stack spacing={2}> | <Stack spacing={2}> | ||||
| {/* Search Box */} | {/* Search Box */} | ||||
| <Box> | <Box> | ||||
| {/* | |||||
| <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}> | <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}> | ||||
| <Typography variant="h6" gutterBottom sx={{ mb: 0 }}> | <Typography variant="h6" gutterBottom sx={{ mb: 0 }}> | ||||
| {t("FG Pick Orders")} | {t("FG Pick Orders")} | ||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| */} | |||||
| {fgPickOrdersLoading ? ( | {fgPickOrdersLoading ? ( | ||||
| <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}> | <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}> | ||||
| <CircularProgress /> | <CircularProgress /> | ||||
| @@ -980,11 +993,11 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| <TableCell>{t("Item Name")}</TableCell> | <TableCell>{t("Item Name")}</TableCell> | ||||
| <TableCell>{t("Lot#")}</TableCell> | <TableCell>{t("Lot#")}</TableCell> | ||||
| <TableCell>{t("Target Date")}</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("Lot Required Pick Qty")}</TableCell> | ||||
| <TableCell align="right">{t("Original Available Qty")}</TableCell> | <TableCell align="right">{t("Original Available Qty")}</TableCell> | ||||
| <TableCell align="center">{t("Lot Actual Pick 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> | <TableCell align="center">{t("Action")}</TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| </TableHead> | </TableHead> | ||||
| @@ -1033,7 +1046,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| </Box> | </Box> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell>{lot.pickOrderTargetDate}</TableCell> | <TableCell>{lot.pickOrderTargetDate}</TableCell> | ||||
| <TableCell>{lot.location}</TableCell> | |||||
| {/* <TableCell>{lot.location}</TableCell> */} | |||||
| <TableCell align="right">{calculateRemainingRequiredQty(lot).toLocaleString()}</TableCell> | <TableCell align="right">{calculateRemainingRequiredQty(lot).toLocaleString()}</TableCell> | ||||
| <TableCell align="right"> | <TableCell align="right"> | ||||
| {(() => { | {(() => { | ||||
| @@ -1123,14 +1136,14 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| </Stack> | </Stack> | ||||
| )} | )} | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell align="right"> | |||||
| {/* <TableCell align="right"> | |||||
| {(() => { | {(() => { | ||||
| const inQty = lot.inQty || 0; | const inQty = lot.inQty || 0; | ||||
| const outQty = lot.outQty || 0; | const outQty = lot.outQty || 0; | ||||
| const result = inQty - outQty; | const result = inQty - outQty; | ||||
| return result.toLocaleString(); | return result.toLocaleString(); | ||||
| })()} | })()} | ||||
| </TableCell> | |||||
| </TableCell> */} | |||||
| <TableCell align="center"> | <TableCell align="center"> | ||||
| <Stack direction="column" spacing={1} alignItems="center"> | <Stack direction="column" spacing={1} alignItems="center"> | ||||
| <Button | <Button | ||||
| @@ -470,7 +470,7 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => { | |||||
| </TableCell> | </TableCell> | ||||
| {/* Group Name */} | {/* Group Name */} | ||||
| <TableCell> | <TableCell> | ||||
| {index === 0 ? (item.groupName || "No Group") : null} | |||||
| {index === 0 ? (item.groupName || t("No Group")) : null} | |||||
| </TableCell> | </TableCell> | ||||
| {/* Item Code */} | {/* Item Code */} | ||||
| <TableCell>{item.itemCode}</TableCell> | <TableCell>{item.itemCode}</TableCell> | ||||
| @@ -607,8 +607,8 @@ const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => { | |||||
| {...params} | {...params} | ||||
| label={t("Assign To")} | label={t("Assign To")} | ||||
| error={!!errors.assignTo} | 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> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Typography variant="body2" color="warning.main"> | <Typography variant="body2" color="warning.main"> | ||||
| {t("Select an action for the assigned pick orders.")} | |||||
| {t("Please assgin/release the pickorders to picker")} | |||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| @@ -595,7 +595,7 @@ const handleQtyBlur = useCallback((itemId: number) => { | |||||
| try { | try { | ||||
| // 获取组的名称和目标日期 | // 获取组的名称和目标日期 | ||||
| const group = groups.find(g => g.id === Number(groupId)); | 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 | // Use the group's target date, fallback to item's target date, then form's target date | ||||
| let groupTargetDate = group?.targetDate; | let groupTargetDate = group?.targetDate; | ||||
| @@ -913,6 +913,12 @@ const handleQtyBlur = useCallback((itemId: number) => { | |||||
| {groups.map((group) => ( | {groups.map((group) => ( | ||||
| <MenuItem key={group.id} value={group.id.toString()}> | <MenuItem key={group.id} value={group.id.toString()}> | ||||
| {group.name} | {group.name} | ||||
| if(group.name === t("No Group")){ | |||||
| <em>{t("No Group")}</em> | |||||
| } else { | |||||
| group.name | |||||
| } | |||||
| </MenuItem> | </MenuItem> | ||||
| ))} | ))} | ||||
| </Select> | </Select> | ||||
| @@ -189,7 +189,7 @@ | |||||
| "Location": "位置", | "Location": "位置", | ||||
| "All Pick Order Lots": "所有提料單批次", | "All Pick Order Lots": "所有提料單批次", | ||||
| "Completed": "已完成", | "Completed": "已完成", | ||||
| "Finished Good Order": "成品訂單", | |||||
| "Finished Good Order": "成品出倉", | |||||
| "Assign and Release": "分派並放單", | "Assign and Release": "分派並放單", | ||||
| "Original Available Qty": "原可用數", | "Original Available Qty": "原可用數", | ||||
| "Remaining Available Qty": "剩餘", | "Remaining Available Qty": "剩餘", | ||||
| @@ -215,5 +215,11 @@ | |||||
| "QR Scan Result:": "QR 掃描結果:", | "QR Scan Result:": "QR 掃描結果:", | ||||
| "Action": "操作", | "Action": "操作", | ||||
| "Please finish pick order.": "請完成提料。", | "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": "已選擇的貨品將加入以上建立的分組" | |||||
| } | } | ||||