浏览代码

update

master
CANCERYS\kw093 2 个月前
父节点
当前提交
5cde96936c
共有 6 个文件被更改,包括 534 次插入2 次删除
  1. +78
    -0
      src/app/api/pickOrder/actions.ts
  2. +1
    -1
      src/components/DoDetail/DoDetail.tsx
  3. +29
    -0
      src/components/FinishedGoodSearch/FinishedGoodSearch.tsx
  4. +416
    -0
      src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx
  5. +1
    -0
      src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx
  6. +9
    -1
      src/i18n/zh/pickOrder.json

+ 78
- 0
src/app/api/pickOrder/actions.ts 查看文件

@@ -267,6 +267,84 @@ export interface AutoAssignReleaseByStoreRequest {
userId: number;
storeId: string; // "2/F" | "4/F"
}
export interface UpdateDoPickOrderHideStatusRequest {
id: number;
name: string;
code: string;
type: string;
message: string;
errorPosition: string;
}
export interface CompletedDoPickOrderResponse {
id: number;
pickOrderId: number;
pickOrderCode: string;
pickOrderConsoCode: string;
pickOrderStatus: string;
deliveryOrderId: number;
deliveryNo: string;
deliveryDate: string;
shopId: number;
shopCode: string;
shopName: string;
shopAddress: string;
ticketNo: string;
shopPoNo: string;
numberOfCartons: number;
truckNo: string;
storeId: string;
completedDate: string;
fgPickOrders: FGPickOrderResponse[];
}

// ✅ 新增:搜索参数接口
export interface CompletedDoPickOrderSearchParams {
pickOrderCode?: string;
shopName?: string;
deliveryNo?: string;
ticketNo?: string;
}

