Browse Source

update

master
CANCERYS\kw093 1 day ago
parent
commit
c2326f0314
4 changed files with 876 additions and 70 deletions
  1. +243
    -24
      src/components/PickOrderSearch/LotTable.tsx
  2. +58
    -14
      src/components/PickOrderSearch/PickExecution.tsx
  3. +64
    -32
      src/components/PickOrderSearch/PickQcStockInModalVer3.tsx
  4. +511
    -0
      src/components/PickOrderSearch/assignTo copy.tsx

+ 243
- 24
src/components/PickOrderSearch/LotTable.tsx View File

@@ -15,11 +15,13 @@ import {
TextField,
Typography,
TablePagination,
Modal,
} from "@mui/material";
import { useCallback, useMemo, useState } from "react";
import { useCallback, useMemo, useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import QrCodeIcon from '@mui/icons-material/QrCode';
import { GetPickOrderLineInfo } from "@/app/api/pickOrder/actions";
import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider';

interface LotPickData {
id: number;
@@ -63,6 +65,164 @@ interface LotTableProps {
generateInputBody: () => any;
}

// ✅ QR Code Modal Component
const QrCodeModal: React.FC<{
open: boolean;
onClose: () => void;
lot: LotPickData | null;
onQrCodeSubmit: (lotNo: string) => void;
}> = ({ open, onClose, lot, onQrCodeSubmit }) => {
const { t } = useTranslation("pickOrder");
const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
const [manualInput, setManualInput] = useState<string>('');
// ✅ Add state to track manual input submission
const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false);
const [manualInputError, setManualInputError] = useState<boolean>(false);

// ✅ Process scanned QR codes
useEffect(() => {
if (qrValues.length > 0 && lot) {
const latestQr = qrValues[qrValues.length - 1];
const qrContent = latestQr.replace(/[{}]/g, '');
if (qrContent === lot.lotNo) {
onQrCodeSubmit(lot.lotNo);
onClose();
resetScan();
} else {
// ✅ Set error state for helper text
setManualInputError(true);
setManualInputSubmitted(true);
}
}
}, [qrValues, lot, onQrCodeSubmit, onClose, resetScan]);

// ✅ Clear states when modal opens or lot changes
useEffect(() => {
if (open) {
setManualInput('');
setManualInputSubmitted(false);
setManualInputError(false);
}
}, [open]);

useEffect(() => {
if (lot) {
setManualInput('');
setManualInputSubmitted(false);
setManualInputError(false);
}
}, [lot]);

const handleManualSubmit = () => {
if (manualInput.trim() === lot?.lotNo) {
// ✅ Success - no error helper text needed
onQrCodeSubmit(lot.lotNo);
onClose();
setManualInput('');
} else {
// ✅ Show error helper text after submit
setManualInputError(true);
setManualInputSubmitted(true);
// Don't clear input - let user see what they typed
}
};

return (
<Modal open={open} onClose={onClose}>
<Box sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
bgcolor: 'background.paper',
p: 3,
borderRadius: 2,
minWidth: 400,
}}>
<Typography variant="h6" gutterBottom>
{t("QR Code Scan for Lot")}: {lot?.lotNo}
</Typography>
{/* QR Scanner Status */}
<Box sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}>
<Typography variant="body2" gutterBottom>
<strong>Scanner Status:</strong> {isScanning ? 'Scanning...' : 'Ready'}
</Typography>
<Stack direction="row" spacing={1}>
<Button
variant="contained"
onClick={isScanning ? stopScan : startScan}
size="small"
>
{isScanning ? 'Stop Scan' : 'Start Scan'}
</Button>
<Button
variant="outlined"
onClick={resetScan}
size="small"
>
Reset
</Button>
</Stack>
</Box>

{/* Manual Input with Submit-Triggered Helper Text */}
<Box sx={{ mb: 2 }}>
<Typography variant="body2" gutterBottom>
<strong>Manual Input:</strong>
</Typography>
<TextField
fullWidth
size="small"
value={manualInput}
onChange={(e) => setManualInput(e.target.value)}
sx={{ mb: 1 }}
// ✅ Only show error after submit button is clicked
error={manualInputSubmitted && manualInputError}
helperText={
// ✅ Show helper text only after submit with error
manualInputSubmitted && manualInputError
? `The input is not the same as the expected lot number. Expected: ${lot?.lotNo}`
: ''
}
/>
<Button
variant="contained"
onClick={handleManualSubmit}
disabled={!manualInput.trim()}
size="small"
color="primary"
>
Submit Manual Input
</Button>
</Box>

