| @@ -17,7 +17,7 @@ import { | |||||
| import { PurchaseQcResult } from "../po/actions"; | import { PurchaseQcResult } from "../po/actions"; | ||||
| import { StringNullableChain } from "lodash"; | import { StringNullableChain } from "lodash"; | ||||
| // import { BASE_API_URL } from "@/config/api"; | // import { BASE_API_URL } from "@/config/api"; | ||||
| import dayjs from "dayjs"; | |||||
| export interface SavePickOrderLineRequest { | export interface SavePickOrderLineRequest { | ||||
| itemId: number | itemId: number | ||||
| qty: number | qty: number | ||||
| @@ -399,14 +399,15 @@ export const updatePickExecutionIssueStatus = async ( | |||||
| revalidateTag("pickExecutionIssues"); | revalidateTag("pickExecutionIssues"); | ||||
| return result; | return result; | ||||
| }; | }; | ||||
| export async function fetchStoreLaneSummary(storeId: string): Promise<StoreLaneSummary> { | |||||
| // ✅ 硬编码测试日期 - 改成你想测试的日期 | |||||
| const testDate = "2025-10-16"; // 或者 "2025-10-16", "2025-10-17" 等 | |||||
| export async function fetchStoreLaneSummary(storeId: string, requiredDate?: string): Promise<StoreLaneSummary> { | |||||
| const dateToUse = requiredDate || dayjs().format('YYYY-MM-DD'); | |||||
| const url = `${BASE_API_URL}/doPickOrder/summary-by-store?storeId=${encodeURIComponent(storeId)}&requiredDate=${encodeURIComponent(dateToUse)}`; | |||||
| const response = await serverFetchJson<StoreLaneSummary>( | const response = await serverFetchJson<StoreLaneSummary>( | ||||
| `${BASE_API_URL}/doPickOrder/summary-by-store?storeId=${encodeURIComponent(storeId)}&requiredDate=${testDate}`, | |||||
| url, | |||||
| { | { | ||||
| method: "GET", | method: "GET", | ||||
| cache: "no-store", | |||||
| next: { revalidate: 0 } | |||||
| } | } | ||||
| ); | ); | ||||
| return response; | return response; | ||||
| @@ -486,6 +487,15 @@ export const fetchFGPickOrders = async (pickOrderId: number) => { | |||||
| ); | ); | ||||
| return response; | return response; | ||||
| }; | }; | ||||
| export const fetchFGPickOrdersByUserId = async (userId: number) => { | |||||
| const response = await serverFetchJson<FGPickOrderResponse[]>( | |||||
| `${BASE_API_URL}/pickOrder/fg-pick-orders/${userId}`, | |||||
| { | |||||
| method: "GET", | |||||
| }, | |||||
| ); | |||||
| return response; | |||||
| }; | |||||
| export const updateSuggestedLotLineId = async (suggestedPickLotId: number, newLotLineId: number) => { | export const updateSuggestedLotLineId = async (suggestedPickLotId: number, newLotLineId: number) => { | ||||
| const response = await serverFetchJson<PostPickOrderResponse<UpdateSuggestedLotLineIdRequest>>( | const response = await serverFetchJson<PostPickOrderResponse<UpdateSuggestedLotLineIdRequest>>( | ||||
| `${BASE_API_URL}/suggestedPickLot/update-suggested-lot/${suggestedPickLotId}`, | `${BASE_API_URL}/suggestedPickLot/update-suggested-lot/${suggestedPickLotId}`, | ||||
| @@ -47,7 +47,7 @@ const FGPickOrderInfoCard: React.FC<Props> = ({ fgOrder }) => { | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| value={fgOrder.ticketNo || ""} | value={fgOrder.ticketNo || ""} | ||||
| label={t("Ticket No")} | |||||
| label={t("Ticket No.")} | |||||
| fullWidth | fullWidth | ||||
| disabled={true} | disabled={true} | ||||
| /> | /> | ||||
| @@ -56,7 +56,7 @@ const FGPickOrderInfoCard: React.FC<Props> = ({ fgOrder }) => { | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| value={fgOrder.storeId || ""} | value={fgOrder.storeId || ""} | ||||
| label={t("Store")} | |||||
| label={t("Store ID")} | |||||
| fullWidth | fullWidth | ||||
| disabled={true} | disabled={true} | ||||
| /> | /> | ||||
| @@ -65,7 +65,7 @@ const FGPickOrderInfoCard: React.FC<Props> = ({ fgOrder }) => { | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| value={fgOrder.truckLanceCode || ""} | value={fgOrder.truckLanceCode || ""} | ||||
| label={t("Truck Lane Code")} | |||||
| label={t("Truck Lance Code")} | |||||
| fullWidth | fullWidth | ||||
| disabled={true} | disabled={true} | ||||
| /> | /> | ||||
| @@ -24,25 +24,35 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||||
| const [isAssigning, setIsAssigning] = useState(false); | const [isAssigning, setIsAssigning] = useState(false); | ||||
| const [selectedDate, setSelectedDate] = useState<string>("today"); | const [selectedDate, setSelectedDate] = useState<string>("today"); | ||||
| const loadSummaries = useCallback(async () => { | |||||
| const loadData = async (dateValue: string) => { | |||||
| setIsLoadingSummary(true); | setIsLoadingSummary(true); | ||||
| try { | try { | ||||
| let dateOffset = 0; | |||||
| if (dateValue === "tomorrow") dateOffset = 1; | |||||
| else if (dateValue === "dayAfterTomorrow") dateOffset = 2; | |||||
| const requiredDate = dayjs().add(dateOffset, "day").format("YYYY-MM-DD"); | |||||
| console.log("🔄 requiredDate:", requiredDate); | |||||
| const [s2, s4] = await Promise.all([ | const [s2, s4] = await Promise.all([ | ||||
| fetchStoreLaneSummary("2/F"), | |||||
| fetchStoreLaneSummary("4/F") | |||||
| fetchStoreLaneSummary("2/F", requiredDate), | |||||
| fetchStoreLaneSummary("4/F", requiredDate), | |||||
| ]); | ]); | ||||
| console.log("🔄 s2:", s2); | |||||
| console.log("🔄 s4:", s4); | |||||
| setSummary2F(s2); | setSummary2F(s2); | ||||
| setSummary4F(s4); | setSummary4F(s4); | ||||
| } catch (error) { | |||||
| console.error("Error loading summaries:", error); | |||||
| } catch (e) { | |||||
| console.error("load summaries failed:", e); | |||||
| } finally { | } finally { | ||||
| setIsLoadingSummary(false); | setIsLoadingSummary(false); | ||||
| } | } | ||||
| }, []); | |||||
| }; | |||||
| // 初始化 | |||||
| useEffect(() => { | useEffect(() => { | ||||
| loadSummaries(); | |||||
| }, [loadSummaries]); | |||||
| loadData("today"); | |||||
| }, []); | |||||
| const handleAssignByLane = useCallback(async ( | const handleAssignByLane = useCallback(async ( | ||||
| storeId: string, | storeId: string, | ||||
| @@ -61,7 +71,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||||
| if (res.code === "SUCCESS") { | if (res.code === "SUCCESS") { | ||||
| console.log("✅ Successfully assigned pick order from lane", truckLanceCode); | console.log("✅ Successfully assigned pick order from lane", truckLanceCode); | ||||
| window.dispatchEvent(new CustomEvent('pickOrderAssigned')); | window.dispatchEvent(new CustomEvent('pickOrderAssigned')); | ||||
| loadSummaries(); // 刷新按钮状态 | |||||
| loadData(selectedDate); // 刷新按钮状态 | |||||
| onPickOrderAssigned?.(); | onPickOrderAssigned?.(); | ||||
| } else if (res.code === "USER_BUSY") { | } else if (res.code === "USER_BUSY") { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| @@ -95,7 +105,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||||
| } finally { | } finally { | ||||
| setIsAssigning(false); | setIsAssigning(false); | ||||
| } | } | ||||
| }, [currentUserId, t, loadSummaries, onPickOrderAssigned]); | |||||
| }, [currentUserId, t, selectedDate, onPickOrderAssigned]); | |||||
| const getDateLabel = (offset: number) => { | const getDateLabel = (offset: number) => { | ||||
| return dayjs().add(offset, 'day').format('YYYY-MM-DD'); | return dayjs().add(offset, 'day').format('YYYY-MM-DD'); | ||||
| @@ -127,7 +137,10 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||||
| value={selectedDate} | value={selectedDate} | ||||
| label={t("Select Date")} | label={t("Select Date")} | ||||
| onChange={(e) => { setSelectedDate(e.target.value)}} | |||||
| onChange={(e) => { { | |||||
| setSelectedDate(e.target.value); | |||||
| loadData(e.target.value); | |||||
| }}} | |||||
| > | > | ||||
| <MenuItem value="today"> | <MenuItem value="today"> | ||||
| {t("Today")} ({getDateLabel(0)}) | {t("Today")} ({getDateLabel(0)}) | ||||
| @@ -26,7 +26,7 @@ import { | |||||
| updateStockOutLineStatus, | updateStockOutLineStatus, | ||||
| createStockOutLine, | createStockOutLine, | ||||
| recordPickExecutionIssue, | recordPickExecutionIssue, | ||||
| fetchFGPickOrders, // ✅ Add this import | |||||
| fetchFGPickOrdersByUserId, // ✅ Add this import | |||||
| FGPickOrderResponse, | FGPickOrderResponse, | ||||
| autoAssignAndReleasePickOrder, | autoAssignAndReleasePickOrder, | ||||
| AutoAssignReleaseResponse, | AutoAssignReleaseResponse, | ||||
| @@ -49,7 +49,7 @@ import { SessionWithTokens } from "@/config/authConfig"; | |||||
| import { fetchStockInLineInfo } from "@/app/api/po/actions"; | import { fetchStockInLineInfo } from "@/app/api/po/actions"; | ||||
| import GoodPickExecutionForm from "./GoodPickExecutionForm"; | import GoodPickExecutionForm from "./GoodPickExecutionForm"; | ||||
| import FGPickOrderCard from "./FGPickOrderCard"; | import FGPickOrderCard from "./FGPickOrderCard"; | ||||
| import FinishedGoodFloorLanePanel from "./FGPickOrderCard"; | |||||
| import FinishedGoodFloorLanePanel from "./FinishedGoodFloorLanePanel"; | |||||
| import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | ||||
| interface Props { | interface Props { | ||||
| filterArgs: Record<string, any>; | filterArgs: Record<string, any>; | ||||
| @@ -357,28 +357,12 @@ const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange }) => | |||||
| setFgPickOrdersLoading(true); | setFgPickOrdersLoading(true); | ||||
| try { | try { | ||||
| // Get all pick order IDs from combinedLotData | |||||
| const pickOrderIds = Array.from(new Set(combinedLotData.map(lot => lot.pickOrderId))); | |||||
| // ✅ 简化:直接使用 userId 调用 API,不需要循环 | |||||
| const fgPickOrders = await fetchFGPickOrdersByUserId(currentUserId); | |||||
| if (pickOrderIds.length === 0) { | |||||
| setFgPickOrders([]); | |||||
| onFgPickOrdersChange?.([]); | |||||
| return; | |||||
| } | |||||
| // Fetch FG pick orders for each pick order ID | |||||
| const fgPickOrdersPromises = pickOrderIds.map(pickOrderId => | |||||
| fetchFGPickOrders(pickOrderId) | |||||
| ); | |||||
| const fgPickOrdersResults = await Promise.all(fgPickOrdersPromises); | |||||
| // Flatten the results (each fetchFGPickOrders returns an array) | |||||
| const allFgPickOrders = fgPickOrdersResults.flat(); | |||||
| setFgPickOrders(allFgPickOrders); | |||||
| onFgPickOrdersChange?.(allFgPickOrders); | |||||
| console.log("✅ Fetched FG pick orders:", allFgPickOrders); | |||||
| setFgPickOrders(fgPickOrders); | |||||
| onFgPickOrdersChange?.(fgPickOrders); | |||||
| console.log("✅ Fetched FG pick orders for user:", fgPickOrders); | |||||
| } catch (error) { | } catch (error) { | ||||
| console.error("❌ Error fetching FG pick orders:", error); | console.error("❌ Error fetching FG pick orders:", error); | ||||
| setFgPickOrders([]); | setFgPickOrders([]); | ||||
| @@ -386,12 +370,14 @@ const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange }) => | |||||
| } finally { | } finally { | ||||
| setFgPickOrdersLoading(false); | setFgPickOrdersLoading(false); | ||||
| } | } | ||||
| }, [currentUserId, combinedLotData]); | |||||
| }, [currentUserId, onFgPickOrdersChange]); | |||||
| // ✅ 简化:移除复杂的 useEffect 依赖 | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (combinedLotData.length > 0) { | |||||
| if (currentUserId) { | |||||
| fetchFgPickOrdersData(); | fetchFgPickOrdersData(); | ||||
| } | } | ||||
| }, [combinedLotData, fetchFgPickOrdersData, onFgPickOrdersChange]); | |||||
| }, [currentUserId, fetchFgPickOrdersData]); | |||||
| // ✅ Handle QR code button click | // ✅ Handle QR code button click | ||||
| const handleQrCodeClick = (pickOrderId: number) => { | const handleQrCodeClick = (pickOrderId: number) => { | ||||
| @@ -963,69 +949,60 @@ const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange }) => | |||||
| return sortedData.slice(startIndex, endIndex); | return sortedData.slice(startIndex, endIndex); | ||||
| }, [combinedLotData, paginationController]); | }, [combinedLotData, paginationController]); | ||||
| return ( | |||||
| <FormProvider {...formProps}> | |||||
| {/* ✅ 条件渲染:没有活动订单时显示楼层选择面板 */} | |||||
| {!combinedDataLoading && fgPickOrders.length === 0 ? ( | |||||
| <FinishedGoodFloorLanePanel | |||||
| onPickOrderAssigned={() => { | |||||
| if (currentUserId) { | |||||
| fetchAllCombinedLotData(currentUserId); | |||||
| } | |||||
| }} | |||||
| /> | |||||
| ) : ( | |||||
| // ✅ 有活动订单时,显示 FG 订单信息卡片 | |||||
| <Box> | |||||
| {fgPickOrdersLoading ? ( | |||||
| <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}> | |||||
| <CircularProgress /> | |||||
| </Box> | |||||
| ) : ( | |||||
| <Box> | |||||
| {fgPickOrders.length === 0 ? ( | |||||
| <Box sx={{ p: 3, textAlign: 'center' }}> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {t("No FG pick orders found")} | |||||
| </Typography> | |||||
| </Box> | |||||
| ) : ( | |||||
| // ✅ 使用新的 FGPickOrderInfoCard 组件(类似 DoInfoCard 的格式) | |||||
| fgPickOrders.map((fgOrder) => ( | |||||
| <FGPickOrderInfoCard | |||||
| key={fgOrder.pickOrderId} | |||||
| fgOrder={fgOrder} | |||||
| /> | |||||
| )) | |||||
| )} | |||||
| </Box> | |||||
| )} | |||||
| </Box> | |||||
| )} | |||||
| {/* Modals */} | |||||
| <QrCodeModal | |||||
| open={qrModalOpen} | |||||
| onClose={() => setQrModalOpen(false)} | |||||
| lot={selectedLotForQr} | |||||
| onQrCodeSubmit={handleQrCodeSubmit} | |||||
| combinedLotData={combinedLotData} | |||||
| /> | |||||
| <GoodPickExecutionForm | |||||
| open={pickExecutionFormOpen} | |||||
| onClose={() => { | |||||
| setPickExecutionFormOpen(false); | |||||
| setSelectedLotForExecutionForm(null); | |||||
| }} | |||||
| onSubmit={handlePickExecutionFormSubmit} | |||||
| selectedLot={selectedLotForExecutionForm} | |||||
| selectedPickOrderLine={null} | |||||
| pickOrderId={selectedLotForExecutionForm?.pickOrderId} | |||||
| pickOrderCreateDate={null} | |||||
| // ... existing code ... | |||||
| return ( | |||||
| <FormProvider {...formProps}> | |||||
| {/* ✅ 修复:改进条件渲染逻辑 */} | |||||
| {combinedDataLoading || fgPickOrdersLoading ? ( | |||||
| // ✅ 数据加载中,显示加载指示器 | |||||
| <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}> | |||||
| <CircularProgress /> | |||||
| </Box> | |||||
| ) : fgPickOrders.length === 0 ? ( | |||||
| // ✅ 没有活动订单,显示楼层选择面板 | |||||
| <FinishedGoodFloorLanePanel | |||||
| onPickOrderAssigned={() => { | |||||
| if (currentUserId) { | |||||
| fetchAllCombinedLotData(currentUserId); | |||||
| } | |||||
| }} | |||||
| /> | /> | ||||
| </FormProvider> | |||||
| ); | |||||
| ) : ( | |||||
| // ✅ 有活动订单,显示 FG 订单信息 | |||||
| <Box> | |||||
| {fgPickOrders.map((fgOrder) => ( | |||||
| <FGPickOrderInfoCard | |||||
| key={fgOrder.pickOrderId} | |||||
| fgOrder={fgOrder} | |||||
| /> | |||||
| ))} | |||||
| </Box> | |||||
| )} | |||||
| {/* Modals */} | |||||
| <QrCodeModal | |||||
| open={qrModalOpen} | |||||
| onClose={() => setQrModalOpen(false)} | |||||
| lot={selectedLotForQr} | |||||
| onQrCodeSubmit={handleQrCodeSubmit} | |||||
| combinedLotData={combinedLotData} | |||||
| /> | |||||
| <GoodPickExecutionForm | |||||
| open={pickExecutionFormOpen} | |||||
| onClose={() => { | |||||
| setPickExecutionFormOpen(false); | |||||
| setSelectedLotForExecutionForm(null); | |||||
| }} | |||||
| onSubmit={handlePickExecutionFormSubmit} | |||||
| selectedLot={selectedLotForExecutionForm} | |||||
| selectedPickOrderLine={null} | |||||
| pickOrderId={selectedLotForExecutionForm?.pickOrderId} | |||||
| pickOrderCreateDate={null} | |||||
| /> | |||||
| </FormProvider> | |||||
| ); | |||||
| }; | }; | ||||
| export default PickExecution; | export default PickExecution; | ||||
| @@ -14,6 +14,10 @@ | |||||
| "Do you want to start?": "確定開始嗎?", | "Do you want to start?": "確定開始嗎?", | ||||
| "Start": "開始", | "Start": "開始", | ||||
| "Start Success": "開始成功", | "Start Success": "開始成功", | ||||
| "Truck Lance Code": "車牌號碼", | |||||
| "Completed Date": "完成日期", | |||||
| "Completed Time": "完成時間", | |||||
| "Start Fail": "開始失敗", | "Start Fail": "開始失敗", | ||||
| "Start PO": "開始採購訂單", | "Start PO": "開始採購訂單", | ||||
| "Do you want to complete?": "確定完成嗎?", | "Do you want to complete?": "確定完成嗎?", | ||||