// ✅ 新增:获取已完成的 DO Pick Orders API
export const fetchCompletedDoPickOrders = async (
userId: number,
searchParams?: CompletedDoPickOrderSearchParams
): Promise<CompletedDoPickOrderResponse[]> => {
const params = new URLSearchParams();
if (searchParams?.pickOrderCode) {
params.append('pickOrderCode', searchParams.pickOrderCode);
}
if (searchParams?.shopName) {
params.append('shopName', searchParams.shopName);
}
if (searchParams?.deliveryNo) {
params.append('deliveryNo', searchParams.deliveryNo);
}
if (searchParams?.ticketNo) {
params.append('ticketNo', searchParams.ticketNo);
}
const queryString = params.toString();
const url = `${BASE_API_URL}/pickOrder/completed-do-pick-orders/${userId}${queryString ? `?${queryString}` : ''}`;
const response = await serverFetchJson<CompletedDoPickOrderResponse[]>(url, {
method: "GET",
});
return response;
};
export const updatePickOrderHideStatus = async (pickOrderId: number, hide: boolean) => {
const response = await serverFetchJson<UpdateDoPickOrderHideStatusRequest>(
`${BASE_API_URL}/pickOrder/update-hide-status/${pickOrderId}?hide=${hide}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
},
);
revalidateTag("pickorder");
return response;
};

export const fetchFGPickOrders = async (pickOrderId: number) => {
const response = await serverFetchJson<FGPickOrderResponse>(


+ 1
- 1
src/components/DoDetail/DoDetail.tsx 查看文件

@@ -64,7 +64,7 @@ const DoDetail: React.FC<Props> = ({
if (response) {
formProps.setValue("status", response.entity.status)
setSuccessMessage("DO released successfully! Pick orders created.")
setSuccessMessage(t("DO released successfully! Pick orders created."))
}
}
} catch (e) {


+ 29
- 0
src/components/FinishedGoodSearch/FinishedGoodSearch.tsx 查看文件

@@ -29,6 +29,7 @@ import Jobcreatitem from "./Jobcreatitem";
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import PickExecutionDetail from "./GoodPickExecutiondetail";
import GoodPickExecutionRecord from "./GoodPickExecutionRecord";
interface Props {
pickOrders: PickOrderResult[];
}
@@ -53,6 +54,17 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
const [tabIndex, setTabIndex] = useState(0);
const [totalCount, setTotalCount] = useState<number>();
const [isAssigning, setIsAssigning] = useState(false);
const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>(
typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true'
);
useEffect(() => {
const onAssigned = () => {
localStorage.removeItem('hideCompletedUntilNext');
setHideCompletedUntilNext(false);
};
window.addEventListener('pickOrderAssigned', onAssigned);
return () => window.removeEventListener('pickOrderAssigned', onAssigned);
}, []);
const handleAssignByStore = async (storeId: "2/F" | "4/F") => {
if (!currentUserId) {
console.error("Missing user id in session");
@@ -326,6 +338,21 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
{/* ✅ Updated print buttons with completion status */}
<Grid item xs={6} display="flex" justifyContent="flex-end">
<Stack direction="row" spacing={1}>
{/*
<Button
variant={hideCompletedUntilNext ? "contained" : "outlined"}
color={hideCompletedUntilNext ? "warning" : "inherit"}
onClick={() => {
const next = !hideCompletedUntilNext;
setHideCompletedUntilNext(next);
if (next) localStorage.setItem('hideCompletedUntilNext', 'true');
else localStorage.removeItem('hideCompletedUntilNext');
window.dispatchEvent(new Event('pickOrderAssigned')); // ask detail to re-fetch
}}
>
{hideCompletedUntilNext ? t("Hide Completed: ON") : t("Hide Completed: OFF")}
</Button>
*/}
<Button
variant="contained"
disabled={!printButtonsEnabled}
@@ -369,6 +396,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Pick Order Detail")} iconPosition="end" />
<Tab label={t("Pick Execution Detail")} iconPosition="end" />
<Tab label={t("Pick Execution Record")} iconPosition="end" />
</Tabs>
</Box>
@@ -379,6 +407,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
}}>
{tabIndex === 0 && <PickExecution filterArgs={filterArgs} />}
{tabIndex === 1 && <PickExecutionDetail filterArgs={filterArgs} />}
{tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} />}
</Box>
</Box>
);


+ 416
- 0
src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx 查看文件

@@ -0,0 +1,416 @@
"use client";

import {
Box,
Button,
Stack,
TextField,
Typography,
Alert,
CircularProgress,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
TablePagination,
Modal,
Card,
CardContent,
CardActions,
Chip,
Accordion,
AccordionSummary,
AccordionDetails,
} from "@mui/material";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useCallback, useEffect, useState, useRef, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { useRouter } from "next/navigation";
import {
fetchALLPickOrderLineLotDetails,
updateStockOutLineStatus,
createStockOutLine,
recordPickExecutionIssue,
fetchFGPickOrders,
FGPickOrderResponse,
autoAssignAndReleasePickOrder,
AutoAssignReleaseResponse,
checkPickOrderCompletion,
PickOrderCompletionResponse,
checkAndCompletePickOrderByConsoCode,
fetchCompletedDoPickOrders, // ✅ 新增:使用新的 API
CompletedDoPickOrderResponse,
CompletedDoPickOrderSearchParams // ✅ 修复:导入类型
} from "@/app/api/pickOrder/actions";
import { fetchNameList, NameList } from "@/app/api/user/actions";
import {
FormProvider,
useForm,
} from "react-hook-form";
import SearchBox, { Criterion } from "../SearchBox";
import { CreateStockOutLine } from "@/app/api/pickOrder/actions";
import { updateInventoryLotLineQuantities } from "@/app/api/inventory/actions";
import QrCodeIcon from '@mui/icons-material/QrCode';
import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider';
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import { fetchStockInLineInfo } from "@/app/api/po/actions";
import GoodPickExecutionForm from "./GoodPickExecutionForm";
import FGPickOrderCard from "./FGPickOrderCard";

interface Props {
filterArgs: Record<string, any>;
}

// ✅ 新增:已完成的 DO Pick Order 接口
interface CompletedDoPickOrder {
id: number;
pickOrderId: number;
pickOrderCode: string;
pickOrderConsoCode: string;
pickOrderStatus: string;
deliveryOrderId: number;
deliveryNo: string;
deliveryDate: string;
shopId: number;
shopCode: string;
shopName: string;
shopAddress: string;
ticketNo: string;
shopPoNo: string;
numberOfCartons: number;
truckNo: string;
storeId: string;
completedDate: string;
fgPickOrders: FGPickOrderResponse[];
}

// ✅ 新增:Pick Order 数据接口
interface PickOrderData {
pickOrderId: number;
pickOrderCode: string;
pickOrderConsoCode: string;
pickOrderStatus: string;
completedDate: string;
lots: any[];
}

const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
const { t } = useTranslation("pickOrder");
const router = useRouter();
const { data: session } = useSession() as { data: SessionWithTokens | null };
const currentUserId = session?.id ? parseInt(session.id) : undefined;
// ✅ 新增:已完成 DO Pick Orders 状态
const [completedDoPickOrders, setCompletedDoPickOrders] = useState<CompletedDoPickOrder[]>([]);
const [completedDoPickOrdersLoading, setCompletedDoPickOrdersLoading] = useState(false);
// ✅ 新增:详情视图状态
const [selectedDoPickOrder, setSelectedDoPickOrder] = useState<CompletedDoPickOrder | null>(null);
const [showDetailView, setShowDetailView] = useState(false);
const [detailLotData, setDetailLotData] = useState<any[]>([]);
// ✅ 新增:搜索状态
const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
const [filteredDoPickOrders, setFilteredDoPickOrders] = useState<CompletedDoPickOrder[]>([]);
// ✅ 新增:分页状态
const [paginationController, setPaginationController] = useState({
pageNum: 0,
pageSize: 10,
});

const formProps = useForm();
const errors = formProps.formState.errors;

// ✅ 修改:使用新的 API 获取已完成的 DO Pick Orders
const fetchCompletedDoPickOrdersData = useCallback(async (searchParams?: CompletedDoPickOrderSearchParams) => {
if (!currentUserId) return;
setCompletedDoPickOrdersLoading(true);
try {
console.log("🔍 Fetching completed DO pick orders with params:", searchParams);
const completedDoPickOrders = await fetchCompletedDoPickOrders(currentUserId, searchParams);
setCompletedDoPickOrders(completedDoPickOrders);
setFilteredDoPickOrders(completedDoPickOrders);
console.log("✅ Fetched completed DO pick orders:", completedDoPickOrders);
} catch (error) {
console.error("❌ Error fetching completed DO pick orders:", error);
setCompletedDoPickOrders([]);
setFilteredDoPickOrders([]);
} finally {
setCompletedDoPickOrdersLoading(false);
}
}, [currentUserId]);

// ✅ 初始化时获取数据
useEffect(() => {
if (currentUserId) {
fetchCompletedDoPickOrdersData();
}
}, [currentUserId, fetchCompletedDoPickOrdersData]);

// ✅ 修改:搜索功能使用新的 API
const handleSearch = useCallback((query: Record<string, any>) => {
setSearchQuery({ ...query });
console.log("Search query:", query);

const searchParams: CompletedDoPickOrderSearchParams = {
pickOrderCode: query.pickOrderCode || undefined,
shopName: query.shopName || undefined,
deliveryNo: query.deliveryNo || undefined,
ticketNo: query.ticketNo || undefined,
};

// 使用新的 API 进行搜索
fetchCompletedDoPickOrdersData(searchParams);
}, [fetchCompletedDoPickOrdersData]);

// ✅ 修复:重命名函数避免重复声明
const handleSearchReset = useCallback(() => {
setSearchQuery({});
fetchCompletedDoPickOrdersData(); // 重新获取所有数据
}, [fetchCompletedDoPickOrdersData]);

// ✅ 分页功能
const handlePageChange = useCallback((event: unknown, newPage: number) => {
setPaginationController(prev => ({
...prev,
pageNum: newPage,
}));
}, []);

const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const newPageSize = parseInt(event.target.value, 10);
setPaginationController({
pageNum: 0,
pageSize: newPageSize,
});
}, []);

// ✅ 分页数据
const paginatedData = useMemo(() => {
const startIndex = paginationController.pageNum * paginationController.pageSize;
const endIndex = startIndex + paginationController.pageSize;
return filteredDoPickOrders.slice(startIndex, endIndex);
}, [filteredDoPickOrders, paginationController]);

// ✅ 搜索条件
const searchCriteria: Criterion<any>[] = [
{
label: t("Pick Order Code"),
paramName: "pickOrderCode",
type: "text",
},
{
label: t("Shop Name"),
paramName: "shopName",
type: "text",
},
{
label: t("Delivery No"),
paramName: "deliveryNo",
type: "text",
},
{
label: t("Ticket No"),
paramName: "ticketNo",
type: "text",
},
];

// ✅ 处理详情点击 - 显示类似 GoodPickExecution 的表格
const handleDetailClick = useCallback(async (doPickOrder: CompletedDoPickOrder) => {
setSelectedDoPickOrder(doPickOrder);
setShowDetailView(true);
// 获取该 pick order 的详细 lot 数据
try {
const allLotDetails = await fetchALLPickOrderLineLotDetails(currentUserId!);
const filteredLots = allLotDetails.filter(lot =>
lot.pickOrderId === doPickOrder.pickOrderId
);
setDetailLotData(filteredLots);
console.log("✅ Loaded detail lot data for pick order:", doPickOrder.pickOrderCode, filteredLots);
} catch (error) {
console.error("❌ Error loading detail lot data:", error);
setDetailLotData([]);
}
}, [currentUserId]);

// ✅ 返回列表视图
const handleBackToList = useCallback(() => {
setShowDetailView(false);
setSelectedDoPickOrder(null);
setDetailLotData([]);
}, []);

// ✅ 如果显示详情视图,渲染类似 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>

{/* FG Pick Orders 信息 */}
<Box sx={{ mb: 2 }}>
{selectedDoPickOrder.fgPickOrders.map((fgOrder, index) => (
<FGPickOrderCard
key={index}
fgOrder={fgOrder}
onQrCodeClick={() => {}} // 只读模式
/>
))}
</Box>

{/* 类似 GoodPickExecution 的表格 */}
<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}</TableCell>
<TableCell>{lot.itemCode}</TableCell>
<TableCell>{lot.itemName}</TableCell>
<TableCell>{lot.lotNo}</TableCell>
<TableCell>{lot.location}</TableCell>
<TableCell>{lot.requiredQty}</TableCell>
<TableCell>{lot.actualPickQty}</TableCell>
<TableCell>
<Chip
label={lot.processingStatus}
color={lot.processingStatus === 'completed' ? 'success' : 'default'}
size="small"
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Box>
</FormProvider>
);
}

// ✅ 默认列表视图
return (
<FormProvider {...formProps}>
<Box>
{/* 搜索框 */}
<Box sx={{ mb: 2 }}>
<SearchBox
criteria={searchCriteria}
onSearch={handleSearch}
onReset={handleSearchReset}
/>
</Box>

{/* 加载状态 */}
{completedDoPickOrdersLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : (
<Box>
{/* 结果统计 */}
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{t("Total")}: {filteredDoPickOrders.length} {t("completed DO pick orders")}
</Typography>

{/* 列表 */}
{filteredDoPickOrders.length === 0 ? (
<Box sx={{ p: 3, textAlign: 'center' }}>
<Typography variant="body2" color="text.secondary">
{t("No completed DO pick orders found")}
</Typography>
</Box>
) : (
<Stack spacing={2}>
{paginatedData.map((doPickOrder) => (
<Card key={doPickOrder.id}>
<CardContent>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Box>
<Typography variant="h6">
{doPickOrder.pickOrderCode}
</Typography>
<Typography variant="body2" color="text.secondary">
{doPickOrder.shopName} - {doPickOrder.deliveryNo}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Completed")}: {new Date(doPickOrder.completedDate).toLocaleString()}
</Typography>
</Box>
<Box>
<Chip
label={doPickOrder.pickOrderStatus}
color={doPickOrder.pickOrderStatus === 'completed' ? 'success' : 'default'}
size="small"
sx={{ mb: 1 }}
/>
<Typography variant="body2" color="text.secondary">
{doPickOrder.fgPickOrders.length} {t("FG orders")}
</Typography>
</Box>
</Stack>
</CardContent>
<CardActions>
<Button
variant="outlined"
onClick={() => handleDetailClick(doPickOrder)}
>
{t("View Details")}
</Button>
</CardActions>
</Card>
))}
</Stack>
)}

{/* 分页 */}
{filteredDoPickOrders.length > 0 && (
<TablePagination
component="div"
count={filteredDoPickOrders.length}
page={paginationController.pageNum}
rowsPerPage={paginationController.pageSize}
onPageChange={handlePageChange}
onRowsPerPageChange={handlePageSizeChange}
rowsPerPageOptions={[5, 10, 25, 50]}
/>
)}
</Box>
)}
</Box>
</FormProvider>
);
};

export default GoodPickExecutionRecord;

+ 1
- 0
src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx 查看文件

@@ -367,6 +367,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
const [lastProcessedQr, setLastProcessedQr] = useState<string>('');
const [isRefreshingData, setIsRefreshingData] = useState<boolean>(false);
const fetchFgPickOrdersData = useCallback(async () => {
if (!currentUserId) return;


+ 9
- 1
src/i18n/zh/pickOrder.json 查看文件

@@ -274,7 +274,15 @@
"Print Pick Order and DN Label":"列印提料單和送貨單標貼",
"Print Pick Order":"列印提料單",
"Print DN Label":"列印送貨單標貼",
"If you confirm, the system will:":"如果您確認,系統將:"
"If you confirm, the system will:":"如果您確認,系統將:",
"QR code verified.":"QR 碼驗證成功。",
"Order Finished":"訂單完成",
"Submitted Status":"提交狀態",
"Pick Execution Record":"提料執行記錄",
"Delivery No.":"送貨單編號",
"Total":"總數",
"completed DO pick orders":"已完成送貨單提料單",
"No completed DO pick orders found":"沒有已完成送貨單提料單"




正在加载...
取消
保存