|
- "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,
- Checkbox,
- Autocomplete,
- } 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 {
- fetchCompletedJobOrderPickOrdersrecords,
- fetchCompletedJobOrderPickOrderLotDetailsForCompletedPick,
- PrintPickRecord
- } from "@/app/api/jo/actions";
- import { fetchNameList, NameList } from "@/app/api/user/actions";
- import {
- FormProvider,
- useForm,
- } from "react-hook-form";
- import SearchBox, { Criterion } from "../SearchBox";
- import { useSession } from "next-auth/react";
- import { SessionWithTokens } from "@/config/authConfig";
- import Swal from "sweetalert2";
- import { PrinterCombo } from "@/app/api/settings/printer";
- interface Props {
- filterArgs: Record<string, any>;
- printerCombo: PrinterCombo[];
- }
-
- // 修改:已完成的 Job Order Pick Order 接口
- interface CompletedJobOrderPickOrder {
- id: number;
- pickOrderId: number;
- pickOrderCode: string;
- pickOrderConsoCode: string;
- pickOrderTargetDate: string;
- pickOrderStatus: string;
- completedDate: string;
- jobOrderId: number;
- jobOrderCode: string;
- jobOrderName: string;
- reqQty: number;
- uom: string;
- planStart: string;
- planEnd: string;
- secondScanCompleted: boolean;
- totalItems: number;
- completedItems: number;
- }
-
- // 新增:Lot 详情接口
- interface LotDetail {
- lotId: number;
- lotNo: string;
- expiryDate: string;
- location: string;
- availableQty: number;
- requiredQty: number;
- actualPickQty: number;
- processingStatus: string;
- lotAvailability: string;
- pickOrderId: number;
- pickOrderCode: string;
- pickOrderConsoCode: string;
- pickOrderLineId: number;
- stockOutLineId: number;
- stockOutLineStatus: string;
- routerIndex: number;
- routerArea: string;
- routerRoute: string;
- uomShortDesc: string;
- secondQrScanStatus: string;
- itemId: number;
- itemCode: string;
- itemName: string;
- uomCode: string;
- uomDesc: string;
- }
-
- const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs ,printerCombo}) => {
- const { t } = useTranslation("jo");
- const router = useRouter();
- const { data: session } = useSession() as { data: SessionWithTokens | null };
-
- const currentUserId = session?.id ? parseInt(session.id) : undefined;
-
- // 修改:已完成 Job Order Pick Orders 状态
- const [completedJobOrderPickOrders, setCompletedJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]);
- const [completedJobOrderPickOrdersLoading, setCompletedJobOrderPickOrdersLoading] = useState(false);
-
- // 修改:详情视图状态
- const [selectedJobOrderPickOrder, setSelectedJobOrderPickOrder] = useState<CompletedJobOrderPickOrder | null>(null);
- const [showDetailView, setShowDetailView] = useState(false);
- const [detailLotData, setDetailLotData] = useState<LotDetail[]>([]);
- const [detailLotDataLoading, setDetailLotDataLoading] = useState(false);
-
- // 修改:搜索状态
- const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
- const [filteredJobOrderPickOrders, setFilteredJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]);
- //const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0]);
- const defaultDemoPrinter: PrinterCombo = {
- id: 2,
- value: 2,
- name: "2fi",
- label: "2fi",
- code: "2fi"
- };
- const availablePrinters = useMemo(() => {
- if (printerCombo.length === 0) {
- console.log("No printers available, using default demo printer");
- return [defaultDemoPrinter];
- }
- return printerCombo;
- }, [printerCombo]);
- const [selectedPrinter, setSelectedPrinter] = useState<PrinterCombo | null>(
- printerCombo && printerCombo.length > 0 ? printerCombo[0] : null
- );
- const [printQty, setPrintQty] = useState<number>(1);
- // 修改:分页状态
- const [paginationController, setPaginationController] = useState({
- pageNum: 0,
- pageSize: 10,
- });
-
- const formProps = useForm();
- const errors = formProps.formState.errors;
-
- // 修改:使用新的 Job Order API 获取已完成的 Job Order Pick Orders(仅完成pick的)
- const fetchCompletedJobOrderPickOrdersData = useCallback(async () => {
- if (!currentUserId) return;
-
- setCompletedJobOrderPickOrdersLoading(true);
- try {
- console.log("🔍 Fetching completed Job Order pick orders (pick completed only)...");
-
- const completedJobOrderPickOrders = await fetchCompletedJobOrderPickOrdersrecords(currentUserId);
-
- // Fix: Ensure the data is always an array
- const safeData = Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : [];
-
- setCompletedJobOrderPickOrders(safeData);
- setFilteredJobOrderPickOrders(safeData);
- console.log(" Fetched completed Job Order pick orders:", safeData);
- } catch (error) {
- console.error("❌ Error fetching completed Job Order pick orders:", error);
- setCompletedJobOrderPickOrders([]);
- setFilteredJobOrderPickOrders([]);
- } finally {
- setCompletedJobOrderPickOrdersLoading(false);
- }
- }, [currentUserId]);
-
- // 新增:获取 lot 详情数据(使用新的API)
- const fetchLotDetailsData = useCallback(async (pickOrderId: number) => {
- setDetailLotDataLoading(true);
- try {
- console.log("🔍 Fetching lot details for completed pick order:", pickOrderId);
-
- const lotDetails = await fetchCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId);
-
- setDetailLotData(lotDetails);
- console.log(" Fetched lot details:", lotDetails);
- } catch (error) {
- console.error("❌ Error fetching lot details:", error);
- setDetailLotData([]);
- } finally {
- setDetailLotDataLoading(false);
- }
- }, []);
-
- // 修改:初始化时获取数据
- useEffect(() => {
- if (currentUserId) {
- fetchCompletedJobOrderPickOrdersData();
- }
- }, [currentUserId, fetchCompletedJobOrderPickOrdersData]);
-
- // 修改:搜索功能
- const handleSearch = useCallback((query: Record<string, any>) => {
- setSearchQuery({ ...query });
- console.log("Search query:", query);
-
- // Fix: Ensure completedJobOrderPickOrders is an array before filtering
- if (!Array.isArray(completedJobOrderPickOrders)) {
- setFilteredJobOrderPickOrders([]);
- return;
- }
-
- const filtered = completedJobOrderPickOrders.filter((pickOrder) => {
- const pickOrderCodeMatch = !query.pickOrderCode ||
- pickOrder.pickOrderCode?.toLowerCase().includes((query.pickOrderCode || "").toLowerCase());
-
- const jobOrderCodeMatch = !query.jobOrderCode ||
- pickOrder.jobOrderCode?.toLowerCase().includes((query.jobOrderCode || "").toLowerCase());
-
- const jobOrderNameMatch = !query.jobOrderName ||
- pickOrder.jobOrderName?.toLowerCase().includes((query.jobOrderName || "").toLowerCase());
-
- return pickOrderCodeMatch && jobOrderCodeMatch && jobOrderNameMatch;
- });
-
- setFilteredJobOrderPickOrders(filtered);
- console.log("Filtered Job Order pick orders count:", filtered.length);
- }, [completedJobOrderPickOrders]);
-
- // 修改:重置搜索
- const handleSearchReset = useCallback(() => {
- setSearchQuery({});
- // Fix: Ensure completedJobOrderPickOrders is an array before setting
- setFilteredJobOrderPickOrders(Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : []);
- }, [completedJobOrderPickOrders]);
-
- // 修改:分页功能
- 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(() => {
- // Fix: Ensure filteredJobOrderPickOrders is an array before calling slice
- if (!Array.isArray(filteredJobOrderPickOrders)) {
- return [];
- }
-
- const startIndex = paginationController.pageNum * paginationController.pageSize;
- const endIndex = startIndex + paginationController.pageSize;
- return filteredJobOrderPickOrders.slice(startIndex, endIndex);
- }, [filteredJobOrderPickOrders, paginationController]);
-
- // 修改:搜索条件
- const searchCriteria: Criterion<any>[] = [
- {
- label: t("Pick Order Code"),
- paramName: "pickOrderCode",
- type: "text",
- },
- {
- label: t("Job Order Code"),
- paramName: "jobOrderCode",
- type: "text",
- },
- {
- label: t("Job Order Item Name"),
- paramName: "jobOrderName",
- type: "text",
- }
- ];
-
- // 修改:详情点击处理
- const handleDetailClick = useCallback(async (jobOrderPickOrder: CompletedJobOrderPickOrder) => {
- setSelectedJobOrderPickOrder(jobOrderPickOrder);
- setShowDetailView(true);
-
- // 获取 lot 详情数据(使用新的API)
- await fetchLotDetailsData(jobOrderPickOrder.pickOrderId);
-
- // 触发打印按钮状态更新 - 基于详情数据
- const allCompleted = jobOrderPickOrder.secondScanCompleted;
-
- // 发送事件,包含标签页信息
- window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
- detail: {
- allLotsCompleted: allCompleted,
- tabIndex: 3 // 明确指定这是来自标签页 3 的事件
- }
- }));
-
- }, [fetchLotDetailsData]);
-
- // 修改:返回列表视图
- const handleBackToList = useCallback(() => {
- setShowDetailView(false);
- setSelectedJobOrderPickOrder(null);
- setDetailLotData([]);
-
- // 返回列表时禁用打印按钮
- window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
- detail: {
- allLotsCompleted: false,
- tabIndex: 3
- }
- }));
- }, []);
-
- const handlePickRecord = useCallback(async (jobOrderPickOrder: CompletedJobOrderPickOrder) => {
- try {
- if (!jobOrderPickOrder) {
- console.error("No selected job order pick order available");
- return;
- }
-
- // 检查是否已选择打印机
- if (!selectedPrinter) {
- Swal.fire({
- position: "bottom-end",
- icon: "warning",
- text: t("Please select a printer first"),
- showConfirmButton: false,
- timer: 1500
- });
- return;
- }
-
- // 检查打印数量是否有效
- if (!printQty || printQty < 1) {
- Swal.fire({
- position: "bottom-end",
- icon: "warning",
- text: t("Please enter a valid print quantity (at least 1)"),
- showConfirmButton: false,
- timer: 1500
- });
- return;
- }
-
- const pickOrderId = jobOrderPickOrder.pickOrderId;
- console.log("Pick Order ID:", pickOrderId);
-
- // 使用已选择的打印机和数量
- const printerId = selectedPrinter.id;
-
- const printRequest = {
- pickOrderId: pickOrderId,
- printerId: printerId,
- printQty: printQty
- };
-
- console.log("Printing Pick Record with request: ", printRequest);
-
- const response = await PrintPickRecord(printRequest);
-
- console.log("Print Pick Record response: ", response);
-
- if (response.success) {
- Swal.fire({
- position: "bottom-end",
- icon: "success",
- text: t("Printed Successfully."),
- showConfirmButton: false,
- timer: 1500
- });
- } else {
- console.error("Print failed: ", response.message);
- Swal.fire({
- position: "bottom-end",
- icon: "error",
- text: response.message || t("Print failed"),
- showConfirmButton: false,
- timer: 1500
- });
- }
- } catch (error) {
- console.error("error: ", error);
- Swal.fire({
- position: "bottom-end",
- icon: "error",
- text: t("An error occurred while printing"),
- showConfirmButton: false,
- timer: 1500
- });
- }
- }, [t, selectedPrinter, printQty]);
- // 修改:如果显示详情视图,渲染 Job Order 详情和 Lot 信息
- if (showDetailView && selectedJobOrderPickOrder) {
- 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("Job Order Pick Order Details")}: {selectedJobOrderPickOrder.pickOrderCode}
- </Typography>
- </Box>
-
- {/* Job Order 信息卡片 */}
- <Card sx={{ mb: 2 }}>
- <CardContent>
- <Stack direction="row" spacing={4} useFlexGap flexWrap="wrap">
- <Typography variant="subtitle1">
- <strong>{t("Pick Order Code")}:</strong> {selectedJobOrderPickOrder.pickOrderCode}
- </Typography>
- <Typography variant="subtitle1">
- <strong>{t("Job Order Code")}:</strong> {selectedJobOrderPickOrder.jobOrderCode}
- </Typography>
- <Typography variant="subtitle1">
- <strong>{t("Job Order Item Name")}:</strong> {selectedJobOrderPickOrder.jobOrderName}
- </Typography>
- <Typography variant="subtitle1">
- <strong>{t("Target Date")}:</strong> {selectedJobOrderPickOrder.pickOrderTargetDate}
- </Typography>
- </Stack>
-
- <Stack direction="row" spacing={4} useFlexGap flexWrap="wrap" sx={{ mt: 2 }}>
- <Typography variant="subtitle1">
- <strong>{t("Required Qty")}:</strong> {selectedJobOrderPickOrder.reqQty} {selectedJobOrderPickOrder.uom}
- </Typography>
- </Stack>
- {/*
- <Stack direction="row" spacing={4} useFlexGap flexWrap="wrap" sx={{ mt: 2 }}>
- <Button
- variant="contained"
- color="primary"
- onClick={() => handlePickRecord(selectedJobOrderPickOrder)}
- sx={{ mt: 1 }}
- >
- {t("Print Pick Record")}
- </Button>
- </Stack>
- */}
- </CardContent>
- </Card>
-
- {/* 修改:Lot 详情表格 - 添加复选框列 */}
- <Card>
- <CardContent>
- <Typography variant="h6" gutterBottom>
- {t("Lot Details")}
- </Typography>
-
- {detailLotDataLoading ? (
- <Box sx={{ display: 'flex', justifyContent: 'center', p: 2 }}>
- <CircularProgress />
- </Box>
- ) : (
- <TableContainer component={Paper}>
- <Table>
- <TableHead>
- <TableRow>
- <TableCell>{t("Index")}</TableCell>
- <TableCell>{t("Route")}</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("Processing Status")}</TableCell>
- <TableCell align="center">{t("Second Scan Status")}</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {detailLotData.length === 0 ? (
- <TableRow>
- <TableCell colSpan={10} align="center">
- <Typography variant="body2" color="text.secondary">
- {t("No lot details available")}
- </Typography>
- </TableCell>
- </TableRow>
- ) : (
- detailLotData.map((lot, index) => (
- <TableRow key={lot.lotId}>
- <TableCell>
- <Typography variant="body2" fontWeight="bold">
- {index + 1}
- </Typography>
- </TableCell>
- <TableCell>
- <Typography variant="body2">
- {lot.routerRoute || '-'}
- </Typography>
- </TableCell>
- <TableCell>{lot.itemCode}</TableCell>
- <TableCell>{lot.itemName}</TableCell>
- <TableCell>{lot.lotNo}</TableCell>
- <TableCell>{lot.location}</TableCell>
- <TableCell align="right">
- {lot.requiredQty?.toLocaleString() || 0} ({lot.uomShortDesc})
- </TableCell>
- <TableCell align="right">
- {lot.actualPickQty?.toLocaleString() || 0} ({lot.uomShortDesc})
- </TableCell>
- {/* 修改:Processing Status 使用复选框 */}
- <TableCell align="center">
- <Box sx={{
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- width: '100%',
- height: '100%'
- }}>
- <Checkbox
- checked={lot.processingStatus === 'completed'}
- disabled={true}
- readOnly={true}
- size="large"
- sx={{
- color: lot.processingStatus === 'completed' ? 'success.main' : 'grey.400',
- '&.Mui-checked': {
- color: 'success.main',
- },
- transform: 'scale(1.3)',
- '& .MuiSvgIcon-root': {
- fontSize: '1.5rem',
- }
- }}
- />
- </Box>
- </TableCell>
- {/* 修改:Second Scan Status 使用复选框 */}
- <TableCell align="center">
- <Box sx={{
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- width: '100%',
- height: '100%'
- }}>
- <Checkbox
- checked={lot.secondQrScanStatus === 'completed'}
- disabled={true}
- readOnly={true}
- size="large"
- sx={{
- color: lot.secondQrScanStatus === 'completed' ? 'success.main' : 'grey.400',
- '&.Mui-checked': {
- color: 'success.main',
- },
- transform: 'scale(1.3)',
- '& .MuiSvgIcon-root': {
- fontSize: '1.5rem',
- }
- }}
- />
- </Box>
- </TableCell>
- </TableRow>
- ))
- )}
- </TableBody>
- </Table>
- </TableContainer>
- )}
- </CardContent>
- </Card>
- </Box>
- </FormProvider>
- );
- }
-
- // 修改:默认列表视图
- return (
- <FormProvider {...formProps}>
- <Box>
- {/* 搜索框 */}
- <Box sx={{ mb: 2 }}>
- <SearchBox
- criteria={searchCriteria}
- onSearch={handleSearch}
- onReset={handleSearchReset}
- />
- </Box>
-
- {/* 加载状态 */}
- {completedJobOrderPickOrdersLoading ? (
- <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
- <CircularProgress />
- </Box>
- ) : (
- <Box>
- {/* 结果统计 */}
- <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
- {t("Total")}: {filteredJobOrderPickOrders.length} {t("completed Job Order pick orders with matching")}
- </Typography>
- <Box sx={{ mb: 2, p: 2, border: '1px solid #e0e0e0', borderRadius: 1, bgcolor: 'background.paper' }}>
- <Stack direction="row" spacing={2} alignItems="center" flexWrap="wrap">
- <Typography variant="subtitle1" sx={{ minWidth: 'fit-content' }}>
- {t("Select Printer")}:
- </Typography>
- <Autocomplete
- options={availablePrinters}
- getOptionLabel={(option) => option.name || option.label || option.code || `Printer ${option.id}`}
- value={selectedPrinter}
- onChange={(_, newValue) => setSelectedPrinter(newValue)}
- sx={{ minWidth: 250 }}
- size="small"
- renderInput={(params) => <TextField {...params} label={t("Printer")} />}
- />
- <Typography variant="subtitle1" sx={{ minWidth: 'fit-content' }}>
- {t("Print Quantity")}:
- </Typography>
- <TextField
- type="number"
- label={t("Print Quantity")}
- value={printQty}
- onChange={(e) => {
- const value = parseInt(e.target.value) || 1;
- setPrintQty(Math.max(1, value));
- }}
- inputProps={{ min: 1, step: 1 }}
- sx={{ width: 120 }}
- size="small"
- />
- </Stack>
- </Box>
- {/* 列表 */}
- {filteredJobOrderPickOrders.length === 0 ? (
- <Box sx={{ p: 3, textAlign: 'center' }}>
- <Typography variant="body2" color="text.secondary">
- {t("No completed Job Order pick orders with matching found")}
- </Typography>
- </Box>
- ) : (
- <Stack spacing={2}>
- {paginatedData.map((jobOrderPickOrder) => (
- <Card key={jobOrderPickOrder.id}>
- <CardContent>
- <Stack direction="row" justifyContent="space-between" alignItems="center">
- <Box>
- <Typography variant="h6">
- {jobOrderPickOrder.jobOrderCode}
- </Typography>
- <Typography variant="body2" color="text.secondary">
- {jobOrderPickOrder.jobOrderName} - {jobOrderPickOrder.pickOrderCode}
- </Typography>
- <Typography variant="body2" color="text.secondary">
- {t("Completed")}: {new Date(jobOrderPickOrder.completedDate).toLocaleString()}
- </Typography>
- <Typography variant="body2" color="text.secondary">
- {t("Target Date")}: {jobOrderPickOrder.pickOrderTargetDate}
- </Typography>
- </Box>
-
- <Box>
- <Chip
- label={t(jobOrderPickOrder.pickOrderStatus) }
- color={jobOrderPickOrder.pickOrderStatus === 'completed' ? 'success' : 'default'}
- size="small"
- sx={{ mb: 1 }}
- />
- <Typography variant="body2" color="text.secondary">
- {jobOrderPickOrder.completedItems}/{jobOrderPickOrder.totalItems} {t("items completed")}
- </Typography>
- <Chip
- label={jobOrderPickOrder.secondScanCompleted ? t("Second Scan Completed") : t("Second Scan Pending")}
- color={jobOrderPickOrder.secondScanCompleted ? 'success' : 'warning'}
- size="small"
- sx={{ mt: 1 }}
- />
- </Box>
- </Stack>
- </CardContent>
- <CardActions>
- <Button
- variant="outlined"
- onClick={() => handleDetailClick(jobOrderPickOrder)}
- >
- {t("View Details")}
- </Button>
-
- <Button
- variant="contained"
- color="primary"
- onClick={() => handlePickRecord(jobOrderPickOrder)}
- sx={{ mt: 1 }}
- >
- {t("Print Pick Record")}
- </Button>
-
- </CardActions>
- </Card>
- ))}
- </Stack>
- )}
-
- {/* 分页 */}
- {filteredJobOrderPickOrders.length > 0 && (
- <TablePagination
- component="div"
- count={filteredJobOrderPickOrders.length}
- page={paginationController.pageNum}
- rowsPerPage={paginationController.pageSize}
- onPageChange={handlePageChange}
- onRowsPerPageChange={handlePageSizeChange}
- rowsPerPageOptions={[5, 10, 25, 50]}
- />
- )}
- </Box>
- )}
- </Box>
- </FormProvider>
- );
- };
-
- export default CompleteJobOrderRecord;
|