Bläddra i källkod

update

master
CANCERYS\kw093 2 månader sedan
förälder
incheckning
66d755635e
7 ändrade filer med 269 tillägg och 71 borttagningar
  1. +22
    -1
      src/app/api/pickOrder/actions.ts
  2. +1
    -10
      src/components/FinishedGoodSearch/FGPickOrderCard.tsx
  3. +50
    -12
      src/components/FinishedGoodSearch/FinishedGoodSearch.tsx
  4. +1
    -1
      src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
  5. +189
    -43
      src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx
  6. +2
    -2
      src/components/FinishedGoodSearch/LotConfirmationModal.tsx
  7. +4
    -2
      src/i18n/zh/pickOrder.json

+ 22
- 1
src/app/api/pickOrder/actions.ts Visa fil

@@ -581,7 +581,28 @@ const fetchSuggestionsWithStatus = async (pickOrderLineId: number) => {
return [];
}
};

export const fetchAllPickOrderLotsHierarchical = cache(async (userId: number): Promise<any> => {
try {
console.log("🔍 Fetching hierarchical pick order lots for userId:", userId);
const data = await serverFetchJson<any>(
`${BASE_API_URL}/pickOrder/all-lots-hierarchical/${userId}`,
{
method: 'GET',
next: { tags: ["pickorder"] },
}
);
console.log("✅ Fetched hierarchical lot details:", data);
return data;
} catch (error) {
console.error("❌ Error fetching hierarchical lot details:", error);
return {
pickOrder: null,
pickOrderLines: []
};
}
});
// Update the existing function to use the non-auto-assign endpoint
export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Promise<any[]> => {
try {


+ 1
- 10
src/components/FinishedGoodSearch/FGPickOrderCard.tsx Visa fil

@@ -109,16 +109,7 @@ const FGPickOrderCard: React.FC<Props> = ({ fgOrder, onQrCodeClick }) => {
value={fgOrder.ticketNo}
/>
</Grid>
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'flex-end', mt: 2 }}>
<Button
variant="contained"
startIcon={<QrCodeIcon />}
onClick={() => onQrCodeClick(fgOrder.pickOrderId)}
sx={{ minWidth: 120 }}
>
{t("Print DN/Label")}
</Button>
</Grid>

</Grid>
</Box>
</CardContent>


+ 50
- 12
src/components/FinishedGoodSearch/FinishedGoodSearch.tsx Visa fil

@@ -46,6 +46,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {

const [isOpenCreateModal, setIsOpenCreateModal] = useState(false)
const [items, setItems] = useState<ItemCombo[]>([])
const [printButtonsEnabled, setPrintButtonsEnabled] = useState(false);
const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders);
const [filterArgs, setFilterArgs] = useState<Record<string, any>>({});
const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
@@ -129,6 +130,19 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
}
}
}, [tabIndex, items.length]);
useEffect(() => {
const handleCompletionStatusChange = (event: CustomEvent) => {
const { allLotsCompleted } = event.detail;
setPrintButtonsEnabled(allLotsCompleted);
console.log("Print buttons enabled:", allLotsCompleted);
};

window.addEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
return () => {
window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
};
}, []);

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => {
@@ -290,19 +304,8 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
</Box>
</Grid>


{/* First 4 buttons aligned left */}
<Grid item xs={6}>
<Stack direction="row" spacing={1}>
<Button variant="contained">{t("Print Draft")}</Button>
<Button variant="contained">{t("Print Pick Order and DN Label")}</Button>
<Button variant="contained">{t("Print Pick Order")}</Button>
<Button variant="contained">{t("Print DN Label")}</Button>
</Stack>
</Grid>

{/* Last 2 buttons aligned right */}
<Grid item xs={6} display="flex" justifyContent="flex-end">
<Grid item xs={6} >
<Stack direction="row" spacing={1}>
<Button
variant="contained"
@@ -320,6 +323,41 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
</Button>
</Stack>
</Grid>
{/* ✅ Updated print buttons with completion status */}
<Grid item xs={6} display="flex" justifyContent="flex-end">
<Stack direction="row" spacing={1}>
<Button
variant="contained"
disabled={!printButtonsEnabled}
title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""}
>
{t("Print Draft")}
</Button>
<Button
variant="contained"
disabled={!printButtonsEnabled}
title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""}
>
{t("Print Pick Order and DN Label")}
</Button>
<Button
variant="contained"
disabled={!printButtonsEnabled}
title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""}
>
{t("Print Pick Order")}
</Button>
<Button
variant="contained"
disabled={!printButtonsEnabled}
title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""}
>
{t("Print DN Label")}
</Button>
</Stack>
</Grid>


