CANCERYS\kw093 vor 2 Monaten
Ursprung
Commit
00dbb3aa6c
8 geänderte Dateien mit 322 neuen und 35 gelöschten Zeilen
  1. +1
    -1
      src/app/api/jo/actions.ts
  2. +2
    -2
      src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx
  3. +120
    -9
      src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx
  4. +100
    -9
      src/components/Jodetail/JobPickExecution.tsx
  5. +87
    -10
      src/components/Jodetail/JobPickExecutionsecondscan.tsx
  6. +1
    -1
      src/components/Jodetail/completeJobOrderRecord.tsx
  7. +5
    -1
      src/i18n/zh/jo.json
  8. +6
    -2
      src/i18n/zh/pickOrder.json

+ 1
- 1
src/app/api/jo/actions.ts Datei anzeigen

@@ -195,7 +195,7 @@ export const fetchJobOrderLotsHierarchical = cache(async (userId: number) => {
});
export const fetchCompletedJobOrderPickOrders = cache(async (userId: number) => {
return serverFetchJson<any>(
`${BASE_API_URL}/jo/completed-job-order-pick-orders${userId}`,
`${BASE_API_URL}/jo/completed-job-order-pick-orders/${userId}`,
{
method: "GET",
next: { tags: ["jo-completed"] },


+ 2
- 2
src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx Datei anzeigen

@@ -328,7 +328,7 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
<TableCell>{lot.actualPickQty}</TableCell>
<TableCell>
<Chip
label={lot.processingStatus}
label={t(lot.processingStatus)}
color={lot.processingStatus === 'completed' ? 'success' : 'default'}
size="small"
/>
@@ -394,7 +394,7 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
</Box>
<Box>
<Chip
label={doPickOrder.pickOrderStatus}
label={t(doPickOrder.pickOrderStatus)}
color={doPickOrder.pickOrderStatus === 'completed' ? 'success' : 'default'}
size="small"
sx={{ mb: 1 }}


+ 120
- 9
src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx Datei anzeigen

@@ -367,7 +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 [isSubmittingAll, setIsSubmittingAll] = useState<boolean>(false);
const fetchFgPickOrdersData = useCallback(async () => {
if (!currentUserId) return;
@@ -1360,6 +1360,108 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
stopScan();
resetScan();
}, [stopScan, resetScan]);
const handleSubmitAllScanned = useCallback(async () => {
const scannedLots = combinedLotData.filter(lot =>
lot.stockOutLineStatus === 'checked' // Only submit items that are scanned but not yet submitted
);
if (scannedLots.length === 0) {
console.log("No scanned items to submit");
return;
}
setIsSubmittingAll(true);
console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`);
try {
// ✅ Submit all items in parallel using Promise.all
const submitPromises = scannedLots.map(async (lot) => {
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
const currentActualPickQty = lot.actualPickQty || 0;
const cumulativeQty = currentActualPickQty + submitQty;
let newStatus = 'partially_completed';
if (cumulativeQty >= lot.requiredQty) {
newStatus = 'completed';
}
console.log(`Submitting lot ${lot.lotNo}: qty=${cumulativeQty}, status=${newStatus}`);
// Update stock out line
await updateStockOutLineStatus({
id: lot.stockOutLineId,
status: newStatus,
qty: cumulativeQty
});
// Update inventory
if (submitQty > 0) {
await updateInventoryLotLineQuantities({
inventoryLotLineId: lot.lotId,
qty: submitQty,
status: 'available',
operation: 'pick'
});
}
// Check if pick order is completed
if (newStatus === 'completed' && lot.pickOrderConsoCode) {
await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
}
return { success: true, lotNo: lot.lotNo };
});
// ✅ Wait for all submissions to complete
const results = await Promise.all(submitPromises);
const successCount = results.filter(r => r.success).length;
console.log(`✅ Batch submit completed: ${successCount}/${scannedLots.length} items submitted`);
// ✅ Refresh data once after all submissions
await fetchAllCombinedLotData();
if (successCount > 0) {
setQrScanSuccess(true);
setTimeout(() => {
setQrScanSuccess(false);
checkAndAutoAssignNext();
}, 2000);
}
} catch (error) {
console.error("Error submitting all scanned items:", error);
setQrScanError(true);
} finally {
setIsSubmittingAll(false);
}
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext]);

// ✅ Calculate scanned items count
const scannedItemsCount = useMemo(() => {
return combinedLotData.filter(lot => lot.stockOutLineStatus === 'checked').length;
}, [combinedLotData]);

// ✅ ADD THIS: Auto-stop scan when no data available
useEffect(() => {
if (isManualScanning && combinedLotData.length === 0) {
console.log("⏹️ No data available, auto-stopping QR scan...");
handleStopScan();
}
}, [combinedLotData.length, isManualScanning, handleStopScan]);

// ✅ Cleanup effect
useEffect(() => {
return () => {
// Cleanup when component unmounts (e.g., when switching tabs)
if (isManualScanning) {
console.log("🧹 Pick execution component unmounting, stopping QR scanner...");
stopScan();
resetScan();
}
};
}, [isManualScanning, stopScan, resetScan]);

const getStatusMessage = useCallback((lot: any) => {
switch (lot.stockOutLineStatus?.toLowerCase()) {
case 'pending':
@@ -1445,14 +1547,23 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
</Button>
)}
{isManualScanning && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<CircularProgress size={16} />
<Typography variant="caption" color="primary">
{t("Scanning...")}
</Typography>
</Box>
)}
{/* ✅ ADD THIS: Submit All Scanned Button */}
<Button
variant="contained"
color="success"
onClick={handleSubmitAllScanned}
disabled={scannedItemsCount === 0 || isSubmittingAll}
sx={{ minWidth: '160px' }}
>
{isSubmittingAll ? (
<>
<CircularProgress size={16} sx={{ mr: 1, color: 'white' }} />
{t("Submitting...")}
</>
) : (
`${t("Submit All Scanned")} (${scannedItemsCount})`
)}
</Button>
</Box>
</Box>


+ 100
- 9
src/components/Jodetail/JobPickExecution.tsx Datei anzeigen

@@ -356,6 +356,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {

const formProps = useForm();
const errors = formProps.formState.errors;
const [isSubmittingAll, setIsSubmittingAll] = useState<boolean>(false);

// ✅ Add QR modal states
const [qrModalOpen, setQrModalOpen] = useState(false);
@@ -1223,6 +1224,87 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
console.error("Error submitting pick quantity:", error);
}
}, [fetchJobOrderData, checkAndAutoAssignNext]);
const handleSubmitAllScanned = useCallback(async () => {
const scannedLots = combinedLotData.filter(lot =>
lot.stockOutLineStatus === 'checked' // Only submit items that are scanned but not yet submitted
);
if (scannedLots.length === 0) {
console.log("No scanned items to submit");
return;
}
setIsSubmittingAll(true);
console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`);
try {
// ✅ Submit all items in parallel using Promise.all
const submitPromises = scannedLots.map(async (lot) => {
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
const currentActualPickQty = lot.actualPickQty || 0;
const cumulativeQty = currentActualPickQty + submitQty;
let newStatus = 'partially_completed';
if (cumulativeQty >= lot.requiredQty) {
newStatus = 'completed';
}
console.log(`Submitting lot ${lot.lotNo}: qty=${cumulativeQty}, status=${newStatus}`);
// Update stock out line
await updateStockOutLineStatus({
id: lot.stockOutLineId,
status: newStatus,
qty: cumulativeQty
});
// Update inventory
if (submitQty > 0) {
await updateInventoryLotLineQuantities({
inventoryLotLineId: lot.lotId,
qty: submitQty,
status: 'available',
operation: 'pick'
});
}
// Check if pick order is completed
if (newStatus === 'completed' && lot.pickOrderConsoCode) {
await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
}
return { success: true, lotNo: lot.lotNo };
});
// ✅ Wait for all submissions to complete
const results = await Promise.all(submitPromises);
const successCount = results.filter(r => r.success).length;
console.log(`✅ Batch submit completed: ${successCount}/${scannedLots.length} items submitted`);
// ✅ Refresh data once after all submissions
await fetchJobOrderData();
if (successCount > 0) {
setQrScanSuccess(true);
setTimeout(() => {
setQrScanSuccess(false);
checkAndAutoAssignNext();
}, 2000);
}
} catch (error) {
console.error("Error submitting all scanned items:", error);
setQrScanError(true);
} finally {
setIsSubmittingAll(false);
}
}, [combinedLotData, fetchJobOrderData, checkAndAutoAssignNext]);

// ✅ Calculate scanned items count
const scannedItemsCount = useMemo(() => {
return combinedLotData.filter(lot => lot.stockOutLineStatus === 'checked').length;
}, [combinedLotData]);
// ✅ Handle reject lot
const handleRejectLot = useCallback(async (lot: any) => {
if (!lot.stockOutLineId) {
@@ -1509,15 +1591,24 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
{t("Stop QR Scan")}
</Button>
)}
{isManualScanning && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<CircularProgress size={16} />
<Typography variant="caption" color="primary">
{t("Scanning...")}
</Typography>
</Box>
)}
{/* ✅ ADD THIS: Submit All Scanned Button */}
<Button
variant="contained"
color="success"
onClick={handleSubmitAllScanned}
disabled={scannedItemsCount === 0 || isSubmittingAll}
sx={{ minWidth: '160px' }}
>
{isSubmittingAll ? (
<>
<CircularProgress size={16} sx={{ mr: 1 }} />
{t("Submitting...")}
</>
) : (
`${t("Submit All Scanned")} (${scannedItemsCount})`
)}
</Button>
</Box>
</Box>



+ 87
- 10
src/components/Jodetail/JobPickExecutionsecondscan.tsx Datei anzeigen

@@ -332,7 +332,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
const [pickQtyData, setPickQtyData] = useState<Record<string, number>>({});
const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
const [isSubmittingAll, setIsSubmittingAll] = useState<boolean>(false);
const [paginationController, setPaginationController] = useState({
pageNum: 0,
pageSize: 10,
@@ -532,6 +532,66 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
setCombinedDataLoading(false);
}
}, [currentUserId]);
const handleSubmitAllScanned = useCallback(async () => {
const scannedLots = combinedLotData.filter(lot =>
lot.secondQrScanStatus === 'scanned' // Only submit items that are scanned but not yet submitted
);
if (scannedLots.length === 0) {
console.log("No scanned items to submit");
return;
}
setIsSubmittingAll(true);
console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`);
try {
// ✅ Submit all items in parallel using Promise.all
const submitPromises = scannedLots.map(async (lot) => {
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
console.log(`Submitting item ${lot.itemCode}: qty=${submitQty}`);
const result = await submitSecondScanQuantity(
lot.pickOrderId,
lot.itemId,
{
qty: submitQty,
isMissing: false,
isBad: false,
reason: undefined
}
);
return { success: result.code === "SUCCESS", itemCode: lot.itemCode };
});
// ✅ Wait for all submissions to complete
const results = await Promise.all(submitPromises);
const successCount = results.filter(r => r.success).length;
console.log(`✅ Batch submit completed: ${successCount}/${scannedLots.length} items submitted`);
// ✅ Refresh data once after all submissions
await fetchJobOrderData();
if (successCount > 0) {
setQrScanSuccess(true);
setTimeout(() => setQrScanSuccess(false), 2000);
}
} catch (error) {
console.error("Error submitting all scanned items:", error);
setQrScanError(true);
} finally {
setIsSubmittingAll(false);
}
}, [combinedLotData, fetchJobOrderData]);

// ✅ Calculate scanned items count
const scannedItemsCount = useMemo(() => {
return combinedLotData.filter(lot => lot.secondQrScanStatus === 'scanned').length;
}, [combinedLotData]);

// ✅ 修改:初始化时加载数据
useEffect(() => {
@@ -983,6 +1043,14 @@ const paginatedData = useMemo(() => {
stopScan();
resetScan();
}, [stopScan, resetScan]);
useEffect(() => {
if (isManualScanning && combinedLotData.length === 0) {
console.log("⏹️ No data available, auto-stopping QR scan...");
handleStopScan();
}
}, [combinedLotData.length, isManualScanning, handleStopScan]);

// ✅ Cleanup effect
useEffect(() => {
return () => {
// Cleanup when component unmounts (e.g., when switching tabs)
@@ -1020,7 +1088,7 @@ const paginatedData = useMemo(() => {
<Paper sx={{ p: 2 }}>
<Stack direction="row" spacing={4} useFlexGap flexWrap="wrap">
<Typography variant="subtitle1">
<strong>{t("Job Order")}:</strong> {jobOrderData.pickOrder?.jobOrder?.name || '-'}
<strong>{t("Job Order")}:</strong> {jobOrderData.pickOrder?.jobOrder?.code || '-'}
</Typography>
<Typography variant="subtitle1">
<strong>{t("Pick Order Code")}:</strong> {jobOrderData.pickOrder?.code || '-'}
@@ -1063,14 +1131,23 @@ const paginatedData = useMemo(() => {
</Button>
)}
{isManualScanning && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<CircularProgress size={16} />
<Typography variant="caption" color="primary">
{t("Scanning...")}
</Typography>
</Box>
)}
{/* ✅ ADD THIS: Submit All Scanned Button */}
<Button
variant="contained"
color="success"
onClick={handleSubmitAllScanned}
disabled={scannedItemsCount === 0 || isSubmittingAll}
sx={{ minWidth: '160px' }}
>
{isSubmittingAll ? (
<>
<CircularProgress size={16} sx={{ mr: 1 }} />
{t("Submitting...")}
</>
) : (
`${t("Submit All Scanned")} (${scannedItemsCount})`
)}
</Button>
</Box>
</Box>



+ 1
- 1
src/components/Jodetail/completeJobOrderRecord.tsx Datei anzeigen

@@ -568,7 +568,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
</Box>
<Box>
<Chip
label={jobOrderPickOrder.pickOrderStatus}
label={t(jobOrderPickOrder.pickOrderStatus) }
color={jobOrderPickOrder.pickOrderStatus === 'completed' ? 'success' : 'default'}
size="small"
sx={{ mb: 1 }}


+ 5
- 1
src/i18n/zh/jo.json Datei anzeigen

@@ -254,5 +254,9 @@
"stock in information": "庫存信息",
"No Uom": "沒有單位",
"Print Pick Record" : "打印板頭紙",
"Printed Successfully." : "成功列印"
"Printed Successfully." : "成功列印",
"Submit All Scanned": "提交所有已掃描項目",
"Submitting...": "提交中...",
"COMPLETED": "已完成",
"success": "成功"
}

+ 6
- 2
src/i18n/zh/pickOrder.json Datei anzeigen

@@ -53,7 +53,7 @@
"received": "已收貨",
"completed": "已完成",
"rejected": "已拒絕",
"success": "成功",
"acceptedQty must not greater than": "接受數量不得大於",
"minimal value is 1": "最小值為1",
"value must be a number": "值必須是數字",
@@ -363,7 +363,11 @@
"Input quantity cannot exceed": "輸入數量不能超過",
"Quantity cannot be negative": "數量不能為負數",
"Enter bad item quantity (required if no missing items)": "請輸入不良數量(如果沒有缺少項目)",
"Enter missing quantity (required if no bad items)": "請輸入缺少數量(如果沒有不良項目)"
"Enter missing quantity (required if no bad items)": "請輸入缺少數量(如果沒有不良項目)",
"Submit All Scanned": "提交所有已掃描項目",
"Submitting...": "提交中...",
"COMPLETED": "已完成"



Laden…
Abbrechen
Speichern