kelvin.yau 2 months ago
parent
commit
ab3032eb91
5 changed files with 112 additions and 108 deletions
  1. +16
    -6
      src/app/api/pickOrder/actions.ts
  2. +3
    -3
      src/components/FinishedGoodSearch/FGPickOrderInfoCard.tsx
  3. +24
    -11
      src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx
  4. +65
    -88
      src/components/FinishedGoodSearch/GoodPickExecution.tsx
  5. +4
    -0
      src/i18n/zh/pickOrder.json

+ 16
- 6
src/app/api/pickOrder/actions.ts View File

@@ -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}`,


+ 3
- 3
src/components/FinishedGoodSearch/FGPickOrderInfoCard.tsx View File

@@ -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
- 11
src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx View File

@@ -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)})


+ 65
- 88
src/components/FinishedGoodSearch/GoodPickExecution.tsx View File

@@ -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;

+ 4
- 0
src/i18n/zh/pickOrder.json View File

@@ -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?": "確定完成嗎?",


Loading…
Cancel
Save