</Grid>
</Stack>
</Box>


+ 1
- 1
src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx Visa fil

@@ -351,7 +351,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
</DialogContent>
<DialogActions>
<Button onClick={handleClose} disabled={loading}>
{t('cancel')}
{t('Cancel')}
</Button>
<Button
onClick={handleSubmit}


+ 189
- 43
src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx Visa fil

@@ -34,6 +34,7 @@ import {
autoAssignAndReleasePickOrder,
AutoAssignReleaseResponse,
checkPickOrderCompletion,
fetchAllPickOrderLotsHierarchical,
PickOrderCompletionResponse,
checkAndCompletePickOrderByConsoCode,
updateSuggestedLotLineId,
@@ -322,7 +323,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
const { data: session } = useSession() as { data: SessionWithTokens | null };
const currentUserId = session?.id ? parseInt(session.id) : undefined;
const [allLotsCompleted, setAllLotsCompleted] = useState(false);
const [combinedLotData, setCombinedLotData] = useState<any[]>([]);
const [combinedDataLoading, setCombinedDataLoading] = useState(false);
const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]);
@@ -403,7 +404,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
fetchFgPickOrdersData();
}
}, [combinedLotData, fetchFgPickOrdersData]);
// ✅ Handle QR code button click
const handleQrCodeClick = (pickOrderId: number) => {
console.log(`QR Code clicked for pick order ID: ${pickOrderId}`);
@@ -416,7 +417,31 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
setScannedLotData(scannedLot);
setLotConfirmationOpen(true);
}, []);
const checkAllLotsCompleted = useCallback((lotData: any[]) => {
if (lotData.length === 0) {
setAllLotsCompleted(false);
return false;
}

// Filter out rejected lots
const nonRejectedLots = lotData.filter(lot =>
lot.lotAvailability !== 'rejected' &&
lot.stockOutLineStatus !== 'rejected'
);

if (nonRejectedLots.length === 0) {
setAllLotsCompleted(false);
return false;
}

// Check if all non-rejected lots are completed
const allCompleted = nonRejectedLots.every(lot =>
lot.stockOutLineStatus === 'completed'
);

setAllLotsCompleted(allCompleted);
return allCompleted;
}, []);
const fetchAllCombinedLotData = useCallback(async (userId?: number) => {
setCombinedDataLoading(true);
try {
@@ -428,22 +453,111 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
console.warn("⚠️ No userId available, skipping API call");
setCombinedLotData([]);
setOriginalCombinedData([]);
setAllLotsCompleted(false);
return;
}
// ✅ Use the non-auto-assign endpoint - this only fetches existing data
const allLotDetails = await fetchALLPickOrderLineLotDetails(userIdToUse);
console.log("✅ All combined lot details:", allLotDetails);
setCombinedLotData(allLotDetails);
setOriginalCombinedData(allLotDetails);
// ✅ Use the hierarchical endpoint that includes rejected lots
const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse);
console.log("✅ Hierarchical lot details:", hierarchicalData);
// ✅ Transform hierarchical data to flat structure for the table
const flatLotData: any[] = [];
if (hierarchicalData.pickOrder && hierarchicalData.pickOrderLines) {
hierarchicalData.pickOrderLines.forEach((line: any) => {
if (line.lots && line.lots.length > 0) {
line.lots.forEach((lot: any) => {
flatLotData.push({
// Pick order info
pickOrderId: hierarchicalData.pickOrder.id,
pickOrderCode: hierarchicalData.pickOrder.code,
pickOrderConsoCode: hierarchicalData.pickOrder.consoCode,
pickOrderTargetDate: hierarchicalData.pickOrder.targetDate,
pickOrderType: hierarchicalData.pickOrder.type,
pickOrderStatus: hierarchicalData.pickOrder.status,
pickOrderAssignTo: hierarchicalData.pickOrder.assignTo,
// Pick order line info
pickOrderLineId: line.id,
pickOrderLineRequiredQty: line.requiredQty,
pickOrderLineStatus: line.status,
// Item info
itemId: line.item.id,
itemCode: line.item.code,
itemName: line.item.name,
uomCode: line.item.uomCode,
uomDesc: line.item.uomDesc,
// Lot info
lotId: lot.id,
lotNo: lot.lotNo,
expiryDate: lot.expiryDate,
location: lot.location,
stockUnit: lot.stockUnit,
availableQty: lot.availableQty,
requiredQty: lot.requiredQty,
actualPickQty: lot.actualPickQty,
inQty: lot.inQty,
outQty: lot.outQty,
holdQty: lot.holdQty,
lotStatus: lot.lotStatus,
lotAvailability: lot.lotAvailability,
processingStatus: lot.processingStatus,
suggestedPickLotId: lot.suggestedPickLotId,
stockOutLineId: lot.stockOutLineId,
stockOutLineStatus: lot.stockOutLineStatus,
stockOutLineQty: lot.stockOutLineQty,
// Router info
routerId: lot.router?.id,
routerIndex: lot.router?.index,
routerRoute: lot.router?.route,
routerArea: lot.router?.area,
uomShortDesc: lot.router?.uomId
});
});
}
});
}
console.log("✅ Transformed flat lot data:", flatLotData);
setCombinedLotData(flatLotData);
setOriginalCombinedData(flatLotData);
// ✅ Check completion status
checkAllLotsCompleted(flatLotData);
} catch (error) {
console.error("❌ Error fetching combined lot data:", error);
setCombinedLotData([]);
setOriginalCombinedData([]);
setAllLotsCompleted(false);
} finally {
setCombinedDataLoading(false);
}
}, [currentUserId]);
}, [currentUserId, checkAllLotsCompleted]);

