| @@ -111,7 +111,7 @@ export interface GetPickOrderLineInfo { | |||||
| itemName: string; | itemName: string; | ||||
| availableQty: number| null; | availableQty: number| null; | ||||
| requiredQty: number; | requiredQty: number; | ||||
| uomCode: string; | |||||
| uomShortDesc: string; | |||||
| uomDesc: string; | uomDesc: string; | ||||
| suggestedList: any[]; | suggestedList: any[]; | ||||
| pickedQty: number; | pickedQty: number; | ||||
| @@ -245,6 +245,15 @@ export interface UpdateSuggestedLotLineIdRequest { | |||||
| newLotLineId: number; | newLotLineId: number; | ||||
| } | } | ||||
| export interface FGPickOrderResponse { | export interface FGPickOrderResponse { | ||||
| // ✅ 新增:支持多个 pick orders | |||||
| doPickOrderId: number; // ✅ 新增:do_pick_order 的 ID | |||||
| pickOrderIds?: number[]; // ✅ 新增:所有 pick order IDs | |||||
| pickOrderCodes?: string; // ✅ 新增:所有 pick order codes(逗号分隔) | |||||
| deliveryOrderIds?: number[]; // ✅ 新增:所有 delivery order IDs | |||||
| deliveryNos?: string; // ✅ 新增:所有 delivery order codes(逗号分隔) | |||||
| numberOfPickOrders?: number; // ✅ 新增:pick order 数量 | |||||
| // ✅ 保留原有字段用于向后兼容(显示第一个 pick order) | |||||
| pickOrderId: number; | pickOrderId: number; | ||||
| pickOrderCode: string; | pickOrderCode: string; | ||||
| pickOrderConsoCode: string; | pickOrderConsoCode: string; | ||||
| @@ -265,6 +274,37 @@ export interface FGPickOrderResponse { | |||||
| storeId: string; | storeId: string; | ||||
| qrCodeData: number; | qrCodeData: number; | ||||
| } | } | ||||
| export interface DoPickOrderDetail { | |||||
| doPickOrder: { | |||||
| id: number; | |||||
| store_id: string; | |||||
| ticket_no: string; | |||||
| ticket_status: string; | |||||
| truck_id: number; | |||||
| truck_departure_time: string; | |||||
| shop_id: number; | |||||
| handled_by: number | null; | |||||
| loading_sequence: number; | |||||
| ticket_release_time: string | null; | |||||
| TruckLanceCode: string; | |||||
| ShopCode: string; | |||||
| ShopName: string; | |||||
| RequiredDeliveryDate: string; | |||||
| }; | |||||
| pickOrders: Array<{ | |||||
| pick_order_id: number; | |||||
| pick_order_code: string; | |||||
| do_order_id: number; | |||||
| delivery_order_code: string; | |||||
| consoCode: string; | |||||
| status: string; | |||||
| targetDate: string; | |||||
| }>; | |||||
| selectedPickOrderId: number; | |||||
| lotDetails: any[]; // 使用现有的 lot detail 结构 | |||||
| pickOrderCodes?: string; | |||||
| deliveryNos?: string; | |||||
| } | |||||
| export interface AutoAssignReleaseByStoreRequest { | export interface AutoAssignReleaseByStoreRequest { | ||||
| userId: number; | userId: number; | ||||
| storeId: string; // "2/F" | "4/F" | storeId: string; // "2/F" | "4/F" | ||||
| @@ -385,6 +425,20 @@ export interface LaneBtn { | |||||
| unassigned: number; | unassigned: number; | ||||
| total: number; | total: number; | ||||
| } | } | ||||
| export const fetchDoPickOrderDetail = async ( | |||||
| doPickOrderId: number, | |||||
| selectedPickOrderId?: number | |||||
| ): Promise<DoPickOrderDetail> => { | |||||
| const url = selectedPickOrderId | |||||
| ? `${BASE_API_URL}/pickOrder/do-pick-order-detail/${doPickOrderId}?selectedPickOrderId=${selectedPickOrderId}` | |||||
| : `${BASE_API_URL}/pickOrder/do-pick-order-detail/${doPickOrderId}`; | |||||
| const response = await serverFetchJson<DoPickOrderDetail>(url, { | |||||
| method: "GET", | |||||
| }); | |||||
| return response; | |||||
| }; | |||||
| export const updatePickExecutionIssueStatus = async ( | export const updatePickExecutionIssueStatus = async ( | ||||
| data: UpdatePickExecutionIssueRequest | data: UpdatePickExecutionIssueRequest | ||||
| ): Promise<PostPickOrderResponse> => { | ): Promise<PostPickOrderResponse> => { | ||||
| @@ -738,6 +792,40 @@ interface SuggestionWithStatus { | |||||
| stockOutLineQty?: number; | stockOutLineQty?: number; | ||||
| suggestionStatus: 'active' | 'completed' | 'rejected' | 'in_progress' | 'unknown'; | suggestionStatus: 'active' | 'completed' | 'rejected' | 'in_progress' | 'unknown'; | ||||
| } | } | ||||
| // 在 actions.ts 中修改接口定义 | |||||
| export interface FGPickOrderHierarchicalResponse { | |||||
| fgInfo: { | |||||
| doPickOrderId: number; | |||||
| ticketNo: string; | |||||
| storeId: string; | |||||
| shopCode: string; | |||||
| shopName: string; | |||||
| truckLanceCode: string; | |||||
| departureTime: string; | |||||
| }; | |||||
| pickOrders: Array<{ | |||||
| pickOrderId: number; | |||||
| pickOrderCode: string; | |||||
| doOrderId: number; | |||||
| deliveryOrderCode: string; | |||||
| consoCode: string; | |||||
| status: string; | |||||
| targetDate: string; | |||||
| pickOrderLines: Array<{ | |||||
| id: number; | |||||
| requiredQty: number; | |||||
| status: string; | |||||
| item: { | |||||
| id: number; | |||||
| code: string; | |||||
| name: string; | |||||
| uomCode: string; | |||||
| uomDesc: string; | |||||
| }; | |||||
| lots: Array<any>; // 可以是空数组 | |||||
| }>; | |||||
| }>; | |||||
| } | |||||
| export interface CheckCompleteResponse { | export interface CheckCompleteResponse { | ||||
| id: number | null; | id: number | null; | ||||
| name: string; | name: string; | ||||
| @@ -2,14 +2,22 @@ | |||||
| import { Box, Card, CardContent, Grid, TextField, Stack } from "@mui/material"; | import { Box, Card, CardContent, Grid, TextField, Stack } from "@mui/material"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { FGPickOrderResponse } from "@/app/api/pickOrder/actions"; | |||||
| import { FGPickOrderResponse, DoPickOrderDetail } from "@/app/api/pickOrder/actions"; | |||||
| interface Props { | interface Props { | ||||
| fgOrder: FGPickOrderResponse; | fgOrder: FGPickOrderResponse; | ||||
| doPickOrderDetail?: DoPickOrderDetail | null; | |||||
| } | } | ||||
| const FGPickOrderInfoCard: React.FC<Props> = ({ fgOrder }) => { | |||||
| const FGPickOrderInfoCard: React.FC<Props> = ({ fgOrder, doPickOrderDetail }) => { | |||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| if (!fgOrder) { | |||||
| return null; | |||||
| } | |||||
| const pickOrderCodes = fgOrder.pickOrderCodes || ""; | |||||
| const deliveryOrderCodes = fgOrder.deliveryNos || ""; | |||||
| return ( | return ( | ||||
| <Card sx={{ display: "block", mb: 2 }}> | <Card sx={{ display: "block", mb: 2 }}> | ||||
| @@ -17,21 +25,25 @@ const FGPickOrderInfoCard: React.FC<Props> = ({ fgOrder }) => { | |||||
| <Box> | <Box> | ||||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | ||||
| <Grid item xs={6}> | |||||
| <Grid item xs={6}> | |||||
| <TextField | <TextField | ||||
| value={fgOrder.pickOrderCode || ""} | |||||
| label={t("Pick Order Code")} | |||||
| value={pickOrderCodes || ""} // ✅ 显示所有 pick order codes | |||||
| label={t("Pick Order Code(s)")} // ✅ 修改标签 | |||||
| fullWidth | fullWidth | ||||
| disabled={true} | disabled={true} | ||||
| multiline={pickOrderCodes.includes(',')} // ✅ 如果有多个代码,使用多行 | |||||
| rows={pickOrderCodes.includes(',') ? 2 : 1} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| value={fgOrder.deliveryNo || ""} | |||||
| label={t("Delivery No")} | |||||
| value={deliveryOrderCodes || ""} // ✅ 显示所有 delivery order codes | |||||
| label={t("Delivery Order Code(s)")} // ✅ 修改标签 | |||||
| fullWidth | fullWidth | ||||
| disabled={true} | disabled={true} | ||||
| multiline={deliveryOrderCodes.includes(',')} // ✅ 如果有多个代码,使用多行 | |||||
| rows={deliveryOrderCodes.includes(',') ? 2 : 1} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| @@ -22,7 +22,8 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||||
| const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null); | const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null); | ||||
| const [isLoadingSummary, setIsLoadingSummary] = useState(false); | const [isLoadingSummary, setIsLoadingSummary] = useState(false); | ||||
| const [isAssigning, setIsAssigning] = useState(false); | const [isAssigning, setIsAssigning] = useState(false); | ||||
| const [selectedDate, setSelectedDate] = useState<string>("today"); | |||||
| //const [selectedDate, setSelectedDate] = useState<string>("today"); | |||||
| const [selectedDate, setSelectedDate] = useState<string>("2025-09-27"); | |||||
| const loadData = async (dateValue: string) => { | const loadData = async (dateValue: string) => { | ||||
| setIsLoadingSummary(true); | setIsLoadingSummary(true); | ||||
| @@ -131,6 +132,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||||
| <Box sx={{ maxWidth: 300, mb: 2 }}> | <Box sx={{ maxWidth: 300, mb: 2 }}> | ||||
| <FormControl fullWidth size="small"> | <FormControl fullWidth size="small"> | ||||
| <InputLabel id="date-select-label">{t("Select Date")}</InputLabel> | <InputLabel id="date-select-label">{t("Select Date")}</InputLabel> | ||||
| <Select | <Select | ||||
| labelId="date-select-label" | labelId="date-select-label" | ||||
| id="date-select" | id="date-select" | ||||
| @@ -152,6 +154,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||||
| {t("Day After Tomorrow")} ({getDateLabel(2)}) | {t("Day After Tomorrow")} ({getDateLabel(2)}) | ||||
| </MenuItem> | </MenuItem> | ||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| </Box> | </Box> | ||||
| @@ -32,7 +32,9 @@ import { | |||||
| AutoAssignReleaseResponse, | AutoAssignReleaseResponse, | ||||
| checkPickOrderCompletion, | checkPickOrderCompletion, | ||||
| PickOrderCompletionResponse, | PickOrderCompletionResponse, | ||||
| checkAndCompletePickOrderByConsoCode | |||||
| checkAndCompletePickOrderByConsoCode, | |||||
| fetchDoPickOrderDetail, | |||||
| DoPickOrderDetail, | |||||
| } from "@/app/api/pickOrder/actions"; | } from "@/app/api/pickOrder/actions"; | ||||
| import { fetchNameList, NameList } from "@/app/api/user/actions"; | import { fetchNameList, NameList } from "@/app/api/user/actions"; | ||||
| import { | import { | ||||
| @@ -50,7 +52,8 @@ 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 "./FinishedGoodFloorLanePanel"; | import FinishedGoodFloorLanePanel from "./FinishedGoodFloorLanePanel"; | ||||
| import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | |||||
| import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | |||||
| import GoodPickExecutiondetail from "./GoodPickExecutiondetail"; | |||||
| interface Props { | interface Props { | ||||
| filterArgs: Record<string, any>; | filterArgs: Record<string, any>; | ||||
| onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void; | onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void; | ||||
| @@ -322,7 +325,9 @@ const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange }) => | |||||
| const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]); | const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]); | ||||
| const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | ||||
| const [doPickOrderDetail, setDoPickOrderDetail] = useState<DoPickOrderDetail | null>(null); | |||||
| const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>(null); | |||||
| const [pickOrderSwitching, setPickOrderSwitching] = useState(false); | |||||
| const [qrScanInput, setQrScanInput] = useState<string>(''); | const [qrScanInput, setQrScanInput] = useState<string>(''); | ||||
| const [qrScanError, setQrScanError] = useState<boolean>(false); | const [qrScanError, setQrScanError] = useState<boolean>(false); | ||||
| const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false); | const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false); | ||||
| @@ -352,25 +357,48 @@ const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange }) => | |||||
| const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null); | const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null); | ||||
| const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]); | const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]); | ||||
| const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false); | const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false); | ||||
| const fetchFgPickOrdersData = useCallback(async () => { | |||||
| if (!currentUserId) return; | |||||
| // 在 GoodPickExecutiondetail.tsx 中修改 fetchFgPickOrdersData | |||||
| // 修改 fetchFgPickOrdersData 函数: | |||||
| const fetchFgPickOrdersData = useCallback(async () => { | |||||
| if (!currentUserId) return; | |||||
| setFgPickOrdersLoading(true); | |||||
| try { | |||||
| const fgPickOrders = await fetchFGPickOrdersByUserId(currentUserId); | |||||
| setFgPickOrdersLoading(true); | |||||
| try { | |||||
| // ✅ 简化:直接使用 userId 调用 API,不需要循环 | |||||
| const fgPickOrders = await fetchFGPickOrdersByUserId(currentUserId); | |||||
| console.log("🔍 DEBUG: Fetched FG pick orders:", fgPickOrders); | |||||
| console.log("🔍 DEBUG: First order numberOfPickOrders:", fgPickOrders[0]?.numberOfPickOrders); | |||||
| setFgPickOrders(fgPickOrders); | |||||
| // ✅ 如果有多个 pick orders,获取 do_pick_order 详细信息 | |||||
| if (fgPickOrders.length > 0 && fgPickOrders[0].numberOfPickOrders && fgPickOrders[0].numberOfPickOrders > 1) { | |||||
| console.log("🔍 This ticket has multiple pick orders, fetching detail..."); | |||||
| setFgPickOrders(fgPickOrders); | |||||
| onFgPickOrdersChange?.(fgPickOrders); | |||||
| console.log("✅ Fetched FG pick orders for user:", fgPickOrders); | |||||
| } catch (error) { | |||||
| console.error("❌ Error fetching FG pick orders:", error); | |||||
| setFgPickOrders([]); | |||||
| onFgPickOrdersChange?.([]); | |||||
| } finally { | |||||
| setFgPickOrdersLoading(false); | |||||
| try { | |||||
| const detail = await fetchDoPickOrderDetail(fgPickOrders[0].doPickOrderId); | |||||
| console.log("🔍 DEBUG: Fetched do_pick_order detail:", detail); | |||||
| setDoPickOrderDetail(detail); | |||||
| // ✅ 设置默认选中第一个 pick order | |||||
| if (!selectedPickOrderId && detail.pickOrders.length > 0) { | |||||
| setSelectedPickOrderId(detail.pickOrders[0].pick_order_id); | |||||
| } | |||||
| } catch (error) { | |||||
| console.error("Error fetching do_pick_order detail:", error); | |||||
| } | |||||
| } else { | |||||
| console.log("🔍 DEBUG: Single pick order or numberOfPickOrders not > 1"); | |||||
| console.log("🔍 DEBUG: numberOfPickOrders value:", fgPickOrders[0]?.numberOfPickOrders); | |||||
| } | } | ||||
| }, [currentUserId, onFgPickOrdersChange]); | |||||
| } catch (error) { | |||||
| console.error("❌ Error fetching FG pick orders:", error); | |||||
| setFgPickOrders([]); | |||||
| } finally { | |||||
| setFgPickOrdersLoading(false); | |||||
| } | |||||
| }, [currentUserId, selectedPickOrderId]); | |||||
| // ✅ 简化:移除复杂的 useEffect 依赖 | // ✅ 简化:移除复杂的 useEffect 依赖 | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -972,13 +1000,16 @@ return ( | |||||
| ) : ( | ) : ( | ||||
| // ✅ 有活动订单,显示 FG 订单信息 | // ✅ 有活动订单,显示 FG 订单信息 | ||||
| <Box> | <Box> | ||||
| {fgPickOrders.map((fgOrder) => ( | |||||
| <FGPickOrderInfoCard | |||||
| key={fgOrder.pickOrderId} | |||||
| fgOrder={fgOrder} | |||||
| /> | |||||
| ))} | |||||
| </Box> | |||||
| {fgPickOrders.map((fgOrder) => ( | |||||
| <Box key={fgOrder.pickOrderId} sx={{ mb: 2 }}> | |||||
| <FGPickOrderInfoCard | |||||
| fgOrder={fgOrder} | |||||
| /> | |||||
| </Box> | |||||
| ))} | |||||
| </Box> | |||||
| )} | )} | ||||
| {/* Modals */} | {/* Modals */} | ||||
| @@ -13,11 +13,14 @@ | |||||
| "Assigned To": "已分配", | "Assigned To": "已分配", | ||||
| "Do you want to start?": "確定開始嗎?", | "Do you want to start?": "確定開始嗎?", | ||||
| "Start": "開始", | "Start": "開始", | ||||
| "Pick Order Code(s)": "提料單編號", | |||||
| "Delivery Order Code(s)": "送貨單編號", | |||||
| "Start Success": "開始成功", | "Start Success": "開始成功", | ||||
| "Truck Lance Code": "車牌號碼", | "Truck Lance Code": "車牌號碼", | ||||
| "Completed Date": "完成日期", | "Completed Date": "完成日期", | ||||
| "Completed Time": "完成時間", | "Completed Time": "完成時間", | ||||
| "Select Pick Order:": "選擇提料單:", | |||||
| "⚠️ No Stock Available": "⚠️ 沒有庫存", | |||||
| "Start Fail": "開始失敗", | "Start Fail": "開始失敗", | ||||
| "Start PO": "開始採購訂單", | "Start PO": "開始採購訂單", | ||||
| "Do you want to complete?": "確定完成嗎?", | "Do you want to complete?": "確定完成嗎?", | ||||