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