// ✅ Add effect to check completion when lot data changes
useEffect(() => {
if (combinedLotData.length > 0) {
checkAllLotsCompleted(combinedLotData);
}
}, [combinedLotData, checkAllLotsCompleted]);

// ✅ Add function to expose completion status to parent
const getCompletionStatus = useCallback(() => {
return allLotsCompleted;
}, [allLotsCompleted]);

// ✅ Expose completion status to parent component
useEffect(() => {
// Dispatch custom event with completion status
const event = new CustomEvent('pickOrderCompletionStatus', {
detail: { allLotsCompleted }
});
window.dispatchEvent(event);
}, [allLotsCompleted]);
const handleLotConfirmation = useCallback(async () => {
if (!expectedLotData || !scannedLotData || !selectedLotForQr) return;
setIsConfirmingLot(true);
@@ -465,6 +579,15 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
newInventoryLotLineId: newLotLineId
});
setQrScanError(false);
setQrScanSuccess(false);
setQrScanInput('');
setIsManualScanning(false);
stopScan();
resetScan();
setProcessedQrCodes(new Set());
setLastProcessedQr('');

setLotConfirmationOpen(false);
setExpectedLotData(null);
setScannedLotData(null);
@@ -672,9 +795,9 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
console.error("No item match in expected lots for scanned code");
setQrScanError(true);
setQrScanSuccess(false);

