| @@ -246,6 +246,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; | ||||
| @@ -266,6 +275,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" | ||||
| @@ -280,12 +320,17 @@ export interface UpdateDoPickOrderHideStatusRequest { | |||||
| } | } | ||||
| export interface CompletedDoPickOrderResponse { | export interface CompletedDoPickOrderResponse { | ||||
| id: number; | id: number; | ||||
| doPickOrderRecordId: number; // ✅ 新增 | |||||
| pickOrderId: number; | pickOrderId: number; | ||||
| pickOrderIds: number[]; // ✅ 新增:所有 pick order IDs | |||||
| pickOrderCode: string; | pickOrderCode: string; | ||||
| pickOrderCodes: string; // ✅ 新增:所有 pick order codes (逗号分隔) | |||||
| pickOrderConsoCode: string; | pickOrderConsoCode: string; | ||||
| pickOrderStatus: string; | pickOrderStatus: string; | ||||
| deliveryOrderId: number; | deliveryOrderId: number; | ||||
| deliveryOrderIds: number[]; // ✅ 新增:所有 delivery order IDs | |||||
| deliveryNo: string; | deliveryNo: string; | ||||
| deliveryNos: string; // ✅ 新增:所有 delivery order codes (逗号分隔) | |||||
| deliveryDate: string; | deliveryDate: string; | ||||
| shopId: number; | shopId: number; | ||||
| shopCode: string; | shopCode: string; | ||||
| @@ -295,6 +340,7 @@ export interface CompletedDoPickOrderResponse { | |||||
| shopPoNo: string; | shopPoNo: string; | ||||
| numberOfCartons: number; | numberOfCartons: number; | ||||
| truckLanceCode: string; | truckLanceCode: string; | ||||
| DepartureTime: string; // ✅ 新增 | |||||
| storeId: string; | storeId: string; | ||||
| completedDate: string; | completedDate: string; | ||||
| fgPickOrders: FGPickOrderResponse[]; | fgPickOrders: FGPickOrderResponse[]; | ||||
| @@ -386,6 +432,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> => { | ||||
| @@ -739,6 +799,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; | ||||
| @@ -823,23 +917,32 @@ export const fetchAllPickOrderLotsHierarchical = cache(async (userId: number): P | |||||
| }; | }; | ||||
| } | } | ||||
| }); | }); | ||||
| export const fetchLotDetailsByPickOrderId = async (pickOrderId: number): Promise<any[]> => { | |||||
| export const fetchLotDetailsByDoPickOrderRecordId = async (doPickOrderRecordId: number): Promise<{ | |||||
| fgInfo: any; | |||||
| pickOrders: any[]; | |||||
| }> => { | |||||
| try { | try { | ||||
| console.log("🔍 Fetching lot details for pickOrderId:", pickOrderId); | |||||
| console.log("🔍 Fetching lot details for doPickOrderRecordId:", doPickOrderRecordId); | |||||
| const data = await serverFetchJson<any[]>( | |||||
| `${BASE_API_URL}/pickOrder/lot-details-by-pick-order/${pickOrderId}`, | |||||
| const data = await serverFetchJson<{ | |||||
| fgInfo: any; | |||||
| pickOrders: any[]; | |||||
| }>( | |||||
| `${BASE_API_URL}/pickOrder/lot-details-by-do-pick-order-record/${doPickOrderRecordId}`, | |||||
| { | { | ||||
| method: 'GET', | method: 'GET', | ||||
| next: { tags: ["pickorder"] }, | next: { tags: ["pickorder"] }, | ||||
| } | } | ||||
| ); | ); | ||||
| console.log("✅ Fetched lot details for pickOrderId:", data); | |||||
| console.log("✅ Fetched hierarchical lot details:", data); | |||||
| return data; | return data; | ||||
| } catch (error) { | } catch (error) { | ||||
| console.error("❌ Error fetching lot details for pickOrderId:", error); | |||||
| return []; | |||||
| console.error("❌ Error fetching lot details:", error); | |||||
| return { | |||||
| fgInfo: null, | |||||
| pickOrders: [] | |||||
| }; | |||||
| } | } | ||||
| }; | }; | ||||
| // Update the existing function to use the non-auto-assign endpoint | // Update the existing function to use the non-auto-assign endpoint | ||||
| @@ -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,6 +22,7 @@ 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>("today"); | ||||
| const loadSummaries = useCallback(async () => { | const loadSummaries = useCallback(async () => { | ||||
| @@ -132,6 +133,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" | ||||
| @@ -154,6 +156,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,28 @@ 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); | |||||
| 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); | |||||
| } | |||||
| }, [currentUserId, onFgPickOrdersChange]); | |||||
| console.log("🔍 DEBUG: Fetched FG pick orders:", fgPickOrders); | |||||
| console.log("🔍 DEBUG: First order numberOfPickOrders:", fgPickOrders[0]?.numberOfPickOrders); | |||||
| setFgPickOrders(fgPickOrders); | |||||
| } catch (error) { | |||||
| console.error("❌ Error fetching FG pick orders:", error); | |||||
| setFgPickOrders([]); | |||||
| } finally { | |||||
| setFgPickOrdersLoading(false); | |||||
| } | |||||
| }, [currentUserId, selectedPickOrderId]); | |||||
| // ✅ 简化:移除复杂的 useEffect 依赖 | // ✅ 简化:移除复杂的 useEffect 依赖 | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -972,13 +980,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 */} | ||||
| @@ -44,7 +44,7 @@ import { | |||||
| fetchCompletedDoPickOrders, | fetchCompletedDoPickOrders, | ||||
| CompletedDoPickOrderResponse, | CompletedDoPickOrderResponse, | ||||
| CompletedDoPickOrderSearchParams, | CompletedDoPickOrderSearchParams, | ||||
| fetchLotDetailsByPickOrderId | |||||
| fetchLotDetailsByDoPickOrderRecordId | |||||
| } 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 { | ||||
| @@ -407,30 +407,54 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| setSelectedDoPickOrder(doPickOrder); | setSelectedDoPickOrder(doPickOrder); | ||||
| setShowDetailView(true); | setShowDetailView(true); | ||||
| // ✅ 修复:使用新的 API 根据 pickOrderId 获取 lot 详情 | |||||
| try { | try { | ||||
| const lotDetails = await fetchLotDetailsByPickOrderId(doPickOrder.pickOrderId); | |||||
| setDetailLotData(lotDetails); | |||||
| console.log("✅ Loaded detail lot data for pick order:", doPickOrder.pickOrderCode, lotDetails); | |||||
| // ✅ 使用 doPickOrderRecordId 而不是 pickOrderId | |||||
| const hierarchicalData = await fetchLotDetailsByDoPickOrderRecordId(doPickOrder.doPickOrderRecordId); | |||||
| console.log("✅ Loaded hierarchical lot data:", hierarchicalData); | |||||
| // ✅ 触发打印按钮状态更新 - 基于详情数据 | |||||
| const allCompleted = lotDetails.length > 0 && lotDetails.every(lot => | |||||
| // ✅ 转换为平铺格式 | |||||
| const flatLotData: any[] = []; | |||||
| if (hierarchicalData.pickOrders && hierarchicalData.pickOrders.length > 0) { | |||||
| hierarchicalData.pickOrders.forEach((po: any) => { | |||||
| po.pickOrderLines?.forEach((line: any) => { | |||||
| if (line.lots && line.lots.length > 0) { | |||||
| line.lots.forEach((lot: any) => { | |||||
| flatLotData.push({ | |||||
| pickOrderCode: po.pickOrderCode, | |||||
| itemCode: line.item.code, | |||||
| itemName: line.item.name, | |||||
| lotNo: lot.lotNo, | |||||
| location: lot.location, | |||||
| requiredQty: lot.requiredQty, | |||||
| actualPickQty: lot.actualPickQty, | |||||
| processingStatus: lot.processingStatus, | |||||
| stockOutLineStatus: lot.stockOutLineStatus | |||||
| }); | |||||
| }); | |||||
| } | |||||
| }); | |||||
| }); | |||||
| } | |||||
| setDetailLotData(flatLotData); | |||||
| // ✅ 计算完成状态 | |||||
| const allCompleted = flatLotData.length > 0 && flatLotData.every(lot => | |||||
| lot.processingStatus === 'completed' | lot.processingStatus === 'completed' | ||||
| ); | ); | ||||
| // ✅ 发送事件,包含标签页信息 | |||||
| window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { | window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { | ||||
| detail: { | detail: { | ||||
| allLotsCompleted: allCompleted, | allLotsCompleted: allCompleted, | ||||
| tabIndex: 2 // ✅ 明确指定这是来自标签页 2 的事件 | |||||
| tabIndex: 2 | |||||
| } | } | ||||
| })); | })); | ||||
| } catch (error) { | |||||
| } catch (error) { // ✅ 添加 catch 块 | |||||
| console.error("❌ Error loading detail lot data:", error); | console.error("❌ Error loading detail lot data:", error); | ||||
| setDetailLotData([]); | setDetailLotData([]); | ||||
| // ✅ 如果加载失败,禁用打印按钮 | |||||
| window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { | window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { | ||||
| detail: { | detail: { | ||||
| allLotsCompleted: false, | allLotsCompleted: false, | ||||
| @@ -458,86 +482,132 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| // ✅ 如果显示详情视图,渲染类似 GoodPickExecution 的表格 | // ✅ 如果显示详情视图,渲染类似 GoodPickExecution 的表格 | ||||
| if (showDetailView && selectedDoPickOrder) { | |||||
| return ( | |||||
| <FormProvider {...formProps}> | |||||
| <Box> | |||||
| {/* 返回按钮和标题 */} | |||||
| <Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 2 }}> | |||||
| <Button variant="outlined" onClick={handleBackToList}> | |||||
| {t("Back to List")} | |||||
| </Button> | |||||
| <Typography variant="h6"> | |||||
| {t("Pick Order Details")}: {selectedDoPickOrder.pickOrderCode} | |||||
| </Typography> | |||||
| </Box> | |||||
| // ✅ 如果显示详情视图,渲染层级结构 | |||||
| if (showDetailView && selectedDoPickOrder) { | |||||
| return ( | |||||
| <FormProvider {...formProps}> | |||||
| <Box> | |||||
| {/* 返回按钮和标题 */} | |||||
| <Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 2 }}> | |||||
| <Button variant="outlined" onClick={handleBackToList}> | |||||
| {t("Back to List")} | |||||
| </Button> | |||||
| <Typography variant="h6"> | |||||
| {t("Pick Order Details")}: {selectedDoPickOrder.ticketNo} | |||||
| </Typography> | |||||
| </Box> | |||||
| {/* 订单基本信息 */} | |||||
| <Box sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}> | |||||
| <Typography variant="h6" gutterBottom> | |||||
| {t("Order Information")} | |||||
| </Typography> | |||||
| <Typography variant="body2"> | |||||
| {/* FG 订单基本信息 */} | |||||
| <Paper sx={{ mb: 2, p: 2 }}> | |||||
| <Stack spacing={1}> | |||||
| <Typography variant="subtitle1"> | |||||
| <strong>{t("Shop Name")}:</strong> {selectedDoPickOrder.shopName} | <strong>{t("Shop Name")}:</strong> {selectedDoPickOrder.shopName} | ||||
| </Typography> | </Typography> | ||||
| <Typography variant="body2"> | |||||
| <strong>{t("Delivery No")}:</strong> {selectedDoPickOrder.deliveryNo} | |||||
| <Typography variant="subtitle1"> | |||||
| <strong>{t("Store ID")}:</strong> {selectedDoPickOrder.storeId} | |||||
| </Typography> | </Typography> | ||||
| <Typography variant="body2"> | |||||
| <Typography variant="subtitle1"> | |||||
| <strong>{t("Ticket No.")}:</strong> {selectedDoPickOrder.ticketNo} | |||||
| </Typography> | |||||
| <Typography variant="subtitle1"> | |||||
| <strong>{t("Truck Lance Code")}:</strong> {selectedDoPickOrder.truckLanceCode} | |||||
| </Typography> | |||||
| <Typography variant="subtitle1"> | |||||
| <strong>{t("Completed Date")}:</strong> {dayjs(selectedDoPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)} | <strong>{t("Completed Date")}:</strong> {dayjs(selectedDoPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)} | ||||
| </Typography> | </Typography> | ||||
| </Box> | |||||
| {/* ✅ 添加数据检查 */} | |||||
| {detailLotData.length === 0 ? ( | |||||
| <Box sx={{ p: 3, textAlign: 'center' }}> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {t("No lot details found for this order")} | |||||
| </Typography> | |||||
| </Stack> | |||||
| </Paper> | |||||
| {/* ✅ 添加:多个 Pick Orders 信息(如果有) */} | |||||
| {selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 1 && ( | |||||
| <Paper sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5' }}> | |||||
| <Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}> | |||||
| {t("This ticket contains")} {selectedDoPickOrder.pickOrderIds.length} {t("pick orders")}: | |||||
| </Typography> | |||||
| <Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}> | |||||
| {selectedDoPickOrder.pickOrderCodes?.split(', ').map((code, idx) => ( | |||||
| <Chip | |||||
| key={idx} | |||||
| label={code} | |||||
| size="small" | |||||
| variant="outlined" | |||||
| /> | |||||
| ))} | |||||
| </Box> | </Box> | ||||
| ) : ( | |||||
| /* 显示完成数据的表格 */ | |||||
| <TableContainer component={Paper}> | |||||
| <Table> | |||||
| <TableHead> | |||||
| <TableRow> | |||||
| <TableCell>{t("Pick Order Code")}</TableCell> | |||||
| <TableCell>{t("Item Code")}</TableCell> | |||||
| <TableCell>{t("Item Name")}</TableCell> | |||||
| <TableCell>{t("Lot No")}</TableCell> | |||||
| <TableCell>{t("Location")}</TableCell> | |||||
| <TableCell>{t("Required Qty")}</TableCell> | |||||
| <TableCell>{t("Actual Pick Qty")}</TableCell> | |||||
| <TableCell>{t("Submitted Status")}</TableCell> | |||||
| </TableRow> | |||||
| </TableHead> | |||||
| <TableBody> | |||||
| {detailLotData.map((lot, index) => ( | |||||
| <TableRow key={index}> | |||||
| <TableCell>{lot.pickOrderCode || 'N/A'}</TableCell> | |||||
| <TableCell>{lot.itemCode || 'N/A'}</TableCell> | |||||
| <TableCell>{lot.itemName || 'N/A'}</TableCell> | |||||
| <TableCell>{lot.lotNo || 'N/A'}</TableCell> | |||||
| <TableCell>{lot.location || 'N/A'}</TableCell> | |||||
| <TableCell>{lot.requiredQty || 0}</TableCell> | |||||
| <TableCell>{lot.actualPickQty || 0}</TableCell> | |||||
| <TableCell> | |||||
| <Chip | |||||
| label={t(lot.processingStatus || 'unknown')} | |||||
| color={lot.processingStatus === 'completed' ? 'success' : 'default'} | |||||
| size="small" | |||||
| /> | |||||
| </TableCell> | |||||
| </TableRow> | |||||
| ))} | |||||
| </TableBody> | |||||
| </Table> | |||||
| </TableContainer> | |||||
| )} | |||||
| </Box> | |||||
| </FormProvider> | |||||
| ); | |||||
| } | |||||
| </Paper> | |||||
| )} | |||||
| {/* ✅ 数据检查 */} | |||||
| {detailLotData.length === 0 ? ( | |||||
| <Box sx={{ p: 3, textAlign: 'center' }}> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {t("No lot details found for this order")} | |||||
| </Typography> | |||||
| </Box> | |||||
| ) : ( | |||||
| /* ✅ 按 Pick Order 分组显示 */ | |||||
| <Stack spacing={2}> | |||||
| {/* ✅ 按 pickOrderCode 分组 */} | |||||
| {Object.entries( | |||||
| detailLotData.reduce((acc: any, lot: any) => { | |||||
| const key = lot.pickOrderCode || 'Unknown'; | |||||
| if (!acc[key]) acc[key] = []; | |||||
| acc[key].push(lot); | |||||
| return acc; | |||||
| }, {}) | |||||
| ).map(([pickOrderCode, lots]: [string, any]) => ( | |||||
| <Accordion key={pickOrderCode} defaultExpanded={true}> | |||||
| <AccordionSummary expandIcon={<ExpandMoreIcon />}> | |||||
| <Typography variant="subtitle1" fontWeight="bold"> | |||||
| {t("Pick Order")}: {pickOrderCode} ({(lots as any[]).length} {t("items")}) | |||||
| </Typography> | |||||
| </AccordionSummary> | |||||
| <AccordionDetails> | |||||
| <TableContainer component={Paper}> | |||||
| <Table size="small"> | |||||
| <TableHead> | |||||
| <TableRow> | |||||
| <TableCell>{t("Index")}</TableCell> | |||||
| <TableCell>{t("Item Code")}</TableCell> | |||||
| <TableCell>{t("Item Name")}</TableCell> | |||||
| <TableCell>{t("Lot No")}</TableCell> | |||||
| <TableCell>{t("Location")}</TableCell> | |||||
| <TableCell align="right">{t("Required Qty")}</TableCell> | |||||
| <TableCell align="right">{t("Actual Pick Qty")}</TableCell> | |||||
| <TableCell align="center">{t("Status")}</TableCell> | |||||
| </TableRow> | |||||
| </TableHead> | |||||
| <TableBody> | |||||
| {(lots as any[]).map((lot: any, index: number) => ( | |||||
| <TableRow key={index}> | |||||
| <TableCell>{index + 1}</TableCell> | |||||
| <TableCell>{lot.itemCode || 'N/A'}</TableCell> | |||||
| <TableCell>{lot.itemName || 'N/A'}</TableCell> | |||||
| <TableCell>{lot.lotNo || 'N/A'}</TableCell> | |||||
| <TableCell>{lot.location || 'N/A'}</TableCell> | |||||
| <TableCell align="right">{lot.requiredQty || 0}</TableCell> | |||||
| <TableCell align="right">{lot.actualPickQty || 0}</TableCell> | |||||
| <TableCell align="center"> | |||||
| <Chip | |||||
| label={t(lot.processingStatus || 'unknown')} | |||||
| color={lot.processingStatus === 'completed' ? 'success' : 'default'} | |||||
| size="small" | |||||
| /> | |||||
| </TableCell> | |||||
| </TableRow> | |||||
| ))} | |||||
| </TableBody> | |||||
| </Table> | |||||
| </TableContainer> | |||||
| </AccordionDetails> | |||||
| </Accordion> | |||||
| ))} | |||||
| </Stack> | |||||
| )} | |||||
| </Box> | |||||
| </FormProvider> | |||||
| ); | |||||
| } | |||||
| // ✅ 默认列表视图 | // ✅ 默认列表视图 | ||||
| return ( | return ( | ||||
| @@ -34,14 +34,18 @@ import { | |||||
| fetchFGPickOrders, // ✅ Add this import | fetchFGPickOrders, // ✅ Add this import | ||||
| FGPickOrderResponse, | FGPickOrderResponse, | ||||
| checkPickOrderCompletion, | checkPickOrderCompletion, | ||||
| fetchAllPickOrderLotsHierarchical, | fetchAllPickOrderLotsHierarchical, | ||||
| PickOrderCompletionResponse, | PickOrderCompletionResponse, | ||||
| checkAndCompletePickOrderByConsoCode, | checkAndCompletePickOrderByConsoCode, | ||||
| updateSuggestedLotLineId, | updateSuggestedLotLineId, | ||||
| confirmLotSubstitution, // ✅ 必须添加 | |||||
| confirmLotSubstitution, | |||||
| fetchDoPickOrderDetail, // ✅ 必须添加 | |||||
| DoPickOrderDetail, // ✅ 必须添加 | |||||
| fetchFGPickOrdersByUserId | fetchFGPickOrdersByUserId | ||||
| } from "@/app/api/pickOrder/actions"; | } from "@/app/api/pickOrder/actions"; | ||||
| import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | import FGPickOrderInfoCard from "./FGPickOrderInfoCard"; | ||||
| import LotConfirmationModal from "./LotConfirmationModal"; | import LotConfirmationModal from "./LotConfirmationModal"; | ||||
| //import { fetchItem } from "@/app/api/settings/item"; | //import { fetchItem } from "@/app/api/settings/item"; | ||||
| @@ -76,7 +80,7 @@ const QrCodeModal: React.FC<{ | |||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | ||||
| const [manualInput, setManualInput] = useState<string>(''); | const [manualInput, setManualInput] = useState<string>(''); | ||||
| const [doPickOrderDetail, setDoPickOrderDetail] = useState<DoPickOrderDetail | null>(null); | |||||
| const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>(null); | const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>(null); | ||||
| const [pickOrderSwitching, setPickOrderSwitching] = useState(false); | const [pickOrderSwitching, setPickOrderSwitching] = useState(false); | ||||
| const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false); | const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false); | ||||
| @@ -325,7 +329,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| const [availablePickOrders, setAvailablePickOrders] = useState<any[]>([]); | |||||
| const [doPickOrderDetail, setDoPickOrderDetail] = useState<DoPickOrderDetail | null>(null); | |||||
| const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>(null); | const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>(null); | ||||
| const [pickOrderSwitching, setPickOrderSwitching] = useState(false); | const [pickOrderSwitching, setPickOrderSwitching] = useState(false); | ||||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | const currentUserId = session?.id ? parseInt(session.id) : undefined; | ||||
| @@ -381,6 +385,9 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| try { | try { | ||||
| const fgPickOrders = await fetchFGPickOrdersByUserId(currentUserId); | const fgPickOrders = await fetchFGPickOrdersByUserId(currentUserId); | ||||
| console.log("🔍 DEBUG: Fetched FG pick orders:", fgPickOrders); | |||||
| console.log("🔍 DEBUG: First order numberOfPickOrders:", fgPickOrders[0]?.numberOfPickOrders); | |||||
| setFgPickOrders(fgPickOrders); | setFgPickOrders(fgPickOrders); | ||||
| // ✅ 移除:不需要再单独调用 fetchDoPickOrderDetail | // ✅ 移除:不需要再单独调用 fetchDoPickOrderDetail | ||||
| @@ -437,11 +444,13 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| return allCompleted; | return allCompleted; | ||||
| }, []); | }, []); | ||||
| const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdOverride?: number) => { | const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdOverride?: number) => { | ||||
| setCombinedDataLoading(true); | setCombinedDataLoading(true); | ||||
| try { | try { | ||||
| const userIdToUse = userId || currentUserId; | const userIdToUse = userId || currentUserId; | ||||
| console.log("🔍 fetchAllCombinedLotData called with userId:", userIdToUse); | console.log("🔍 fetchAllCombinedLotData called with userId:", userIdToUse); | ||||
| if (!userIdToUse) { | if (!userIdToUse) { | ||||
| console.warn("⚠️ No userId available, skipping API call"); | console.warn("⚠️ No userId available, skipping API call"); | ||||
| @@ -451,6 +460,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| return; | return; | ||||
| } | } | ||||
| // ✅ 获取新结构的层级数据 | |||||
| // ✅ 获取新结构的层级数据 | // ✅ 获取新结构的层级数据 | ||||
| const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse); | const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse); | ||||
| console.log("✅ Hierarchical data (new structure):", hierarchicalData); | console.log("✅ Hierarchical data (new structure):", hierarchicalData); | ||||
| @@ -466,6 +476,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| // ✅ 设置 FG info 到 fgPickOrders(用于显示 FG 信息卡片) | // ✅ 设置 FG info 到 fgPickOrders(用于显示 FG 信息卡片) | ||||
| const fgOrder: FGPickOrderResponse = { | const fgOrder: FGPickOrderResponse = { | ||||
| doPickOrderId: hierarchicalData.fgInfo.doPickOrderId, | |||||
| ticketNo: hierarchicalData.fgInfo.ticketNo, | ticketNo: hierarchicalData.fgInfo.ticketNo, | ||||
| storeId: hierarchicalData.fgInfo.storeId, | storeId: hierarchicalData.fgInfo.storeId, | ||||
| shopCode: hierarchicalData.fgInfo.shopCode, | shopCode: hierarchicalData.fgInfo.shopCode, | ||||
| @@ -489,17 +500,54 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| qrCodeData: hierarchicalData.fgInfo.doPickOrderId, | qrCodeData: hierarchicalData.fgInfo.doPickOrderId, | ||||
| // ✅ 新增:多个 pick orders 信息 | // ✅ 新增:多个 pick orders 信息 | ||||
| // numberOfPickOrders: hierarchicalData.pickOrders.length, | |||||
| // pickOrderIds: hierarchicalData.pickOrders.map((po: any) => po.pickOrderId), | |||||
| //pickOrderCodes: hierarchicalData.pickOrders.map((po: any) => po.pickOrderCode).join(", "), | |||||
| // deliveryOrderIds: hierarchicalData.pickOrders.map((po: any) => po.doOrderId), | |||||
| //deliveryNos: hierarchicalData.pickOrders.map((po: any) => po.deliveryOrderCode).join(", ") | |||||
| numberOfPickOrders: hierarchicalData.pickOrders.length, | |||||
| pickOrderIds: hierarchicalData.pickOrders.map((po: any) => po.pickOrderId), | |||||
| pickOrderCodes: hierarchicalData.pickOrders.map((po: any) => po.pickOrderCode).join(", "), | |||||
| deliveryOrderIds: hierarchicalData.pickOrders.map((po: any) => po.doOrderId), | |||||
| deliveryNos: hierarchicalData.pickOrders.map((po: any) => po.deliveryOrderCode).join(", ") | |||||
| }; | }; | ||||
| setFgPickOrders([fgOrder]); | setFgPickOrders([fgOrder]); | ||||
| setAvailablePickOrders(hierarchicalData.pickOrders); | |||||
| // ✅ 构建 doPickOrderDetail(用于 switcher) | // ✅ 构建 doPickOrderDetail(用于 switcher) | ||||
| if (hierarchicalData.pickOrders.length > 1) { | |||||
| const detail: DoPickOrderDetail = { | |||||
| doPickOrder: { | |||||
| id: hierarchicalData.fgInfo.doPickOrderId, | |||||
| store_id: hierarchicalData.fgInfo.storeId, | |||||
| ticket_no: hierarchicalData.fgInfo.ticketNo, | |||||
| ticket_status: "", | |||||
| truck_id: 0, | |||||
| truck_departure_time: hierarchicalData.fgInfo.departureTime, | |||||
| shop_id: 0, | |||||
| handled_by: null, | |||||
| loading_sequence: 0, | |||||
| ticket_release_time: null, | |||||
| TruckLanceCode: hierarchicalData.fgInfo.truckLanceCode, | |||||
| ShopCode: hierarchicalData.fgInfo.shopCode, | |||||
| ShopName: hierarchicalData.fgInfo.shopName, | |||||
| RequiredDeliveryDate: "" | |||||
| }, | |||||
| pickOrders: hierarchicalData.pickOrders.map((po: any) => ({ | |||||
| pick_order_id: po.pickOrderId, | |||||
| pick_order_code: po.pickOrderCode, | |||||
| do_order_id: po.doOrderId, | |||||
| delivery_order_code: po.deliveryOrderCode, | |||||
| consoCode: po.consoCode, | |||||
| status: po.status, | |||||
| targetDate: po.targetDate | |||||
| })), | |||||
| selectedPickOrderId: pickOrderIdOverride || hierarchicalData.pickOrders[0]?.pickOrderId || 0, | |||||
| lotDetails: [] | |||||
| }; | |||||
| setDoPickOrderDetail(detail); | |||||
| // ✅ 设置默认选中的 pick order ID | |||||
| if (!selectedPickOrderId) { | |||||
| setSelectedPickOrderId(pickOrderIdOverride || hierarchicalData.pickOrders[0]?.pickOrderId); | |||||
| } | |||||
| } | |||||
| // ✅ 确定要显示的 pick order | // ✅ 确定要显示的 pick order | ||||
| const targetPickOrderId = pickOrderIdOverride || selectedPickOrderId || hierarchicalData.pickOrders[0]?.pickOrderId; | const targetPickOrderId = pickOrderIdOverride || selectedPickOrderId || hierarchicalData.pickOrders[0]?.pickOrderId; | ||||
| @@ -615,14 +663,16 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| }); | }); | ||||
| } | } | ||||
| }); | }); | ||||
| console.log("✅ Transformed flat lot data:", flatLotData); | console.log("✅ Transformed flat lot data:", flatLotData); | ||||
| console.log("🔍 Total items (including null stock):", flatLotData.length); | console.log("🔍 Total items (including null stock):", flatLotData.length); | ||||
| setCombinedLotData(flatLotData); | setCombinedLotData(flatLotData); | ||||
| setOriginalCombinedData(flatLotData); | setOriginalCombinedData(flatLotData); | ||||
| checkAllLotsCompleted(flatLotData); | checkAllLotsCompleted(flatLotData); | ||||
| } catch (error) { | } catch (error) { | ||||
| console.error("❌ Error fetching combined lot data:", error); | console.error("❌ Error fetching combined lot data:", error); | ||||
| setCombinedLotData([]); | setCombinedLotData([]); | ||||
| @@ -1463,8 +1513,9 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe | |||||
| console.error("Error switching pick order:", error); | console.error("Error switching pick order:", error); | ||||
| } finally { | } finally { | ||||
| setPickOrderSwitching(false); | setPickOrderSwitching(false); | ||||
| } | |||||
| }, [pickOrderSwitching, currentUserId, fetchAllCombinedLotData]); | |||||
| } | |||||
| }, [pickOrderSwitching, currentUserId, fetchAllCombinedLotData]); | |||||
| const handleStopScan = useCallback(() => { | const handleStopScan = useCallback(() => { | ||||
| console.log("⏹️ Stopping manual QR scan..."); | console.log("⏹️ Stopping manual QR scan..."); | ||||
| setIsManualScanning(false); | setIsManualScanning(false); | ||||
| @@ -1629,6 +1680,8 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe | |||||
| <strong>{t("Departure Time")}:</strong> {fgPickOrders[0].DepartureTime || '-'} | <strong>{t("Departure Time")}:</strong> {fgPickOrders[0].DepartureTime || '-'} | ||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| </Stack> | </Stack> | ||||
| </Paper> | </Paper> | ||||
| @@ -1638,28 +1691,28 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe | |||||
| {/* ✅ FG Info Card */} | {/* ✅ FG Info Card */} | ||||
| {/* ✅ Pick Order Switcher - 放在 FG Info 下面,QR 按钮上面 */} | {/* ✅ Pick Order Switcher - 放在 FG Info 下面,QR 按钮上面 */} | ||||
| {availablePickOrders.length > 1 && ( | |||||
| <Box sx={{ mb: 2, mt: 1 }}> | |||||
| <Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}> | |||||
| {t("Select Pick Order:")} | |||||
| </Typography> | |||||
| <Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}> | |||||
| {availablePickOrders.map((po: any) => ( | |||||
| <Chip | |||||
| key={po.pickOrderId} | |||||
| label={`${po.pickOrderCode} (${po.deliveryOrderCode})`} | |||||
| onClick={() => handlePickOrderSwitch(po.pickOrderId)} | |||||
| color={selectedPickOrderId === po.pickOrderId ? "primary" : "default"} | |||||
| variant={selectedPickOrderId === po.pickOrderId ? "filled" : "outlined"} | |||||
| sx={{ | |||||
| cursor: 'pointer', | |||||
| '&:hover': { backgroundColor: 'primary.light', color: 'white' } | |||||
| }} | |||||
| /> | |||||
| ))} | |||||
| </Box> | |||||
| </Box> | |||||
| )} | |||||
| {doPickOrderDetail && doPickOrderDetail.pickOrders.length > 1 && ( | |||||
| <Box sx={{ mb: 2, mt: 1 }}> | |||||
| <Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}> | |||||
| {t("Select Pick Order:")} | |||||
| </Typography> | |||||
| <Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}> | |||||
| {doPickOrderDetail.pickOrders.map((po: any) => ( | |||||
| <Chip | |||||
| key={po.pick_order_id} | |||||
| label={`${po.pick_order_code} (${po.delivery_order_code})`} | |||||
| onClick={() => handlePickOrderSwitch(po.pick_order_id)} | |||||
| color={selectedPickOrderId === po.pick_order_id ? "primary" : "default"} | |||||
| variant={selectedPickOrderId === po.pick_order_id ? "filled" : "outlined"} | |||||
| sx={{ | |||||
| cursor: 'pointer', | |||||
| '&:hover': { backgroundColor: 'primary.light', color: 'white' } | |||||
| }} | |||||
| /> | |||||
| ))} | |||||
| </Box> | |||||
| </Box> | |||||
| )} | |||||
| </Box> | </Box> | ||||
| {/* ✅ 保留:Combined Lot Table - 包含所有 QR 扫描功能 */} | {/* ✅ 保留:Combined Lot Table - 包含所有 QR 扫描功能 */} | ||||
| <Box> | <Box> | ||||
| @@ -1967,8 +2020,12 @@ paginatedData.map((lot, index) => { | |||||
| /> | /> | ||||
| )} | )} | ||||
| </FormProvider> | </FormProvider> | ||||
| </TestQrCodeProvider> | </TestQrCodeProvider> | ||||
| ); | ); | ||||
| }; | }; | ||||
| export default PickExecution; | |||||
| export default PickExecution; | |||||
| @@ -25,17 +25,19 @@ import dayjs from "dayjs"; | |||||
| import { fetchInventories } from "@/app/api/inventory/actions"; | import { fetchInventories } from "@/app/api/inventory/actions"; | ||||
| import { InventoryResult } from "@/app/api/inventory"; | import { InventoryResult } from "@/app/api/inventory"; | ||||
| import { PrinterCombo } from "@/app/api/settings/printer"; | |||||
| interface Props { | interface Props { | ||||
| defaultInputs: SearchJoResultRequest, | defaultInputs: SearchJoResultRequest, | ||||
| bomCombo: BomCombo[] | bomCombo: BomCombo[] | ||||
| printerCombo: PrinterCombo[]; | |||||
| } | } | ||||
| type SearchQuery = Partial<Omit<JobOrder, "id">>; | type SearchQuery = Partial<Omit<JobOrder, "id">>; | ||||
| type SearchParamNames = keyof SearchQuery; | type SearchParamNames = keyof SearchQuery; | ||||
| const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo, printerCombo }) => { | |||||
| const { t } = useTranslation("jo"); | const { t } = useTranslation("jo"); | ||||
| const router = useRouter() | const router = useRouter() | ||||
| const [filteredJos, setFilteredJos] = useState<JobOrder[]>([]); | const [filteredJos, setFilteredJos] = useState<JobOrder[]>([]); | ||||
| @@ -426,7 +428,7 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| open={openModal} | open={openModal} | ||||
| onClose={closeNewModal} | onClose={closeNewModal} | ||||
| inputDetail={modalInfo} | inputDetail={modalInfo} | ||||
| printerCombo={[]} | |||||
| printerCombo={printerCombo} | |||||
| // skipQc={true} | // skipQc={true} | ||||
| /> | /> | ||||
| </> | </> | ||||
| @@ -3,6 +3,7 @@ import GeneralLoading from "../General/GeneralLoading"; | |||||
| import JoSearch from "./JoSearch"; | import JoSearch from "./JoSearch"; | ||||
| import { SearchJoResultRequest } from "@/app/api/jo/actions"; | import { SearchJoResultRequest } from "@/app/api/jo/actions"; | ||||
| import { fetchBomCombo } from "@/app/api/bom"; | import { fetchBomCombo } from "@/app/api/bom"; | ||||
| import { fetchPrinterCombo } from "@/app/api/settings/printer"; | |||||
| interface SubComponents { | interface SubComponents { | ||||
| Loading: typeof GeneralLoading; | Loading: typeof GeneralLoading; | ||||
| @@ -15,12 +16,14 @@ const JoSearchWrapper: React.FC & SubComponents = async () => { | |||||
| } | } | ||||
| const [ | const [ | ||||
| bomCombo | |||||
| bomCombo, | |||||
| printerCombo | |||||
| ] = await Promise.all([ | ] = await Promise.all([ | ||||
| fetchBomCombo() | |||||
| fetchBomCombo(), | |||||
| fetchPrinterCombo() | |||||
| ]) | ]) | ||||
| return <JoSearch defaultInputs={defaultInputs} bomCombo={bomCombo}/> | |||||
| return <JoSearch defaultInputs={defaultInputs} bomCombo={bomCombo} printerCombo={printerCombo}/> | |||||
| } | } | ||||
| JoSearchWrapper.Loading = GeneralLoading; | JoSearchWrapper.Loading = GeneralLoading; | ||||
| @@ -14,16 +14,16 @@ const Logo: React.FC<Props> = ({ width, height }) => { | |||||
| <g | <g | ||||
| id="svgGroup" | id="svgGroup" | ||||
| strokeLinecap="round" | strokeLinecap="round" | ||||
| fill-rule="evenodd" | |||||
| font-size="9pt" | |||||
| fillRule="evenodd" | |||||
| fontSize="9pt" | |||||
| stroke="#000" | stroke="#000" | ||||
| stroke-width="0.25mm" | |||||
| strokeWidth="0.25mm" | |||||
| fill="#000" | fill="#000" | ||||
| // style="stroke:#000;stroke-width:0.25mm;fill:#000" | // style="stroke:#000;stroke-width:0.25mm;fill:#000" | ||||
| > | > | ||||
| <path | <path | ||||
| d="M 72.768 0.48 L 75.744 0.48 L 88.224 29.568 L 88.56 29.568 L 101.04 0.48 L 103.92 0.48 L 103.92 34.416 L 101.136 34.416 L 101.136 7.344 L 100.896 7.344 L 89.136 34.416 L 87.504 34.416 L 75.744 7.344 L 75.552 7.344 L 75.552 34.416 L 72.768 34.416 L 72.768 0.48 Z M 137.808 0.48 L 140.784 0.48 L 153.264 29.568 L 153.6 29.568 L 166.08 0.48 L 168.96 0.48 L 168.96 34.416 L 166.176 34.416 L 166.176 7.344 L 165.936 7.344 L 154.176 34.416 L 152.544 34.416 L 140.784 7.344 L 140.592 7.344 L 140.592 34.416 L 137.808 34.416 L 137.808 0.48 Z M 198.72 7.824 L 195.84 7.824 Q 195.456 4.848 193.224 3.696 Q 190.992 2.544 187.344 2.544 Q 183.168 2.544 181.152 4.152 Q 179.136 5.76 179.136 8.88 Q 179.136 10.704 179.832 11.856 Q 180.528 13.008 181.632 13.704 Q 182.736 14.4 183.984 14.808 Q 185.232 15.216 186.288 15.504 L 189.984 16.512 Q 191.376 16.896 193.008 17.472 Q 194.64 18.048 196.104 19.056 Q 197.568 20.064 198.48 21.648 Q 199.392 23.232 199.392 25.584 Q 199.392 28.272 198.096 30.432 Q 196.8 32.592 194.112 33.816 Q 191.424 35.04 187.248 35.04 Q 181.68 35.04 178.704 32.784 Q 175.728 30.528 175.344 26.688 L 178.32 26.688 Q 178.608 28.992 179.808 30.24 Q 181.008 31.488 182.904 31.992 Q 184.8 32.496 187.248 32.496 Q 191.664 32.496 194.088 30.792 Q 196.512 29.088 196.512 25.488 Q 196.512 23.328 195.48 22.08 Q 194.448 20.832 192.744 20.112 Q 191.04 19.392 189.072 18.864 L 184.464 17.616 Q 180.528 16.512 178.392 14.544 Q 176.256 12.576 176.256 9.12 Q 176.256 6.288 177.624 4.248 Q 178.992 2.208 181.512 1.104 Q 184.032 0 187.488 0 Q 190.848 0 193.272 0.984 Q 195.696 1.968 197.112 3.72 Q 198.528 5.472 198.72 7.824 Z M 0 34.416 L 0 0.48 L 19.344 0.48 L 19.344 2.976 L 2.88 2.976 L 2.88 16.176 L 17.76 16.176 L 17.76 18.672 L 2.88 18.672 L 2.88 34.416 L 0 34.416 Z M 108.336 2.976 L 108.336 0.48 L 133.392 0.48 L 133.392 2.976 L 122.304 2.976 L 122.304 34.416 L 119.424 34.416 L 119.424 2.976 L 108.336 2.976 Z M 25.152 34.416 L 25.152 0.48 L 36.48 0.48 Q 40.56 0.48 43.056 1.752 Q 45.552 3.024 46.704 5.328 Q 47.856 7.632 47.856 10.8 Q 47.856 13.968 46.704 16.32 Q 45.552 18.672 43.08 19.968 Q 40.608 21.264 36.576 21.264 L 28.032 21.264 L 28.032 34.416 L 25.152 34.416 Z M 28.032 18.768 L 36.384 18.768 Q 39.744 18.768 41.616 17.784 Q 43.488 16.8 44.232 15 Q 44.976 13.2 44.976 10.8 Q 44.976 8.352 44.232 6.6 Q 43.488 4.848 41.616 3.912 Q 39.744 2.976 36.288 2.976 L 28.032 2.976 L 28.032 18.768 Z M 65.664 18 L 65.664 20.496 L 52.704 20.496 L 52.704 18 L 65.664 18 Z" | d="M 72.768 0.48 L 75.744 0.48 L 88.224 29.568 L 88.56 29.568 L 101.04 0.48 L 103.92 0.48 L 103.92 34.416 L 101.136 34.416 L 101.136 7.344 L 100.896 7.344 L 89.136 34.416 L 87.504 34.416 L 75.744 7.344 L 75.552 7.344 L 75.552 34.416 L 72.768 34.416 L 72.768 0.48 Z M 137.808 0.48 L 140.784 0.48 L 153.264 29.568 L 153.6 29.568 L 166.08 0.48 L 168.96 0.48 L 168.96 34.416 L 166.176 34.416 L 166.176 7.344 L 165.936 7.344 L 154.176 34.416 L 152.544 34.416 L 140.784 7.344 L 140.592 7.344 L 140.592 34.416 L 137.808 34.416 L 137.808 0.48 Z M 198.72 7.824 L 195.84 7.824 Q 195.456 4.848 193.224 3.696 Q 190.992 2.544 187.344 2.544 Q 183.168 2.544 181.152 4.152 Q 179.136 5.76 179.136 8.88 Q 179.136 10.704 179.832 11.856 Q 180.528 13.008 181.632 13.704 Q 182.736 14.4 183.984 14.808 Q 185.232 15.216 186.288 15.504 L 189.984 16.512 Q 191.376 16.896 193.008 17.472 Q 194.64 18.048 196.104 19.056 Q 197.568 20.064 198.48 21.648 Q 199.392 23.232 199.392 25.584 Q 199.392 28.272 198.096 30.432 Q 196.8 32.592 194.112 33.816 Q 191.424 35.04 187.248 35.04 Q 181.68 35.04 178.704 32.784 Q 175.728 30.528 175.344 26.688 L 178.32 26.688 Q 178.608 28.992 179.808 30.24 Q 181.008 31.488 182.904 31.992 Q 184.8 32.496 187.248 32.496 Q 191.664 32.496 194.088 30.792 Q 196.512 29.088 196.512 25.488 Q 196.512 23.328 195.48 22.08 Q 194.448 20.832 192.744 20.112 Q 191.04 19.392 189.072 18.864 L 184.464 17.616 Q 180.528 16.512 178.392 14.544 Q 176.256 12.576 176.256 9.12 Q 176.256 6.288 177.624 4.248 Q 178.992 2.208 181.512 1.104 Q 184.032 0 187.488 0 Q 190.848 0 193.272 0.984 Q 195.696 1.968 197.112 3.72 Q 198.528 5.472 198.72 7.824 Z M 0 34.416 L 0 0.48 L 19.344 0.48 L 19.344 2.976 L 2.88 2.976 L 2.88 16.176 L 17.76 16.176 L 17.76 18.672 L 2.88 18.672 L 2.88 34.416 L 0 34.416 Z M 108.336 2.976 L 108.336 0.48 L 133.392 0.48 L 133.392 2.976 L 122.304 2.976 L 122.304 34.416 L 119.424 34.416 L 119.424 2.976 L 108.336 2.976 Z M 25.152 34.416 L 25.152 0.48 L 36.48 0.48 Q 40.56 0.48 43.056 1.752 Q 45.552 3.024 46.704 5.328 Q 47.856 7.632 47.856 10.8 Q 47.856 13.968 46.704 16.32 Q 45.552 18.672 43.08 19.968 Q 40.608 21.264 36.576 21.264 L 28.032 21.264 L 28.032 34.416 L 25.152 34.416 Z M 28.032 18.768 L 36.384 18.768 Q 39.744 18.768 41.616 17.784 Q 43.488 16.8 44.232 15 Q 44.976 13.2 44.976 10.8 Q 44.976 8.352 44.232 6.6 Q 43.488 4.848 41.616 3.912 Q 39.744 2.976 36.288 2.976 L 28.032 2.976 L 28.032 18.768 Z M 65.664 18 L 65.664 20.496 L 52.704 20.496 L 52.704 18 L 65.664 18 Z" | ||||
| vector-effect="non-scaling-stroke" | |||||
| vectorEffect="non-scaling-stroke" | |||||
| /> | /> | ||||
| </g> | </g> | ||||
| </svg> | </svg> | ||||
| @@ -123,7 +123,7 @@ | |||||
| /* Input styles */ | /* Input styles */ | ||||
| /* .tiptap-input { | /* .tiptap-input { | ||||
| font-size: 14px; | |||||
| fontSize: 14px; | |||||
| font-weight: 500; | font-weight: 500; | ||||
| line-height: 12px; | line-height: 12px; | ||||
| } */ | } */ | ||||
| @@ -67,7 +67,7 @@ const textfieldSx = { | |||||
| transform: "translate(14px, 1.2rem) scale(1)", | transform: "translate(14px, 1.2rem) scale(1)", | ||||
| "&.MuiInputLabel-shrink": { | "&.MuiInputLabel-shrink": { | ||||
| fontSize: 24, | fontSize: 24, | ||||
| transform: "translate(14px, -0.5rem) scale(1)", | |||||
| transform: "translate(14px, -9px) scale(1)", | |||||
| }, | }, | ||||
| // [theme.breakpoints.down("sm")]: { | // [theme.breakpoints.down("sm")]: { | ||||
| // fontSize: "1rem", | // fontSize: "1rem", | ||||
| @@ -60,7 +60,7 @@ const textfieldSx = { | |||||
| transform: "translate(14px, 1.2rem) scale(1)", | transform: "translate(14px, 1.2rem) scale(1)", | ||||
| "&.MuiInputLabel-shrink": { | "&.MuiInputLabel-shrink": { | ||||
| fontSize: 24, | fontSize: 24, | ||||
| transform: "translate(14px, -0.5rem) scale(1)", | |||||
| transform: "translate(14px, -9px) scale(1)", | |||||
| }, | }, | ||||
| // [theme.breakpoints.down("sm")]: { | // [theme.breakpoints.down("sm")]: { | ||||
| // fontSize: "1rem", | // fontSize: "1rem", | ||||
| @@ -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?": "確定完成嗎?", | ||||
| @@ -164,5 +164,6 @@ | |||||
| "Expiry Date cannot be earlier than Production Date": "到期日不可早於生產日期", | "Expiry Date cannot be earlier than Production Date": "到期日不可早於生產日期", | ||||
| "Production Date must be earlier than Expiry Date": "生產日期必須早於到期日", | "Production Date must be earlier than Expiry Date": "生產日期必須早於到期日", | ||||
| "confirm expiry date": "確認到期日", | "confirm expiry date": "確認到期日", | ||||
| "Invalid Date": "無效日期" | |||||
| "Invalid Date": "無效日期", | |||||
| "Missing QC Template, please contact administrator": "找不到品檢模板,請聯絡管理員" | |||||
| } | } | ||||