{/* ✅ Show QR Scan Status */}
{qrValues.length > 0 && (
<Box sx={{ mb: 2, p: 2, backgroundColor: manualInputError ? '#ffebee' : '#e8f5e8', borderRadius: 1 }}>
<Typography variant="body2" color={manualInputError ? 'error' : 'success'}>
<strong>QR Scan Result:</strong> {qrValues[qrValues.length - 1]}
</Typography>
{manualInputError && (
<Typography variant="caption" color="error" display="block">
❌ Mismatch! Expected: {lot?.lotNo}
</Typography>
)}
</Box>
)}

<Box sx={{ mt: 2, textAlign: 'right' }}>
<Button onClick={onClose} variant="outlined">
Cancel
</Button>
</Box>
</Box>
</Modal>
);
};

const LotTable: React.FC<LotTableProps> = ({
lotData,
selectedRowId,
@@ -83,6 +243,14 @@ const LotTable: React.FC<LotTableProps> = ({
}) => {
const { t } = useTranslation("pickOrder");
// ✅ Add QR scanner context
const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
// ✅ Add state for QR input modal
const [qrModalOpen, setQrModalOpen] = useState(false);
const [selectedLotForQr, setSelectedLotForQr] = useState<LotPickData | null>(null);
const [manualQrInput, setManualQrInput] = useState<string>('');
// 分页控制器
const [lotTablePagingController, setLotTablePagingController] = useState({
pageNum: 0,
@@ -95,10 +263,10 @@ const LotTable: React.FC<LotTableProps> = ({
return "Please finish QR code scan, QC check and pick order.";
}
switch (lot.stockOutLineStatus?.toUpperCase()) {
case 'PENDING':
switch (lot.stockOutLineStatus?.toLowerCase()) {
case 'pending':
return "Please finish QC check and pick order.";
case 'COMPLETE':
case 'completed':
return "Please submit the pick order.";
case 'unavailable':
return "This order is insufficient, please pick another lot.";
@@ -137,6 +305,23 @@ const LotTable: React.FC<LotTableProps> = ({
});
}, []);

// ✅ Handle QR code submission
const handleQrCodeSubmit = useCallback((lotNo: string) => {
if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) {
console.log(`✅ QR Code verified for lot: ${lotNo}`);
// ✅ Create stock out line
onCreateStockOutLine(selectedLotForQr.lotId);
// ✅ Show success message
console.log("Stock out line created successfully!");
// ✅ Close modal
setQrModalOpen(false);
setSelectedLotForQr(null);
}
}, [selectedLotForQr, onCreateStockOutLine]);

return (
<>
<TableContainer component={Paper}>
@@ -198,26 +383,47 @@ const LotTable: React.FC<LotTableProps> = ({
{/* QR Code Scan Button */}
<TableCell align="center">
<Button
variant="outlined"
size="small"
onClick={() => {
onCreateStockOutLine(lot.lotId);
onLotSelectForInput(lot); // Show input body when button is clicked
}}
// ✅ Allow creation for available AND insufficient_stock lots
disabled={(lot.lotAvailability === 'expired' || lot.lotAvailability === 'status_unavailable') || Boolean(lot.stockOutLineId)}
sx={{
fontSize: '0.7rem',
py: 0.5,
minHeight: '28px',
whiteSpace: 'nowrap',
minWidth: '40px'
}}
startIcon={<QrCodeIcon />} // ✅ Add QR code icon
>
{lot.stockOutLineId ? t("Scanned") : t("Scan")}
</Button>
<Box sx={{ textAlign: 'center' }}>
<Button
variant="outlined"
size="small"
onClick={() => {
setSelectedLotForQr(lot);
setQrModalOpen(true);
resetScan();
}}
// ✅ Disable when:
// 1. Lot is expired or unavailable
// 2. Already scanned (has stockOutLineId)
// 3. Not selected (selectedLotRowId doesn't match)
disabled={
(lot.lotAvailability === 'expired' || lot.lotAvailability === 'status_unavailable') ||
Boolean(lot.stockOutLineId) ||
selectedLotRowId !== `row_${index}`
}
sx={{
fontSize: '0.7rem',
py: 0.5,
minHeight: '28px',
whiteSpace: 'nowrap',
minWidth: '40px',
// ✅ Visual feedback
opacity: selectedLotRowId === `row_${index}` ? 1 : 0.5
}}
startIcon={<QrCodeIcon />}
title={
selectedLotRowId !== `row_${index}`
? "Please select this lot first to enable QR scanning"
: lot.stockOutLineId
? "Already scanned"
: "Click to scan QR code"
}
>
{lot.stockOutLineId ? t("Scanned") : t("Scan")}
</Button>
</Box>
</TableCell>
{/* QC Check Button */}
@@ -320,6 +526,19 @@ const LotTable: React.FC<LotTableProps> = ({
`${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
}
/>
{/* ✅ QR Code Modal */}
<QrCodeModal
open={qrModalOpen}
onClose={() => {
setQrModalOpen(false);
setSelectedLotForQr(null);
stopScan();
resetScan();
}}
lot={selectedLotForQr}
onQrCodeSubmit={handleQrCodeSubmit}
/>
</>
);
};


+ 58
- 14
src/components/PickOrderSearch/PickExecution.tsx View File

@@ -490,20 +490,22 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
}, [selectedLotRowId]);

// ✅ Add function to handle row selection that resets lot selection
const handleRowSelect = useCallback(async (lineId: number) => {
const handleRowSelect = useCallback(async (lineId: number, preserveLotSelection: boolean = false) => {
setSelectedRowId(lineId);
// ✅ Reset lot selection when changing pick order line
setSelectedLotRowId(null);
setSelectedLotId(null);
// ✅ Only reset lot selection if not preserving
if (!preserveLotSelection) {
setSelectedLotRowId(null);
setSelectedLotId(null);
}
try {
const lotDetails = await fetchPickOrderLineLotDetails(lineId);
console.log("Lot details from API:", lotDetails);
const realLotData: LotPickData[] = lotDetails.map((lot: any) => ({
id: lot.lotId, // This is actually ill.id (inventory lot line ID)
lotId: lot.lotId, // This should be the unique inventory lot line ID
id: lot.id, // This should be the unique row ID for the table
lotId: lot.lotId, // This is the inventory lot line ID
lotNo: lot.lotNo,
expiryDate: lot.expiryDate ? new Date(lot.expiryDate).toLocaleDateString() : 'N/A',
location: lot.location,
@@ -513,7 +515,6 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
actualPickQty: lot.actualPickQty || 0,
lotStatus: lot.lotStatus,
lotAvailability: lot.lotAvailability,
// ✅ Add StockOutLine fields
stockOutLineId: lot.stockOutLineId,
stockOutLineStatus: lot.stockOutLineStatus,
stockOutLineQty: lot.stockOutLineQty
@@ -545,6 +546,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
pickOrderCode: pickOrder.code,
targetDate: formattedTargetDate, // ✅ 使用 dayjs 格式化的日期
balanceToPick: balanceToPick,
pickedQty: line.pickedQty,
// 确保 availableQty 不为 null
availableQty: availableQty,
};
@@ -675,6 +677,10 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
}
try {
// ✅ Store current lot selection before refresh
const currentSelectedLotRowId = selectedLotRowId;
const currentSelectedLotId = selectedLotId;
const stockOutLineData: CreateStockOutLine = {
consoCode: pickOrderDetails.consoCode,
pickOrderLineId: selectedRowId,
@@ -692,19 +698,57 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
if (result) {
console.log("Stock out line created successfully:", result);
//alert(`Stock out line created successfully! ID: ${result.id}`);
// ✅ Don't refresh immediately - let user see the result first
// ✅ Auto-refresh data after successful creation
console.log("🔄 Refreshing data after stock out line creation...");
try {
// ✅ Refresh lot data for the selected row (maintains selection)
if (selectedRowId) {
await handleRowSelect(selectedRowId, true); // ✅ Preserve lot selection
}
// ✅ Refresh main pick order details
await handleFetchAllPickOrderDetails();
console.log("✅ Data refresh completed - lot selection maintained!");
} catch (refreshError) {
console.error("❌ Error refreshing data:", refreshError);
}
setShowInputBody(false); // Hide preview after successful creation
} else {
console.error("Failed to create stock out line: No response");
//alert("Failed to create stock out line: No response");
}
} catch (error) {
console.error("Error creating stock out line:", error);
//alert("Error creating stock out line. Please try again.");
}
}, [selectedRowId, pickOrderDetails?.consoCode]);
}, [selectedRowId, pickOrderDetails?.consoCode, handleRowSelect, handleFetchAllPickOrderDetails, selectedLotRowId, selectedLotId]);

// ✅ New function to refresh data while preserving lot selection
const handleRefreshDataPreserveSelection = useCallback(async () => {
if (!selectedRowId) return;
// ✅ Store current lot selection
const currentSelectedLotRowId = selectedLotRowId;
const currentSelectedLotId = selectedLotId;
try {
// ✅ Refresh lot data
await handleRowSelect(selectedRowId, true); // ✅ Preserve selection
// ✅ Refresh main pick order details
await handleFetchAllPickOrderDetails();
// ✅ Restore lot selection
setSelectedLotRowId(currentSelectedLotRowId);
setSelectedLotId(currentSelectedLotId);
console.log("✅ Data refreshed with selection preserved");
} catch (error) {
console.error("❌ Error refreshing data:", error);
}
}, [selectedRowId, selectedLotRowId, selectedLotId, handleRowSelect, handleFetchAllPickOrderDetails]);

// 自定义主表格组件
const CustomMainTable = () => {
@@ -740,7 +784,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
const availableQty = line.availableQty ?? 0;
const balanceToPick = Math.max(0, availableQty - line.requiredQty); // 确保不为负数
const totalPickedQty = getTotalPickedQty(line.id);
const actualPickedQty = line.pickedQty ?? 0;
return (
<TableRow
key={line.id}
@@ -777,7 +821,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
}}>
{availableQty.toLocaleString()} {/* 添加千位分隔符 */}
</TableCell>
<TableCell align="right">{totalPickedQty}</TableCell>
<TableCell align="right">{actualPickedQty}</TableCell>
<TableCell align="right">{line.uomDesc}</TableCell>
<TableCell align="right">{line.targetDate}</TableCell>
</TableRow>


+ 64
- 32
src/components/PickOrderSearch/PickQcStockInModalVer3.tsx View File

@@ -40,6 +40,8 @@ interface ExtendedQcItem extends QcItemWithChecks {
qcPassed?: boolean;
failQty?: number;
remarks?: string;
order?: number; // ✅ Add order property
stableId?: string; // ✅ Also add stableId for better row identification
}

const style = {
@@ -195,15 +197,22 @@ const PickQcStockInModalVer3: React.FC<Props> = ({

// ✅ 修改:在组件开始时自动设置失败数量
useEffect(() => {
if (itemDetail && qcItems.length > 0) {
// ✅ 自动将 Lot Required Pick Qty 设置为所有失败项目的 failQty
const updatedQcItems = qcItems.map(item => ({
...item,
failQty: itemDetail.requiredQty || 0 // 使用 Lot Required Pick Qty
}));
setQcItems(updatedQcItems);
if (itemDetail && qcItems.length > 0 && selectedLotId) {
// ✅ 获取选中的批次数据
const selectedLot = lotData.find(lot => lot.stockOutLineId === selectedLotId);
if (selectedLot) {
// ✅ 自动将 Lot Required Pick Qty 设置为所有失败项目的 failQty
const updatedQcItems = qcItems.map((item, index) => ({
...item,
failQty: selectedLot.requiredQty || 0, // 使用 Lot Required Pick Qty
// ✅ Add stable order and ID fields
order: index,
stableId: `qc-${item.id}-${index}`
}));
setQcItems(updatedQcItems);
}
}
}, [itemDetail, qcItems.length]);
}, [itemDetail, qcItems.length, selectedLotId, lotData]);

// ✅ 修改:移除 alert 弹窗,改为控制台日志
const onSubmitQc = useCallback<SubmitHandler<any>>(
@@ -215,7 +224,8 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
const acceptQty = Number(accQty) || null;
const validationErrors : string[] = [];
const selectedLot = lotData.find(lot => lot.stockOutLineId === selectedLotId);
const itemsWithoutResult = qcItems.filter(item => item.qcPassed === undefined);
if (itemsWithoutResult.length > 0) {
validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(", ")}`);
@@ -234,7 +244,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
qcItem: item.code,
qcDescription: item.description || "",
isPassed: item.qcPassed,
failQty: item.qcPassed ? 0 : (itemDetail?.requiredQty || 0),
failQty: item.qcPassed ? 0 : (selectedLot?.requiredQty || 0),
remarks: item.remarks || "",
})),
};
@@ -248,7 +258,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
}
// ✅ Fix: Update stock out line status based on QC decision
if (selectedLotId && qcData.qcAccept) {
if (selectedLotId) { // ✅ Remove qcData.qcAccept condition
try {
const allPassed = qcData.qcItems.every(item => item.isPassed);
@@ -261,17 +271,19 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
});
// ✅ Fix: 1. Update stock out line status with required qty field
await updateStockOutLineStatus({
id: selectedLotId,
status: newStockOutLineStatus,
qty: itemDetail?.requiredQty || 0 // ✅ Add required qty field
});
if (selectedLot) {
await updateStockOutLineStatus({
id: selectedLotId,
status: newStockOutLineStatus,
qty: selectedLot.requiredQty || 0
});
} else {
console.warn("Selected lot not found for stock out line status update");
}
// ✅ Fix: 2. If QC failed, also update inventory lot line status
if (!allPassed) {
try {
// ✅ Fix: Get the correct lot data
const selectedLot = lotData.find(lot => lot.stockOutLineId === selectedLotId);
if (selectedLot) {
console.log("Updating inventory lot line status for failed QC:", {
inventoryLotLineId: selectedLot.lotId,
@@ -280,7 +292,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
await updateInventoryLotLineStatus({
inventoryLotLineId: selectedLot.lotId,
status: 'unavailable' // ✅ Use correct backend enum value
status: 'unavailable'
});
console.log("Inventory lot line status updated to unavailable");
} else {
@@ -288,7 +300,6 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
}
} catch (error) {
console.error("Failed to update inventory lot line status:", error);
// ✅ Don't fail the entire operation, just log the error
}
}
@@ -300,12 +311,6 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
}
} catch (error) {
console.error("Error updating stock out line status after QC:", error);
// ✅ Log detailed error information
if (error instanceof Error) {
console.error("Error details:", error.message);
console.error("Error stack:", error.stack);
}
// ✅ Don't fail the entire QC submission, just log the error
}
}
@@ -362,15 +367,20 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
value={current.qcPassed === undefined ? "" : (current.qcPassed ? "true" : "false")}
onChange={(e) => {
const value = e.target.value === "true";
setQcItems((prev) =>
prev.map((r): ExtendedQcItem => (r.id === params.id ? { ...r, qcPassed: value } : r))
// ✅ Simple state update
setQcItems(prev =>
prev.map(item =>
item.id === params.id
? { ...item, qcPassed: value }
: item
)
);
}}
name={`qcPassed-${params.id}`}
>
<FormControlLabel
value="true"
control={<Radio size="small" />}
control={<Radio />}
label="合格"
sx={{
color: current.qcPassed === true ? "green" : "inherit",
@@ -379,7 +389,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
/>
<FormControlLabel
value="false"
control={<Radio size="small" />}
control={<Radio />}
label="不合格"
sx={{
color: current.qcPassed === false ? "red" : "inherit",
@@ -400,7 +410,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
type="number"
size="small"
// ✅ 修改:失败项目自动显示 Lot Required Pick Qty
value={!params.row.qcPassed ? (itemDetail?.requiredQty || 0) : 0}
value={!params.row.qcPassed ? (0) : 0}
disabled={params.row.qcPassed}
// ✅ 移除 onChange,因为数量是固定的
// onChange={(e) => {
@@ -444,6 +454,27 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
[t],
);

// ✅ Add stable update function
const handleQcResultChange = useCallback((itemId: number, qcPassed: boolean) => {
setQcItems(prevItems =>
prevItems.map(item =>
item.id === itemId
? { ...item, qcPassed }
: item
)
);
}, []);

// ✅ Remove duplicate functions
const getRowId = useCallback((row: any) => {
return row.id; // Just use the original ID
}, []);

// ✅ Remove complex sorting logic
// const stableQcItems = useMemo(() => { ... }); // Remove
// const sortedQcItems = useMemo(() => { ... }); // Remove

// ✅ Use qcItems directly in DataGrid
return (
<>
<FormProvider {...formProps}>
@@ -481,8 +512,9 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
<StyledDataGrid
columns={qcColumns}
rows={qcItems}
rows={qcItems} // ✅ Use qcItems directly
autoHeight
getRowId={getRowId} // ✅ Simple row ID function
/>
</Grid>
</>


+ 511
- 0
src/components/PickOrderSearch/assignTo copy.tsx View File

@@ -0,0 +1,511 @@
"use client";
import {
Autocomplete,
Box,
Button,
CircularProgress,
FormControl,
Grid,
Modal,
TextField,
Typography,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Checkbox,
TablePagination,
} from "@mui/material";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import {
newassignPickOrder,
AssignPickOrderInputs,
releaseAssignedPickOrders,
fetchPickOrderWithStockClient, // Add this import
} from "@/app/api/pickOrder/actions";
import { fetchNameList, NameList } from "@/app/api/user/actions";
import {
FormProvider,
useForm,
} from "react-hook-form";
import { isEmpty, upperFirst, groupBy } from "lodash";
import { OUTPUT_DATE_FORMAT, arrayToDayjs } from "@/app/utils/formatUtil";
import useUploadContext from "../UploadProvider/useUploadContext";
import dayjs from "dayjs";
import arraySupport from "dayjs/plugin/arraySupport";
import SearchBox, { Criterion } from "../SearchBox";
import { sortBy, uniqBy } from "lodash";
import { createStockOutLine, CreateStockOutLine, fetchPickOrderDetails } from "@/app/api/pickOrder/actions";
dayjs.extend(arraySupport);

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

// Update the interface to match the new API response structure
interface PickOrderRow {
id: string;
code: string;
targetDate: string;
type: string;
status: string;
assignTo: number;
groupName: string;
consoCode?: string;
pickOrderLines: PickOrderLineRow[];
}

interface PickOrderLineRow {
id: number;
itemId: number;
itemCode: string;
itemName: string;
availableQty: number | null;
requiredQty: number;
uomCode: string;
uomDesc: string;
suggestedList: any[];
}

const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
bgcolor: "background.paper",
pt: 5,
px: 5,
pb: 10,
width: { xs: "100%", sm: "100%", md: "100%" },
};

const AssignTo: React.FC<Props> = ({ filterArgs }) => {
const { t } = useTranslation("pickOrder");
const { setIsUploading } = useUploadContext();
const [isUploading, setIsUploadingLocal] = useState(false);
// Update state to use pick order data directly
const [selectedPickOrderIds, setSelectedPickOrderIds] = useState<string[]>([]);
const [filteredPickOrders, setFilteredPickOrders] = useState<PickOrderRow[]>([]);
const [isLoadingItems, setIsLoadingItems] = useState(false);
const [pagingController, setPagingController] = useState({
pageNum: 1,
pageSize: 10,
});
const [totalCountItems, setTotalCountItems] = useState<number>();
const [modalOpen, setModalOpen] = useState(false);
const [usernameList, setUsernameList] = useState<NameList[]>([]);
const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
const [originalPickOrderData, setOriginalPickOrderData] = useState<PickOrderRow[]>([]);

const formProps = useForm<AssignPickOrderInputs>();
const errors = formProps.formState.errors;

// Update the handler functions to work with string IDs
const handlePickOrderSelect = useCallback((pickOrderId: string, checked: boolean) => {
if (checked) {
setSelectedPickOrderIds(prev => [...prev, pickOrderId]);
} else {
setSelectedPickOrderIds(prev => prev.filter(id => id !== pickOrderId));
}
}, []);

const isPickOrderSelected = useCallback((pickOrderId: string) => {
return selectedPickOrderIds.includes(pickOrderId);
}, [selectedPickOrderIds]);

// Update the fetch function to use the correct endpoint
const fetchNewPageItems = useCallback(
async (pagingController: Record<string, number>, filterArgs: Record<string, any>) => {
setIsLoadingItems(true);
try {
const params = {
...pagingController,
...filterArgs,
pageNum: (pagingController.pageNum || 1) - 1,
pageSize: pagingController.pageSize || 10,
// Filter for assigned status only
status: "assigned"
};

const res = await fetchPickOrderWithStockClient(params);

if (res && res.records) {
// Convert pick order data to the expected format
const pickOrderRows: PickOrderRow[] = res.records.map((pickOrder: any) => ({
id: pickOrder.id,
code: pickOrder.code,
targetDate: pickOrder.targetDate,
type: pickOrder.type,
status: pickOrder.status,
assignTo: pickOrder.assignTo,
groupName: pickOrder.groupName || "No Group",
consoCode: pickOrder.consoCode,
pickOrderLines: pickOrder.pickOrderLines || []
}));
setOriginalPickOrderData(pickOrderRows);
setFilteredPickOrders(pickOrderRows);
setTotalCountItems(res.total);
} else {
setFilteredPickOrders([]);
setTotalCountItems(0);
}
} catch (error) {
console.error("Error fetching pick orders:", error);
setFilteredPickOrders([]);
setTotalCountItems(0);
} finally {
setIsLoadingItems(false);
}
},
[],
);

// Handle Release operation
// Handle Release operation
const handleRelease = useCallback(async () => {
if (selectedPickOrderIds.length === 0) return;
setIsUploading(true);
try {
// Get the assigned user from the selected pick orders
const selectedPickOrders = filteredPickOrders.filter(pickOrder =>
selectedPickOrderIds.includes(pickOrder.id)
);
// Check if all selected pick orders have the same assigned user
const assignedUsers = selectedPickOrders.map(po => po.assignTo).filter(Boolean);
if (assignedUsers.length === 0) {
alert("Selected pick orders are not assigned to any user.");
return;
}
const assignToValue = assignedUsers[0];
// Validate that all pick orders are assigned to the same user
const allSameUser = assignedUsers.every(userId => userId === assignToValue);
if (!allSameUser) {
alert("All selected pick orders must be assigned to the same user.");
return;
}
console.log("Using assigned user:", assignToValue);
console.log("selectedPickOrderIds:", selectedPickOrderIds);
const releaseRes = await releaseAssignedPickOrders({
pickOrderIds: selectedPickOrderIds.map(id => parseInt(id)),
assignTo: assignToValue
});
if (releaseRes.code === "SUCCESS") {
console.log("Pick orders released successfully");
// Get the consoCode from the response
const consoCode = (releaseRes.entity as any)?.consoCode;
if (consoCode) {
// Create StockOutLine records for each pick order line
for (const pickOrder of selectedPickOrders) {
for (const line of pickOrder.pickOrderLines) {
try {
const stockOutLineData = {
consoCode: consoCode,
pickOrderLineId: line.id,
inventoryLotLineId: 0, // This will be set when user scans QR code
qty: line.requiredQty,
};
console.log("Creating stock out line:", stockOutLineData);
await createStockOutLine(stockOutLineData);
} catch (error) {
console.error("Error creating stock out line for line", line.id, error);
}
}
}
}
fetchNewPageItems(pagingController, filterArgs);
} else {
console.error("Release failed:", releaseRes.message);
}
} catch (error) {
console.error("Error releasing pick orders:", error);
} finally {
setIsUploading(false);
}
}, [selectedPickOrderIds, filteredPickOrders, setIsUploading, fetchNewPageItems, pagingController, filterArgs]);

// Update search criteria to match the new data structure
const searchCriteria: Criterion<any>[] = useMemo(
() => [
{
label: t("Pick Order Code"),
paramName: "code",
type: "text",
},
{
label: t("Group Code"),
paramName: "groupName",
type: "text",
},
{
label: t("Target Date From"),
label2: t("Target Date To"),
paramName: "targetDate",
type: "dateRange",
},
],
[t],
);

// Update search function to work with pick order data
const handleSearch = useCallback((query: Record<string, any>) => {
setSearchQuery({ ...query });

const filtered = originalPickOrderData.filter((pickOrder) => {
const pickOrderTargetDateStr = arrayToDayjs(pickOrder.targetDate);

const codeMatch = !query.code ||
pickOrder.code?.toLowerCase().includes((query.code || "").toLowerCase());
const groupNameMatch = !query.groupName ||
pickOrder.groupName?.toLowerCase().includes((query.groupName || "").toLowerCase());
// Date range search
let dateMatch = true;
if (query.targetDate || query.targetDateTo) {
try {
if (query.targetDate && !query.targetDateTo) {
const fromDate = dayjs(query.targetDate);
dateMatch = pickOrderTargetDateStr.isSame(fromDate, 'day') ||
pickOrderTargetDateStr.isAfter(fromDate, 'day');
} else if (!query.targetDate && query.targetDateTo) {
const toDate = dayjs(query.targetDateTo);
dateMatch = pickOrderTargetDateStr.isSame(toDate, 'day') ||
pickOrderTargetDateStr.isBefore(toDate, 'day');
} else if (query.targetDate && query.targetDateTo) {
const fromDate = dayjs(query.targetDate);
const toDate = dayjs(query.targetDateTo);
dateMatch = (pickOrderTargetDateStr.isSame(fromDate, 'day') ||
pickOrderTargetDateStr.isAfter(fromDate, 'day')) &&
(pickOrderTargetDateStr.isSame(toDate, 'day') ||
pickOrderTargetDateStr.isBefore(toDate, 'day'));
}
} catch (error) {
console.error("Date parsing error:", error);
dateMatch = true;
}
}

return codeMatch && groupNameMatch && dateMatch;
});
setFilteredPickOrders(filtered);
}, [originalPickOrderData]);

const handleReset = useCallback(() => {
setSearchQuery({});
setFilteredPickOrders(originalPickOrderData);
setTimeout(() => {
setSearchQuery({});
}, 0);
}, [originalPickOrderData]);

// Pagination handlers
const handlePageChange = useCallback((event: unknown, newPage: number) => {
const newPagingController = {
...pagingController,
pageNum: newPage + 1,
};
setPagingController(newPagingController);
}, [pagingController]);

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

// Component mount effect
useEffect(() => {
fetchNewPageItems(pagingController, filterArgs || {});
}, []);

// Dependencies change effect
useEffect(() => {
if (pagingController && (filterArgs || {})) {
fetchNewPageItems(pagingController, filterArgs || {});
}
}, [pagingController, filterArgs, fetchNewPageItems]);

useEffect(() => {
const loadUsernameList = async () => {
try {
const res = await fetchNameList();
if (res) {
setUsernameList(res);
}
} catch (error) {
console.error("Error loading username list:", error);
}
};
loadUsernameList();
}, []);

// Update the table component to work with pick order data directly
const CustomPickOrderTable = () => {
// Helper function to get user name
const getUserName = useCallback((assignToId: number | null | undefined) => {
if (!assignToId) return '-';
const user = usernameList.find(u => u.id === assignToId);
return user ? user.name : `User ${assignToId}`;
}, [usernameList]);

return (
<>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>{t("Selected")}</TableCell>
<TableCell>{t("Pick Order Code")}</TableCell>
<TableCell>{t("Group Code")}</TableCell>
<TableCell>{t("Item Code")}</TableCell>
<TableCell>{t("Item Name")}</TableCell>
<TableCell align="right">{t("Order Quantity")}</TableCell>
<TableCell align="right">{t("Current Stock")}</TableCell>
<TableCell align="right">{t("Stock Unit")}</TableCell>
<TableCell>{t("Target Date")}</TableCell>
<TableCell>{t("Assigned To")}</TableCell>
</TableRow>
</TableHead>
<TableBody>
{filteredPickOrders.length === 0 ? (
<TableRow>
<TableCell colSpan={10} align="center">
<Typography variant="body2" color="text.secondary">
{t("No data available")}
</Typography>
</TableCell>
</TableRow>
) : (
filteredPickOrders.map((pickOrder) => (
pickOrder.pickOrderLines.map((line: PickOrderLineRow, index: number) => (
<TableRow key={`${pickOrder.id}-${line.id}`}>
{/* Checkbox - only show for first line of each pick order */}
<TableCell>
{index === 0 ? (
<Checkbox
checked={isPickOrderSelected(pickOrder.id)}
onChange={(e) => handlePickOrderSelect(pickOrder.id, e.target.checked)}
disabled={!isEmpty(pickOrder.consoCode)}
/>
) : null}
</TableCell>
{/* Pick Order Code - only show for first line */}
<TableCell>
{index === 0 ? pickOrder.code : null}
</TableCell>
{/* Group Name - only show for first line */}
<TableCell>
{index === 0 ? pickOrder.groupName : null}
</TableCell>
{/* Item Code */}
<TableCell>{line.itemCode}</TableCell>
{/* Item Name */}
<TableCell>{line.itemName}</TableCell>
{/* Order Quantity */}
<TableCell align="right">{line.requiredQty}</TableCell>
{/* Current Stock */}
<TableCell align="right">
<Typography
variant="body2"
color={line.availableQty && line.availableQty > 0 ? "success.main" : "error.main"}
sx={{ fontWeight: line.availableQty && line.availableQty > 0 ? 'bold' : 'normal' }}
>
{(line.availableQty || 0).toLocaleString()}
</Typography>
</TableCell>
{/* Unit */}
<TableCell align="right">{line.uomDesc}</TableCell>
{/* Target Date - only show for first line */}
<TableCell>
{index === 0 ? (
arrayToDayjs(pickOrder.targetDate)
.add(-1, "month")
.format(OUTPUT_DATE_FORMAT)
) : null}
</TableCell>
{/* Assigned To - only show for first line */}
<TableCell>
{index === 0 ? (
<Typography variant="body2">
{getUserName(pickOrder.assignTo)}
</Typography>
) : null}
</TableCell>
</TableRow>
))
))
)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
component="div"
count={totalCountItems || 0}
page={(pagingController.pageNum - 1)}
rowsPerPage={pagingController.pageSize}
onPageChange={handlePageChange}
onRowsPerPageChange={handlePageSizeChange}
rowsPerPageOptions={[10, 25, 50, 100]}
labelRowsPerPage={t("Rows per page")}
labelDisplayedRows={({ from, to, count }) =>
`${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
}
/>
</>
);
};

return (
<>
<SearchBox criteria={searchCriteria} onSearch={handleSearch} onReset={handleReset} />
<Grid container rowGap={1}>
<Grid item xs={12}>
{isLoadingItems ? (
<CircularProgress size={40} />
) : (
<CustomPickOrderTable />
)}
</Grid>
<Grid item xs={12}>
<Box sx={{ display: "flex", justifyContent: "flex-start", mt: 2 }}>
<Button
disabled={selectedPickOrderIds.length < 1}
variant="outlined"
onClick={handleRelease}
>
{t("Release")}
</Button>
</Box>
</Grid>
</Grid>
</>
);
};

export default AssignTo;

Loading…
Cancel
Save