| @@ -111,7 +111,7 @@ export interface GetPickOrderLineInfo { | |||
| itemName: string; | |||
| availableQty: number| null; | |||
| requiredQty: number; | |||
| uomCode: string; | |||
| uomShortDesc: string; | |||
| uomDesc: string; | |||
| suggestedList: any[]; | |||
| pickedQty: number; | |||
| @@ -245,6 +245,15 @@ export interface UpdateSuggestedLotLineIdRequest { | |||
| newLotLineId: number; | |||
| } | |||
| 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; | |||
| pickOrderCode: string; | |||
| pickOrderConsoCode: string; | |||
| @@ -265,6 +274,37 @@ export interface FGPickOrderResponse { | |||
| storeId: string; | |||
| 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 { | |||
| userId: number; | |||
| storeId: string; // "2/F" | "4/F" | |||
| @@ -385,6 +425,20 @@ export interface LaneBtn { | |||
| unassigned: 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 ( | |||
| data: UpdatePickExecutionIssueRequest | |||
| ): Promise<PostPickOrderResponse> => { | |||
| @@ -738,6 +792,40 @@ interface SuggestionWithStatus { | |||
| stockOutLineQty?: number; | |||
| 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 { | |||
| id: number | null; | |||
| name: string; | |||
| @@ -2,14 +2,22 @@ | |||
| import { Box, Card, CardContent, Grid, TextField, Stack } from "@mui/material"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { FGPickOrderResponse } from "@/app/api/pickOrder/actions"; | |||
| import { FGPickOrderResponse, DoPickOrderDetail } from "@/app/api/pickOrder/actions"; | |||
| interface Props { | |||
| fgOrder: FGPickOrderResponse; | |||
| doPickOrderDetail?: DoPickOrderDetail | null; | |||
| } | |||
| const FGPickOrderInfoCard: React.FC<Props> = ({ fgOrder }) => { | |||
| const FGPickOrderInfoCard: React.FC<Props> = ({ fgOrder, doPickOrderDetail }) => { | |||
| const { t } = useTranslation("pickOrder"); | |||
| if (!fgOrder) { | |||
| return null; | |||
| } | |||
| const pickOrderCodes = fgOrder.pickOrderCodes || ""; | |||
| const deliveryOrderCodes = fgOrder.deliveryNos || ""; | |||
| return ( | |||
| <Card sx={{ display: "block", mb: 2 }}> | |||
| @@ -17,21 +25,25 @@ const FGPickOrderInfoCard: React.FC<Props> = ({ fgOrder }) => { | |||
| <Box> | |||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||
| <Grid item xs={6}> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| value={fgOrder.pickOrderCode || ""} | |||
| label={t("Pick Order Code")} | |||
| value={pickOrderCodes || ""} // ✅ 显示所有 pick order codes | |||
| label={t("Pick Order Code(s)")} // ✅ 修改标签 | |||
| fullWidth | |||
| disabled={true} | |||
| multiline={pickOrderCodes.includes(',')} // ✅ 如果有多个代码,使用多行 | |||
| rows={pickOrderCodes.includes(',') ? 2 : 1} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| value={fgOrder.deliveryNo || ""} | |||
| label={t("Delivery No")} | |||
| value={deliveryOrderCodes || ""} // ✅ 显示所有 delivery order codes | |||
| label={t("Delivery Order Code(s)")} // ✅ 修改标签 | |||
| fullWidth | |||
| disabled={true} | |||
| multiline={deliveryOrderCodes.includes(',')} // ✅ 如果有多个代码,使用多行 | |||
| rows={deliveryOrderCodes.includes(',') ? 2 : 1} | |||
| /> | |||
| </Grid> | |||
| @@ -22,7 +22,8 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||
| const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null); | |||
| const [isLoadingSummary, setIsLoadingSummary] = 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) => { | |||
| setIsLoadingSummary(true); | |||
| @@ -131,6 +132,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||
| <Box sx={{ maxWidth: 300, mb: 2 }}> | |||
| <FormControl fullWidth size="small"> | |||
| <InputLabel id="date-select-label">{t("Select Date")}</InputLabel> | |||
| <Select | |||
| labelId="date-select-label" | |||
| id="date-select" | |||
| @@ -152,6 +154,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||
| {t("Day After Tomorrow")} ({getDateLabel(2)}) | |||
| </MenuItem> | |||
| </Select> | |||
| </FormControl> | |||
| </Box> | |||
| @@ -32,7 +32,9 @@ import { | |||
| AutoAssignReleaseResponse, | |||
| checkPickOrderCompletion, | |||
| PickOrderCompletionResponse, | |||
| checkAndCompletePickOrderByConsoCode | |||
| checkAndCompletePickOrderByConsoCode, | |||
| fetchDoPickOrderDetail, | |||
| DoPickOrderDetail, | |||
| } from "@/app/api/pickOrder/actions"; | |||
| import { fetchNameList, NameList } from "@/app/api/user/actions"; | |||
| import { | |||
| @@ -50,7 +52,8 @@ import { fetchStockInLineInfo } from "@/app/api/po/actions"; | |||
| import GoodPickExecutionForm from "./GoodPickExecutionForm"; | |||
| import FGPickOrderCard from "./FGPickOrderCard"; | |||
| import FinishedGoodFloorLanePanel from "./FinishedGoodFloorLanePanel"; | |||
| import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | |||
| import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | |||
| import GoodPickExecutiondetail from "./GoodPickExecutiondetail"; | |||
| interface Props { | |||
| filterArgs: Record<string, any>; | |||
| onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void; | |||
| @@ -322,7 +325,9 @@ const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange }) => | |||
| const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]); | |||
| 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 [qrScanError, setQrScanError] = 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 [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]); | |||
| 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(() => { | |||
| @@ -972,13 +1000,16 @@ return ( | |||
| ) : ( | |||
| // ✅ 有活动订单,显示 FG 订单信息 | |||
| <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 */} | |||
| @@ -13,11 +13,14 @@ | |||
| "Assigned To": "已分配", | |||
| "Do you want to start?": "確定開始嗎?", | |||
| "Start": "開始", | |||
| "Pick Order Code(s)": "提料單編號", | |||
| "Delivery Order Code(s)": "送貨單編號", | |||
| "Start Success": "開始成功", | |||
| "Truck Lance Code": "車牌號碼", | |||
| "Completed Date": "完成日期", | |||
| "Completed Time": "完成時間", | |||
| "Select Pick Order:": "選擇提料單:", | |||
| "⚠️ No Stock Available": "⚠️ 沒有庫存", | |||
| "Start Fail": "開始失敗", | |||
| "Start PO": "開始採購訂單", | |||
| "Do you want to complete?": "確定完成嗎?", | |||