return;
}
// 2) Check if scanned lot is exactly in expected lots
const exactLotMatch = sameItemLotsInExpected.find(l =>
(scanned?.inventoryLotLineId && l.lotId === scanned.inventoryLotLineId) ||
@@ -697,6 +820,13 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
return;
}
// ✅ Check if the expected lot is already the scanned lot (after substitution)
if (expectedLot.lotNo === scanned?.lotNo) {
console.log(`Lot already substituted, proceeding with ${scanned.lotNo}`);
handleQrCodeSubmit(scanned.lotNo);
return;
}
setSelectedLotForQr(expectedLot);
handleLotMismatch(
{
@@ -720,26 +850,27 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
}
}, [combinedLotData, handleQrCodeSubmit, handleLotMismatch]);
// ✅ Update the outside QR scanning effect to use enhanced processing
useEffect(() => {
if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) {
return;
}
// ✅ Update the outside QR scanning effect to use enhanced processing
useEffect(() => {
if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) {
return;
}

const latestQr = qrValues[qrValues.length - 1];
const latestQr = qrValues[qrValues.length - 1];
if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) {
console.log("QR code already processed, skipping...");
return;
}

if (latestQr && latestQr !== lastProcessedQr) {
console.log(`🔍 Processing new QR code with enhanced validation: ${latestQr}`);
setLastProcessedQr(latestQr);
setProcessedQrCodes(prev => new Set(prev).add(latestQr));
if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) {
console.log("QR code already processed, skipping...");
return;
}
if (latestQr && latestQr !== lastProcessedQr) {
console.log(`🔍 Processing new QR code with enhanced validation: ${latestQr}`);
setLastProcessedQr(latestQr);
setProcessedQrCodes(prev => new Set(prev).add(latestQr));
processOutsideQrCode(latestQr);
}
}, [qrValues, isManualScanning, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode]);
processOutsideQrCode(latestQr);
}
}, [qrValues, isManualScanning, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode, combinedLotData]);
// ✅ Only fetch existing data when session is ready, no auto-assignment
useEffect(() => {
if (session && currentUserId && !initializationRef.current) {
@@ -1315,7 +1446,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
{/* <TableCell>{t("Lot Location")}</TableCell> */}
<TableCell align="right">{t("Lot Required Pick Qty")}</TableCell>
{/* <TableCell align="right">{t("Original Available Qty")}</TableCell> */}
<TableCell align="right">{t("Scan Result")}</TableCell>
<TableCell align="center">{t("Scan Result")}</TableCell>
<TableCell align="center">{t("Submit Required Pick Qty")}</TableCell>
{/* <TableCell align="right">{t("Remaining Available Qty")}</TableCell> */}
@@ -1380,21 +1511,36 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
return result.toLocaleString()+'('+lot.uomShortDesc+')';
})()}
</TableCell>
<TableCell align="center">
{lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? (
<Checkbox
checked={lot.stockOutLineStatus?.toLowerCase() !== 'pending'}
disabled={true}
readOnly={true}
sx={{
color: lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? 'success.main' : 'grey.400',
'&.Mui-checked': {
color: 'success.main',
},
}}
/>
) : null}
</TableCell>
<TableCell align="center">
{lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? (
<Box sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: '100%'
}}>
<Checkbox
checked={lot.stockOutLineStatus?.toLowerCase() !== 'pending'}
disabled={true}
readOnly={true}
size="large"
sx={{
color: lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? 'success.main' : 'grey.400',
'&.Mui-checked': {
color: 'success.main',
},
transform: 'scale(1.3)',
'& .MuiSvgIcon-root': {
fontSize: '1.5rem',
}
}}
/>
</Box>
) : null}
</TableCell>

<TableCell align="center">
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<Stack direction="row" spacing={1} alignItems="center">


+ 2
- 2
src/components/FinishedGoodSearch/LotConfirmationModal.tsx Visa fil

@@ -92,7 +92,7 @@ const LotConfirmationModal: React.FC<LotConfirmationModalProps> = ({
</Box>

<Alert severity="info">
{t("If you proceed, the system will:")}
{t("If you confirm, the system will:")}
<ul style={{ margin: '8px 0 0 16px' }}>
<li>{t("Update your suggested lot to the this scanned lot")}</li>
</ul>
@@ -114,7 +114,7 @@ const LotConfirmationModal: React.FC<LotConfirmationModalProps> = ({
color="warning"
disabled={isLoading}
>
{isLoading ? t("Processing...") : t("Yes, Use This Lot")}
{isLoading ? t("Processing...") : t("Confirm")}
</Button>
</DialogActions>
</Dialog>


+ 4
- 2
src/i18n/zh/pickOrder.json Visa fil

@@ -192,7 +192,7 @@
"Finished Good Order": "成品出倉",
"Assign and Release": "分派並放單",
"Original Available Qty": "原可用數",
"Remaining Available Qty": "剩餘",
"Remaining Available Qty": "剩餘可用數",
"Please submit pick order.": "請提交提料單。",
"Please finish QR code scan and pick order.": "請完成 QR 碼掃描和提料。",
"Please finish QR code scanand pick order.": "請完成 QR 碼掃描和提料。",
@@ -273,7 +273,9 @@
"Print Draft":"列印草稿",
"Print Pick Order and DN Label":"列印提料單和送貨單標貼",
"Print Pick Order":"列印提料單",
"Print DN Label":"列印送貨單標貼"
"Print DN Label":"列印送貨單標貼",
"If you confirm, the system will:":"如果您確認,系統將:"





Laddar…
Avbryt
Spara