Browse Source

update

master
CANCERYS\kw093 1 month ago
parent
commit
7e827c5192
35 changed files with 1834 additions and 785 deletions
  1. +4
    -4
      check-translations.js
  2. +8
    -9
      src/app/(main)/production/page.tsx
  3. +133
    -3
      src/app/api/jo/actions.ts
  4. +24
    -22
      src/app/api/pickOrder/actions.ts
  5. +12
    -12
      src/components/DoDetail/DoDetail.tsx
  6. +3
    -3
      src/components/FinishedGoodSearch/CombinedLotTable.tsx
  7. +6
    -5
      src/components/FinishedGoodSearch/FGPickOrderCard.tsx
  8. +6
    -6
      src/components/FinishedGoodSearch/FGPickOrderInfoCard.tsx
  9. +25
    -16
      src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx
  10. +6
    -6
      src/components/FinishedGoodSearch/FinishedGoodSearch.tsx
  11. +53
    -53
      src/components/FinishedGoodSearch/GoodPickExecution.tsx
  12. +13
    -13
      src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
  13. +30
    -30
      src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx
  14. +131
    -131
      src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx
  15. +38
    -38
      src/components/FinishedGoodSearch/PickQcStockInModalVer3.tsx
  16. +2
    -2
      src/components/FinishedGoodSearch/newcreatitem.tsx
  17. +3
    -3
      src/components/Jodetail/CombinedLotTable.tsx
  18. +30
    -30
      src/components/Jodetail/FInishedJobOrderRecord.tsx
  19. +90
    -90
      src/components/Jodetail/JobPickExecution.tsx
  20. +20
    -20
      src/components/Jodetail/JobPickExecutionForm.tsx
  21. +73
    -73
      src/components/Jodetail/JobPickExecutionsecondscan.tsx
  22. +21
    -21
      src/components/Jodetail/JobmatchForm.tsx
  23. +10
    -10
      src/components/Jodetail/JodetailSearch.tsx
  24. +32
    -32
      src/components/Jodetail/completeJobOrderRecord.tsx
  25. +66
    -66
      src/components/PickOrderSearch/LotTable.tsx
  26. +20
    -20
      src/components/PickOrderSearch/PickExecution.tsx
  27. +10
    -10
      src/components/PickOrderSearch/PickExecutionForm.tsx
  28. +3
    -3
      src/components/PickOrderSearch/PickOrderDetailsTable.tsx
  29. +38
    -38
      src/components/PickOrderSearch/PickQcStockInModalVer3.tsx
  30. +2
    -2
      src/components/PickOrderSearch/newcreatitem.tsx
  31. +6
    -6
      src/components/ProductionProcess/MachineScanner.tsx
  32. +8
    -8
      src/components/ProductionProcess/OperatorScanner.tsx
  33. +656
    -0
      src/components/ProductionProcess/ProductionProcessDetail.tsx
  34. +185
    -0
      src/components/ProductionProcess/ProductionProcessList.tsx
  35. +67
    -0
      src/components/ProductionProcess/ProductionProcessPage.tsx

+ 4
- 4
check-translations.js View File

@@ -11,7 +11,7 @@ function checkMissingTranslations(sourceFile, jsonFile) {
// 读取翻译 JSON 文件 // 读取翻译 JSON 文件
const translations = JSON.parse(fs.readFileSync(jsonFile, 'utf-8')); const translations = JSON.parse(fs.readFileSync(jsonFile, 'utf-8'));
// 只匹配 t('...') 和 t("...") 和 t(`...`),不包含模板变量
// 只匹配 t('...') 和 t("...") 和 t(`...`),不包含模板变量
const tRegex = /\bt\(["`']([^"`'${}]+)["`']\)/g; const tRegex = /\bt\(["`']([^"`'${}]+)["`']\)/g;
const matches = [...sourceCode.matchAll(tRegex)]; const matches = [...sourceCode.matchAll(tRegex)];
@@ -86,7 +86,7 @@ if (args.length === 0) {
console.log(' node check-translations.js src/components/Jodetail/JodetailSearch.tsx src/i18n/zh/jo.json'); console.log(' node check-translations.js src/components/Jodetail/JodetailSearch.tsx src/i18n/zh/jo.json');
console.log(' node check-translations.js --dir src/components/Jodetail src/i18n/zh/jo.json'); console.log(' node check-translations.js --dir src/components/Jodetail src/i18n/zh/jo.json');
console.log('\n注意:'); console.log('\n注意:');
console.log(' 只检查 t("key") 调用');
console.log(' 只检查 t("key") 调用');
console.log(' ❌ 忽略 alert(), console.log() 等普通字符串'); console.log(' ❌ 忽略 alert(), console.log() 等普通字符串');
console.log(' ❌ 忽略模板字符串中的 ${} 变量部分'); console.log(' ❌ 忽略模板字符串中的 ${} 变量部分');
process.exit(0); process.exit(0);
@@ -103,7 +103,7 @@ if (args[0] === '--dir') {
const { results, totalMissing } = checkDirectory(directory, jsonFile); const { results, totalMissing } = checkDirectory(directory, jsonFile);
if (Object.keys(results).length === 0) { if (Object.keys(results).length === 0) {
console.log(' 太棒了!没有发现缺失的翻译键!');
console.log(' 太棒了!没有发现缺失的翻译键!');
} else { } else {
console.log(`⚠️ 发现 ${Object.keys(results).length} 个文件有缺失的翻译键\n`); console.log(`⚠️ 发现 ${Object.keys(results).length} 个文件有缺失的翻译键\n`);
@@ -153,7 +153,7 @@ if (args[0] === '--dir') {
}); });
console.log('─'.repeat(60)); console.log('─'.repeat(60));
} else { } else {
console.log('\n 太棒了!所有使用的翻译键都已定义!');
console.log('\n 太棒了!所有使用的翻译键都已定义!');
} }
if (result.unusedKeys.length > 0 && result.unusedKeys.length <= 20) { if (result.unusedKeys.length > 0 && result.unusedKeys.length <= 20) {


+ 8
- 9
src/app/(main)/production/page.tsx View File

@@ -1,4 +1,4 @@
import ProductionProcess from "../../../components/ProductionProcess";
import ProductionProcessPage from "../../../components/ProductionProcess/ProductionProcessPage";
import { getServerI18n } from "../../../i18n"; import { getServerI18n } from "../../../i18n";


import Add from "@mui/icons-material/Add"; import Add from "@mui/icons-material/Add";
@@ -15,7 +15,6 @@ export const metadata: Metadata = {


const production: React.FC = async () => { const production: React.FC = async () => {
const { t } = await getServerI18n("claims"); const { t } = await getServerI18n("claims");
// preloadClaims();


return ( return (
<> <>
@@ -26,22 +25,22 @@ const production: React.FC = async () => {
rowGap={2} rowGap={2}
> >
<Typography variant="h4" marginInlineEnd={2}> <Typography variant="h4" marginInlineEnd={2}>
{t("Production")}
{t("Production Process")}
</Typography> </Typography>
<Button
{/* Optional: Remove or modify create button, because creation is done via API automatically */}
{/* <Button
variant="contained" variant="contained"
startIcon={<Add />} startIcon={<Add />}
LinkComponent={Link} LinkComponent={Link}
href="/production/create" href="/production/create"
> >
{t("Create Claim")}
</Button>
{t("Create Process")}
</Button> */}
</Stack> </Stack>
{/* <Suspense fallback={<ClaimSearch.Loading />}> */}
<ProductionProcess />
{/* </Suspense> */}
<ProductionProcessPage /> {/* Use new component */}
</> </>
); );
}; };



export default production; export default production;

+ 133
- 3
src/app/api/jo/actions.ts View File

@@ -129,13 +129,13 @@ export const recordSecondScanIssue = cache(async (
itemId: number, itemId: number,
data: { data: {
qty: number; // verified qty (actual pick qty) qty: number; // verified qty (actual pick qty)
missQty?: number; // 添加:miss qty
badItemQty?: number; // 添加:bad item qty
missQty?: number; // 添加:miss qty
badItemQty?: number; // 添加:bad item qty
isMissing: boolean; isMissing: boolean;
isBad: boolean; isBad: boolean;
reason: string; reason: string;
createdBy: number; createdBy: number;
type?: string; // type 也应该是可选的
type?: string; // type 也应该是可选的
} }
) => { ) => {
@@ -188,6 +188,136 @@ export interface ProductProcessWithLinesResponse {
date: string; date: string;
lines: ProductProcessLineResponse[]; lines: ProductProcessLineResponse[];
} }
export interface UpdateProductProcessLineQtyRequest {
productProcessLineId: number;
outputFromProcessQty: number;
outputFromProcessUom: string;
defectQty: number;
defectUom: string;
scrapQty: number;
scrapUom: string;
}
export interface UpdateProductProcessLineQtyResponse {
id: number;
outputFromProcessQty: number;
outputFromProcessUom: string;
defectQty: number;
defectUom: string;
scrapQty: number;
scrapUom: string;
byproductName: string;
byproductQty: number;
byproductUom: string;
}
export interface AllProductProcessResponse {
id: number;
productProcessCode: string;
status: string;
startTime?: string;
endTime?: string;
date: string;
bomId?: number;
}
export interface AllJoborderProductProcessInfoResponse {
id: number;
productProcessCode: string;
status: string;
startTime?: string;
endTime?: string;
date: string;
bomId?: number;
itemName: string;
jobOrderId: number;
jobOrderCode: string;
productProcessLineCount: number;
FinishedProductProcessLineCount: number;
lines: ProductProcessInfoResponse[];
}
export interface ProductProcessInfoResponse {
id: number;
operatorId?: number;
operatorName?: string;
equipmentId?: number;
equipmentName?: string;
startTime?: string;
endTime?: string;
status: string;
}
export interface ProductProcessLineQrscanUpadteRequest {
lineId: number;
operatorId?: number;
equipmentId?: number;
}
export interface ProductProcessLineDetailResponse {
id: number,
productProcessId: number,
bomProcessId: number,
operatorId: number,
equipmentType: string,
operatorName: string,
handlerId: number,
seqNo: number,
name: string,
description: string,
equipment: string,
startTime: string,
endTime: string,
defectQty: number,
defectUom: string,
scrapQty: number,
scrapUom: string,
byproductId: number,
byproductName: string,
byproductQty: number,
byproductUom: string | undefined,
}
export const fetchProductProcessLineDetailByJoid = cache(async (joid: number) => {
return serverFetchJson<ProductProcessLineDetailResponse>(
`${BASE_API_URL}/product-process/demo/joid/${joid}`,
{
method: "GET",
}
);
});

// /product-process/Demo/ProcessLine/detail/{lineId}
export const fetchProductProcessLineDetail = cache(async (lineId: number) => {
return serverFetchJson<ProductProcessLineDetailResponse>(
`${BASE_API_URL}/product-process/Demo/ProcessLine/detail/${lineId}`,
{
method: "GET",
}
);
});
export const updateProductProcessLineQrscan = cache(async (request: ProductProcessLineQrscanUpadteRequest) => {
return serverFetchJson<any>(
`${BASE_API_URL}/product-process/Demo/update`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request),
}
);
});
export const fetchAllJoborderProductProcessInfo = cache(async () => {
return serverFetchJson<AllJoborderProductProcessInfoResponse[]>(
`${BASE_API_URL}/product-process/Demo/Process/all`,
{
method: "GET",
next: { tags: ["productProcess"] },
}
);
});
export const updateProductProcessLineQty = async (request: UpdateProductProcessLineQtyRequest) => {
return serverFetchJson<UpdateProductProcessLineQtyResponse>(
`${BASE_API_URL}/product-process/lines/${request.productProcessLineId}/update/qty`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request),
}
);
};
export const startProductProcessLine = async (lineId: number, userId: number) => { export const startProductProcessLine = async (lineId: number, userId: number) => {
return serverFetchJson<ProductProcessLineResponse>( return serverFetchJson<ProductProcessLineResponse>(
`${BASE_API_URL}/product-process/lines/${lineId}/start?userId=${userId}`, `${BASE_API_URL}/product-process/lines/${lineId}/start?userId=${userId}`,


+ 24
- 22
src/app/api/pickOrder/actions.ts View File

@@ -95,12 +95,12 @@ export interface GetPickOrderInfoResponse {
export interface GetPickOrderInfo { export interface GetPickOrderInfo {
id: number; id: number;
code: string; code: string;
consoCode: string | null; // 添加 consoCode 属性
targetDate: string | number[]; // Support both formats
consoCode: string | null; // 添加 consoCode 属性
targetDate: string | number[]; // Support both formats
type: string; type: string;
status: string; status: string;
assignTo: number; assignTo: number;
groupName: string; // Add this field
groupName: string; // Add this field
pickOrderLines: GetPickOrderLineInfo[]; pickOrderLines: GetPickOrderLineInfo[];
} }


@@ -256,16 +256,16 @@ export interface stockReponse{
noLot: boolean; noLot: boolean;
} }
export interface FGPickOrderResponse { export interface FGPickOrderResponse {
// 新增:支持多个 pick orders
// 新增:支持多个 pick orders
doPickOrderId: number; doPickOrderId: number;
pickOrderIds?: number[]; pickOrderIds?: number[];
pickOrderCodes?: string[]; // 改为数组
pickOrderCodes?: string[]; // 改为数组
deliveryOrderIds?: number[]; deliveryOrderIds?: number[];
deliveryNos?: string[]; // 改为数组
deliveryNos?: string[]; // 改为数组
numberOfPickOrders?: number; numberOfPickOrders?: number;
lineCountsPerPickOrder?: number[];// 新增:pick order 数量
lineCountsPerPickOrder?: number[];// 新增:pick order 数量
// 保留原有字段用于向后兼容(显示第一个 pick order)
// 保留原有字段用于向后兼容(显示第一个 pick order)
pickOrderId: number; pickOrderId: number;
pickOrderCode: string; pickOrderCode: string;
pickOrderConsoCode: string; pickOrderConsoCode: string;
@@ -332,17 +332,17 @@ export interface UpdateDoPickOrderHideStatusRequest {
} }
export interface CompletedDoPickOrderResponse { export interface CompletedDoPickOrderResponse {
id: number; id: number;
doPickOrderRecordId: number; // 新增
doPickOrderRecordId: number; // 新增
pickOrderId: number; pickOrderId: number;
pickOrderIds: number[]; // 新增:所有 pick order IDs
pickOrderIds: number[]; // 新增:所有 pick order IDs
pickOrderCode: string; pickOrderCode: string;
pickOrderCodes: string; // 新增:所有 pick order codes (逗号分隔)
pickOrderCodes: string; // 新增:所有 pick order codes (逗号分隔)
pickOrderConsoCode: string; pickOrderConsoCode: string;
pickOrderStatus: string; pickOrderStatus: string;
deliveryOrderId: number; deliveryOrderId: number;
deliveryOrderIds: number[]; // 新增:所有 delivery order IDs
deliveryOrderIds: number[]; // 新增:所有 delivery order IDs
deliveryNo: string; deliveryNo: string;
deliveryNos: string; // 新增:所有 delivery order codes (逗号分隔)
deliveryNos: string; // 新增:所有 delivery order codes (逗号分隔)
deliveryDate: string; deliveryDate: string;
shopId: number; shopId: number;
shopCode: string; shopCode: string;
@@ -352,13 +352,13 @@ export interface CompletedDoPickOrderResponse {
shopPoNo: string; shopPoNo: string;
numberOfCartons: number; numberOfCartons: number;
truckLanceCode: string; truckLanceCode: string;
DepartureTime: string; // 新增
DepartureTime: string; // 新增
storeId: string; storeId: string;
completedDate: string; completedDate: string;
fgPickOrders: FGPickOrderResponse[]; fgPickOrders: FGPickOrderResponse[];
} }


// 新增:搜索参数接口
// 新增:搜索参数接口
export interface CompletedDoPickOrderSearchParams { export interface CompletedDoPickOrderSearchParams {
pickOrderCode?: string; pickOrderCode?: string;
shopName?: string; shopName?: string;
@@ -491,7 +491,8 @@ export async function assignByLane(
userId: number, userId: number,
storeId: string, storeId: string,
truckLanceCode: string, truckLanceCode: string,
truckDepartureTime?: string
truckDepartureTime?: string,
requiredDate?: string
): Promise<any> { ): Promise<any> {
const response = await serverFetchJson( const response = await serverFetchJson(
`${BASE_API_URL}/doPickOrder/assign-by-lane`, `${BASE_API_URL}/doPickOrder/assign-by-lane`,
@@ -505,12 +506,13 @@ export async function assignByLane(
storeId, storeId,
truckLanceCode, truckLanceCode,
truckDepartureTime, truckDepartureTime,
requiredDate,
}), }),
} }
); );
return response; return response;
} }
// 新增:获取已完成的 DO Pick Orders API
// 新增:获取已完成的 DO Pick Orders API
export const fetchCompletedDoPickOrders = async ( export const fetchCompletedDoPickOrders = async (
userId: number, userId: number,
searchParams?: CompletedDoPickOrderSearchParams searchParams?: CompletedDoPickOrderSearchParams
@@ -919,7 +921,7 @@ export const fetchAllPickOrderLotsHierarchical = cache(async (userId: number): P
} }
); );
console.log(" Fetched hierarchical lot details:", data);
console.log(" Fetched hierarchical lot details:", data);
return data; return data;
} catch (error) { } catch (error) {
console.error("❌ Error fetching hierarchical lot details:", error); console.error("❌ Error fetching hierarchical lot details:", error);
@@ -947,7 +949,7 @@ export const fetchLotDetailsByDoPickOrderRecordId = async (doPickOrderRecordId:
} }
); );
console.log(" Fetched hierarchical lot details:", data);
console.log(" Fetched hierarchical lot details:", data);
return data; return data;
} catch (error) { } catch (error) {
console.error("❌ Error fetching lot details:", error); console.error("❌ Error fetching lot details:", error);
@@ -962,7 +964,7 @@ export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Pro
try { try {
console.log("🔍 Fetching all pick order line lot details for userId:", userId); console.log("🔍 Fetching all pick order line lot details for userId:", userId);
// Use the non-auto-assign endpoint
// Use the non-auto-assign endpoint
const data = await serverFetchJson<any[]>( const data = await serverFetchJson<any[]>(
`${BASE_API_URL}/pickOrder/all-lots-with-details-no-auto-assign/${userId}`, `${BASE_API_URL}/pickOrder/all-lots-with-details-no-auto-assign/${userId}`,
{ {
@@ -971,7 +973,7 @@ export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Pro
} }
); );
console.log(" Fetched lot details:", data);
console.log(" Fetched lot details:", data);
return data; return data;
} catch (error) { } catch (error) {
console.error("❌ Error fetching lot details:", error); console.error("❌ Error fetching lot details:", error);
@@ -987,7 +989,7 @@ export const fetchAllPickOrderDetails = cache(async (userId?: number) => {
}; };
} }
// Use the correct endpoint with userId in the path
// Use the correct endpoint with userId in the path
const url = `${BASE_API_URL}/pickOrder/detail-optimized/${userId}`; const url = `${BASE_API_URL}/pickOrder/detail-optimized/${userId}`;
return serverFetchJson<GetPickOrderInfoResponse>( return serverFetchJson<GetPickOrderInfoResponse>(


+ 12
- 12
src/components/DoDetail/DoDetail.tsx View File

@@ -13,7 +13,7 @@ import { releaseDo, assignPickOrderByStore, releaseAssignedPickOrderByStore } fr
import DoInfoCard from "./DoInfoCard"; import DoInfoCard from "./DoInfoCard";
import DoLineTable from "./DoLineTable"; import DoLineTable from "./DoLineTable";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig"; // Import the correct session type
import { SessionWithTokens } from "@/config/authConfig"; // Import the correct session type


type Props = { type Props = {
id?: number; id?: number;
@@ -30,9 +30,9 @@ const DoDetail: React.FC<Props> = ({
const [serverError, setServerError] = useState(""); const [serverError, setServerError] = useState("");
const [successMessage, setSuccessMessage] = useState(""); const [successMessage, setSuccessMessage] = useState("");
const [isAssigning, setIsAssigning] = useState(false); const [isAssigning, setIsAssigning] = useState(false);
const { data: session } = useSession() as { data: SessionWithTokens | null }; // Use correct session type
const { data: session } = useSession() as { data: SessionWithTokens | null }; // Use correct session type
const currentUserId = session?.id ? parseInt(session.id) : undefined; // Get user ID from session.id
const currentUserId = session?.id ? parseInt(session.id) : undefined; // Get user ID from session.id
console.log("🔍 DoSearch - session:", session); console.log("🔍 DoSearch - session:", session);
console.log("🔍 DoSearch - currentUserId:", currentUserId); console.log("🔍 DoSearch - currentUserId:", currentUserId);
const formProps = useForm<DoDetailType>({ const formProps = useForm<DoDetailType>({
@@ -50,7 +50,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
setSuccessMessage("") setSuccessMessage("")
if (id) { if (id) {
// Get current user ID from session
// Get current user ID from session
const currentUserId = session?.id ? parseInt(session.id) : undefined; const currentUserId = session?.id ? parseInt(session.id) : undefined;
if (!currentUserId) { if (!currentUserId) {
@@ -60,7 +60,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
const response = await releaseDo({ const response = await releaseDo({
id: id, id: id,
userId: currentUserId // Pass user ID from session
userId: currentUserId // Pass user ID from session
}) })
if (response) { if (response) {
@@ -74,16 +74,16 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
} finally { } finally {
setIsUploading(false) setIsUploading(false)
} }
}, [id, formProps, t, setIsUploading, session]) // Add session to dependencies
}, [id, formProps, t, setIsUploading, session]) // Add session to dependencies


// UPDATE STORE-BASED ASSIGNMENT HANDLERS
// UPDATE STORE-BASED ASSIGNMENT HANDLERS
const handleAssignByStore = useCallback(async (storeId: string) => { const handleAssignByStore = useCallback(async (storeId: string) => {
try { try {
setIsAssigning(true) setIsAssigning(true)
setServerError("") setServerError("")
setSuccessMessage("") setSuccessMessage("")
// Get current user ID from session
// Get current user ID from session
const currentUserId = session?.id ? parseInt(session.id) : undefined; const currentUserId = session?.id ? parseInt(session.id) : undefined;
if (!currentUserId) { if (!currentUserId) {
@@ -107,7 +107,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
} finally { } finally {
setIsAssigning(false) setIsAssigning(false)
} }
}, [t, session]) // Add session to dependencies
}, [t, session]) // Add session to dependencies


const handleReleaseByStore = useCallback(async (storeId: string) => { const handleReleaseByStore = useCallback(async (storeId: string) => {
try { try {
@@ -115,7 +115,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
setServerError("") setServerError("")
setSuccessMessage("") setSuccessMessage("")
// Get current user ID from session
// Get current user ID from session
const currentUserId = session?.id ? parseInt(session.id) : undefined; const currentUserId = session?.id ? parseInt(session.id) : undefined;
if (!currentUserId) { if (!currentUserId) {
@@ -139,7 +139,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
} finally { } finally {
setIsAssigning(false) setIsAssigning(false)
} }
}, [t, session]) // Add session to dependencies
}, [t, session]) // Add session to dependencies


const onSubmit = useCallback<SubmitHandler<DoDetailType>>(async (data, event) => { const onSubmit = useCallback<SubmitHandler<DoDetailType>>(async (data, event) => {
console.log(data) console.log(data)
@@ -182,7 +182,7 @@ console.log("🔍 DoSearch - currentUserId:", currentUserId);
</Stack> </Stack>
)} )}
{/* ADD STORE-BASED ASSIGNMENT BUTTONS */}
{/* ADD STORE-BASED ASSIGNMENT BUTTONS */}
{ {
formProps.watch("status")?.toLowerCase() === "released" && ( formProps.watch("status")?.toLowerCase() === "released" && (
<Box sx={{ mb: 2 }}> <Box sx={{ mb: 2 }}>


+ 3
- 3
src/components/FinishedGoodSearch/CombinedLotTable.tsx View File

@@ -34,7 +34,7 @@ interface CombinedLotTableProps {
onPageSizeChange: (event: React.ChangeEvent<HTMLInputElement>) => void; onPageSizeChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
} }


// Simple helper function to check if item is completed
// Simple helper function to check if item is completed
const isItemCompleted = (lot: any) => { const isItemCompleted = (lot: any) => {
const actualPickQty = Number(lot.actualPickQty) || 0; const actualPickQty = Number(lot.actualPickQty) || 0;
const requiredQty = Number(lot.requiredQty) || 0; const requiredQty = Number(lot.requiredQty) || 0;
@@ -60,7 +60,7 @@ const CombinedLotTable: React.FC<CombinedLotTableProps> = ({
}) => { }) => {
const { t } = useTranslation("pickOrder"); const { t } = useTranslation("pickOrder");


// Paginated data
// Paginated data
const paginatedLotData = useMemo(() => { const paginatedLotData = useMemo(() => {
const startIndex = paginationController.pageNum * paginationController.pageSize; const startIndex = paginationController.pageNum * paginationController.pageSize;
const endIndex = startIndex + paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize;
@@ -113,7 +113,7 @@ const CombinedLotTable: React.FC<CombinedLotTableProps> = ({
const isCompleted = isItemCompleted(lot); const isCompleted = isItemCompleted(lot);
const isRejected = isItemRejected(lot); const isRejected = isItemRejected(lot);
// Green text color for completed items
// Green text color for completed items
const textColor = isCompleted ? 'success.main' : isRejected ? 'error.main' : 'inherit'; const textColor = isCompleted ? 'success.main' : isRejected ? 'error.main' : 'inherit';
return ( return (


+ 6
- 5
src/components/FinishedGoodSearch/FGPickOrderCard.tsx View File

@@ -47,7 +47,8 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) =>
const handleAssignByLane = useCallback(async ( const handleAssignByLane = useCallback(async (
storeId: string, storeId: string,
truckDepartureTime: string, truckDepartureTime: string,
truckLanceCode: string
truckLanceCode: string,
requiredDate: string
) => { ) => {
if (!currentUserId) { if (!currentUserId) {
console.error("Missing user id in session"); console.error("Missing user id in session");
@@ -56,10 +57,10 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) =>
setIsAssigning(true); setIsAssigning(true);
try { try {
const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime);
const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, requiredDate);
if (res.code === "SUCCESS") { if (res.code === "SUCCESS") {
console.log(" Successfully assigned pick order from lane", truckLanceCode);
console.log(" Successfully assigned pick order from lane", truckLanceCode);
window.dispatchEvent(new CustomEvent('pickOrderAssigned')); window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
loadSummaries(); // 刷新按钮状态 loadSummaries(); // 刷新按钮状态
onPickOrderAssigned?.(); onPickOrderAssigned?.();
@@ -231,7 +232,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) =>
variant="outlined" variant="outlined"
size="medium" size="medium"
disabled={item.lane.unassigned === 0 || isAssigning} disabled={item.lane.unassigned === 0 || isAssigning}
onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode)}
onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)}
sx={{ sx={{
flex: 1, flex: 1,
fontSize: '1.1rem', fontSize: '1.1rem',
@@ -344,7 +345,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) =>
variant="outlined" variant="outlined"
size="medium" size="medium"
disabled={item.lane.unassigned === 0 || isAssigning} disabled={item.lane.unassigned === 0 || isAssigning}
onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode)}
onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)}
sx={{ sx={{
flex: 1, flex: 1,
fontSize: '1.1rem', fontSize: '1.1rem',


+ 6
- 6
src/components/FinishedGoodSearch/FGPickOrderInfoCard.tsx View File

@@ -27,22 +27,22 @@ const FGPickOrderInfoCard: React.FC<Props> = ({ fgOrder, doPickOrderDetail }) =>
<Grid item xs={6}> <Grid item xs={6}>
<TextField <TextField
value={pickOrderCodes || ""} // 显示所有 pick order codes
label={t("Pick Order Code(s)")} // 修改标签
value={pickOrderCodes || ""} // 显示所有 pick order codes
label={t("Pick Order Code(s)")} // 修改标签
fullWidth fullWidth
disabled={true} disabled={true}
multiline={pickOrderCodes.includes(',')} // 如果有多个代码,使用多行
multiline={pickOrderCodes.includes(',')} // 如果有多个代码,使用多行
rows={pickOrderCodes.includes(',') ? 2 : 1} rows={pickOrderCodes.includes(',') ? 2 : 1}
/> />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<TextField <TextField
value={deliveryOrderCodes || ""} // 显示所有 delivery order codes
label={t("Delivery Order Code(s)")} // 修改标签
value={deliveryOrderCodes || ""} // 显示所有 delivery order codes
label={t("Delivery Order Code(s)")} // 修改标签
fullWidth fullWidth
disabled={true} disabled={true}
multiline={deliveryOrderCodes.includes(',')} // 如果有多个代码,使用多行
multiline={deliveryOrderCodes.includes(',')} // 如果有多个代码,使用多行
rows={deliveryOrderCodes.includes(',') ? 2 : 1} rows={deliveryOrderCodes.includes(',') ? 2 : 1}
/> />
</Grid> </Grid>


+ 25
- 16
src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx View File

@@ -56,22 +56,31 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) =>
loadSummaries(); loadSummaries();
}, [loadSummaries]); }, [loadSummaries]);


const handleAssignByLane = useCallback(async (
storeId: string,
truckDepartureTime: string,
truckLanceCode: string
) => {
if (!currentUserId) {
console.error("Missing user id in session");
return;
}
setIsAssigning(true);
try {
const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime);
const handleAssignByLane = useCallback(async (
storeId: string,
truckDepartureTime: string,
truckLanceCode: string,
requiredDate: string

) => {
if (!currentUserId) {
console.error("Missing user id in session");
return;
}
let dateParam: string | undefined;
if (requiredDate === "today") {
dateParam = dayjs().format('YYYY-MM-DD');
} else if (requiredDate === "tomorrow") {
dateParam = dayjs().add(1, 'day').format('YYYY-MM-DD');
} else if (requiredDate === "dayAfterTomorrow") {
dateParam = dayjs().add(2, 'day').format('YYYY-MM-DD');
}
setIsAssigning(true);
try {
const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, dateParam);
if (res.code === "SUCCESS") { if (res.code === "SUCCESS") {
console.log("✅ Successfully assigned pick order from lane", truckLanceCode);
console.log(" Successfully assigned pick order from lane", truckLanceCode);
window.dispatchEvent(new CustomEvent('pickOrderAssigned')); window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
loadSummaries(); // 刷新按钮状态 loadSummaries(); // 刷新按钮状态
onPickOrderAssigned?.(); onPickOrderAssigned?.();
@@ -236,7 +245,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) =>
variant="outlined" variant="outlined"
size="medium" size="medium"
disabled={item.lane.unassigned === 0 || isAssigning} disabled={item.lane.unassigned === 0 || isAssigning}
onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode)}
onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)}
sx={{ sx={{
flex: 1, flex: 1,
fontSize: '1.1rem', fontSize: '1.1rem',
@@ -336,7 +345,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) =>
variant="outlined" variant="outlined"
size="medium" size="medium"
disabled={item.lane.unassigned === 0 || isAssigning} disabled={item.lane.unassigned === 0 || isAssigning}
onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode)}
onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)}
sx={{ sx={{
flex: 1, flex: 1,
fontSize: '1.1rem', fontSize: '1.1rem',


+ 6
- 6
src/components/FinishedGoodSearch/FinishedGoodSearch.tsx View File

@@ -247,7 +247,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
const handleCompletionStatusChange = (event: CustomEvent) => { const handleCompletionStatusChange = (event: CustomEvent) => {
const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail; const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail;
// 修复:根据标签页和事件来源决定是否更新打印按钮状态
// 修复:根据标签页和事件来源决定是否更新打印按钮状态
if (eventTabIndex === undefined || eventTabIndex === tabIndex) { if (eventTabIndex === undefined || eventTabIndex === tabIndex) {
setPrintButtonsEnabled(allLotsCompleted); setPrintButtonsEnabled(allLotsCompleted);
console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted); console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted);
@@ -259,9 +259,9 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
return () => { return () => {
window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener); window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
}; };
}, [tabIndex]); // 添加 tabIndex 依赖
}, [tabIndex]); // 添加 tabIndex 依赖


// 新增:处理标签页切换时的打印按钮状态重置
// 新增:处理标签页切换时的打印按钮状态重置
useEffect(() => { useEffect(() => {
// 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态 // 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态
if (tabIndex === 2) { if (tabIndex === 2) {
@@ -286,7 +286,7 @@ const handleAssignByLane = useCallback(async (
const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime); const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime);
if (res.code === "SUCCESS") { if (res.code === "SUCCESS") {
console.log(" Successfully assigned pick order from lane", truckLanceCode);
console.log(" Successfully assigned pick order from lane", truckLanceCode);
window.dispatchEvent(new CustomEvent('pickOrderAssigned')); window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
loadSummaries(); // 刷新按钮状态 loadSummaries(); // 刷新按钮状态
} else if (res.code === "USER_BUSY") { } else if (res.code === "USER_BUSY") {
@@ -322,7 +322,7 @@ const handleAssignByLane = useCallback(async (
setIsAssigning(false); setIsAssigning(false);
} }
}, [currentUserId, t, loadSummaries]); }, [currentUserId, t, loadSummaries]);
// Manual assignment handler - uses the action function
// Manual assignment handler - uses the action function
*/ */
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => { (_e, newValue) => {
@@ -607,7 +607,7 @@ const handleAssignByLane = useCallback(async (
</Grid> </Grid>
</Box> </Box>


{/* Tabs section - Move the click handler here */}
{/* Tabs section - Move the click handler here */}
<Box sx={{ <Box sx={{
borderBottom: '1px solid #e0e0e0' borderBottom: '1px solid #e0e0e0'
}}> }}>


+ 53
- 53
src/components/FinishedGoodSearch/GoodPickExecution.tsx View File

@@ -26,7 +26,7 @@ import {
updateStockOutLineStatus, updateStockOutLineStatus,
createStockOutLine, createStockOutLine,
recordPickExecutionIssue, recordPickExecutionIssue,
fetchFGPickOrdersByUserId, // Add this import
fetchFGPickOrdersByUserId, // Add this import
FGPickOrderResponse, FGPickOrderResponse,
autoAssignAndReleasePickOrder, autoAssignAndReleasePickOrder,
AutoAssignReleaseResponse, AutoAssignReleaseResponse,
@@ -59,13 +59,13 @@ interface Props {
onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void; onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void;
} }


// QR Code Modal Component (from LotTable)
// QR Code Modal Component (from LotTable)
const QrCodeModal: React.FC<{ const QrCodeModal: React.FC<{
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
lot: any | null; lot: any | null;
onQrCodeSubmit: (lotNo: string) => void; onQrCodeSubmit: (lotNo: string) => void;
combinedLotData: any[]; // Add this prop
combinedLotData: any[]; // Add this prop
}> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => { }> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => {
const { t } = useTranslation("pickOrder"); const { t } = useTranslation("pickOrder");
const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
@@ -105,7 +105,7 @@ const QrCodeModal: React.FC<{
setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number'); setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number');
if (stockInLineInfo.lotNo === lot.lotNo) { if (stockInLineInfo.lotNo === lot.lotNo) {
console.log(` QR Code verified for lot: ${lot.lotNo}`);
console.log(` QR Code verified for lot: ${lot.lotNo}`);
setQrScanSuccess(true); setQrScanSuccess(true);
onQrCodeSubmit(lot.lotNo); onQrCodeSubmit(lot.lotNo);
onClose(); onClose();
@@ -297,7 +297,7 @@ const QrCodeModal: React.FC<{


{qrScanSuccess && ( {qrScanSuccess && (
<Typography variant="caption" color="success" display="block"> <Typography variant="caption" color="success" display="block">
{t("Verified successfully!")}
{t("Verified successfully!")}
</Typography> </Typography>
)} )}
</Box> </Box>
@@ -348,11 +348,11 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false);
const formProps = useForm(); const formProps = useForm();
const errors = formProps.formState.errors; const errors = formProps.formState.errors;


// Add QR modal states
// Add QR modal states
const [qrModalOpen, setQrModalOpen] = useState(false); const [qrModalOpen, setQrModalOpen] = useState(false);
const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null); const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null);


// Add GoodPickExecutionForm states
// Add GoodPickExecutionForm states
const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null); const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null);
const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]); const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]);
@@ -389,14 +389,14 @@ const fetchFgPickOrdersData = useCallback(async () => {
} }
}, [currentUserId, selectedPickOrderId]); }, [currentUserId, selectedPickOrderId]);
// 简化:移除复杂的 useEffect 依赖
// 简化:移除复杂的 useEffect 依赖
useEffect(() => { useEffect(() => {
if (currentUserId) { if (currentUserId) {
fetchFgPickOrdersData(); fetchFgPickOrdersData();
} }
}, [currentUserId, fetchFgPickOrdersData]); }, [currentUserId, fetchFgPickOrdersData]);


// Handle QR code button click
// Handle QR code button click
const handleQrCodeClick = (pickOrderId: number) => { const handleQrCodeClick = (pickOrderId: number) => {
console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); console.log(`QR Code clicked for pick order ID: ${pickOrderId}`);
// TODO: Implement QR code functionality // TODO: Implement QR code functionality
@@ -424,22 +424,22 @@ const fetchFgPickOrdersData = useCallback(async () => {
return; return;
} }
// Use the non-auto-assign endpoint - this only fetches existing data
// Use the non-auto-assign endpoint - this only fetches existing data
const allLotDetails = await fetchALLPickOrderLineLotDetails(userIdToUse); const allLotDetails = await fetchALLPickOrderLineLotDetails(userIdToUse);
console.log(" All combined lot details:", allLotDetails);
console.log(" All combined lot details:", allLotDetails);
setCombinedLotData(allLotDetails); setCombinedLotData(allLotDetails);
setOriginalCombinedData(allLotDetails); setOriginalCombinedData(allLotDetails);
// 计算完成状态并发送事件
// 计算完成状态并发送事件
const allCompleted = allLotDetails.length > 0 && allLotDetails.every(lot => const allCompleted = allLotDetails.length > 0 && allLotDetails.every(lot =>
lot.processingStatus === 'completed' lot.processingStatus === 'completed'
); );
// 发送完成状态事件,包含标签页信息
// 发送完成状态事件,包含标签页信息
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: { detail: {
allLotsCompleted: allCompleted, allLotsCompleted: allCompleted,
tabIndex: 0 // 明确指定这是来自标签页 0 的事件
tabIndex: 0 // 明确指定这是来自标签页 0 的事件
} }
})); }));
@@ -448,7 +448,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
setCombinedLotData([]); setCombinedLotData([]);
setOriginalCombinedData([]); setOriginalCombinedData([]);
// 如果加载失败,禁用打印按钮
// 如果加载失败,禁用打印按钮
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: { detail: {
allLotsCompleted: false, allLotsCompleted: false,
@@ -460,18 +460,18 @@ const fetchFgPickOrdersData = useCallback(async () => {
} }
}, [currentUserId, combinedLotData]); }, [currentUserId, combinedLotData]);


// Only fetch existing data when session is ready, no auto-assignment
// Only fetch existing data when session is ready, no auto-assignment
useEffect(() => { useEffect(() => {
if (session && currentUserId && !initializationRef.current) { if (session && currentUserId && !initializationRef.current) {
console.log(" Session loaded, initializing pick order...");
console.log(" Session loaded, initializing pick order...");
initializationRef.current = true; initializationRef.current = true;
// Only fetch existing data, no auto-assignment
// Only fetch existing data, no auto-assignment
fetchAllCombinedLotData(); fetchAllCombinedLotData();
} }
}, [session, currentUserId, fetchAllCombinedLotData]); }, [session, currentUserId, fetchAllCombinedLotData]);


// Add event listener for manual assignment
// Add event listener for manual assignment
useEffect(() => { useEffect(() => {
const handlePickOrderAssigned = () => { const handlePickOrderAssigned = () => {
console.log("🔄 Pick order assigned event received, refreshing data..."); console.log("🔄 Pick order assigned event received, refreshing data...");
@@ -485,12 +485,12 @@ const fetchFgPickOrdersData = useCallback(async () => {
}; };
}, [fetchAllCombinedLotData]); }, [fetchAllCombinedLotData]);


// Handle QR code submission for matched lot (external scanning)
// Handle QR code submission for matched lot (external scanning)
// Handle QR code submission for matched lot (external scanning)
// Handle QR code submission for matched lot (external scanning)
const handleQrCodeSubmit = useCallback(async (lotNo: string) => { const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
console.log(` Processing QR Code for lot: ${lotNo}`);
console.log(` Processing QR Code for lot: ${lotNo}`);
// Use current data without refreshing to avoid infinite loop
// Use current data without refreshing to avoid infinite loop
const currentLotData = combinedLotData; const currentLotData = combinedLotData;
console.log(`🔍 Available lots:`, currentLotData.map(lot => lot.lotNo)); console.log(`🔍 Available lots:`, currentLotData.map(lot => lot.lotNo));
@@ -506,7 +506,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
return; return;
} }
console.log(` Found ${matchingLots.length} matching lots:`, matchingLots);
console.log(` Found ${matchingLots.length} matching lots:`, matchingLots);
setQrScanError(false); setQrScanError(false);
try { try {
@@ -518,7 +518,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
console.log(`🔄 Processing pick order line ${matchingLot.pickOrderLineId} for lot ${lotNo}`); console.log(`🔄 Processing pick order line ${matchingLot.pickOrderLineId} for lot ${lotNo}`);
if (matchingLot.stockOutLineId) { if (matchingLot.stockOutLineId) {
console.log(` Stock out line already exists for line ${matchingLot.pickOrderLineId}`);
console.log(` Stock out line already exists for line ${matchingLot.pickOrderLineId}`);
existsCount++; existsCount++;
} else { } else {
const stockOutLineData: CreateStockOutLine = { const stockOutLineData: CreateStockOutLine = {
@@ -533,10 +533,10 @@ const fetchFgPickOrdersData = useCallback(async () => {
console.log(`Create stock out line result for line ${matchingLot.pickOrderLineId}:`, result); console.log(`Create stock out line result for line ${matchingLot.pickOrderLineId}:`, result);
if (result && result.code === "EXISTS") { if (result && result.code === "EXISTS") {
console.log(` Stock out line already exists for line ${matchingLot.pickOrderLineId}`);
console.log(` Stock out line already exists for line ${matchingLot.pickOrderLineId}`);
existsCount++; existsCount++;
} else if (result && result.code === "SUCCESS") { } else if (result && result.code === "SUCCESS") {
console.log(` Stock out line created successfully for line ${matchingLot.pickOrderLineId}`);
console.log(` Stock out line created successfully for line ${matchingLot.pickOrderLineId}`);
successCount++; successCount++;
} else { } else {
console.error(`❌ Failed to create stock out line for line ${matchingLot.pickOrderLineId}:`, result); console.error(`❌ Failed to create stock out line for line ${matchingLot.pickOrderLineId}:`, result);
@@ -545,16 +545,16 @@ const fetchFgPickOrdersData = useCallback(async () => {
} }
} }
// Always refresh data after processing (success or failure)
// Always refresh data after processing (success or failure)
console.log("🔄 Refreshing data after QR code processing..."); console.log("🔄 Refreshing data after QR code processing...");
await fetchAllCombinedLotData(); await fetchAllCombinedLotData();
if (successCount > 0 || existsCount > 0) { if (successCount > 0 || existsCount > 0) {
console.log(` QR Code processing completed: ${successCount} created, ${existsCount} already existed`);
console.log(` QR Code processing completed: ${successCount} created, ${existsCount} already existed`);
setQrScanSuccess(true); setQrScanSuccess(true);
setQrScanInput(''); // Clear input after successful processing setQrScanInput(''); // Clear input after successful processing
// Clear success state after a delay
// Clear success state after a delay
setTimeout(() => { setTimeout(() => {
setQrScanSuccess(false); setQrScanSuccess(false);
}, 2000); }, 2000);
@@ -563,7 +563,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
setQrScanError(true); setQrScanError(true);
setQrScanSuccess(false); setQrScanSuccess(false);
// Clear error state after a delay
// Clear error state after a delay
setTimeout(() => { setTimeout(() => {
setQrScanError(false); setQrScanError(false);
}, 3000); }, 3000);
@@ -573,10 +573,10 @@ const fetchFgPickOrdersData = useCallback(async () => {
setQrScanError(true); setQrScanError(true);
setQrScanSuccess(false); setQrScanSuccess(false);
// Still refresh data even on error
// Still refresh data even on error
await fetchAllCombinedLotData(); await fetchAllCombinedLotData();
// Clear error state after a delay
// Clear error state after a delay
setTimeout(() => { setTimeout(() => {
setQrScanError(false); setQrScanError(false);
}, 3000); }, 3000);
@@ -589,17 +589,17 @@ const fetchFgPickOrdersData = useCallback(async () => {
} }
}, [qrScanInput, handleQrCodeSubmit]); }, [qrScanInput, handleQrCodeSubmit]);


// Handle QR code submission from modal (internal scanning)
// Handle QR code submission from modal (internal scanning)
const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => { const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => {
if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) { if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) {
console.log(` QR Code verified for lot: ${lotNo}`);
console.log(` QR Code verified for lot: ${lotNo}`);
const requiredQty = selectedLotForQr.requiredQty; const requiredQty = selectedLotForQr.requiredQty;
const lotId = selectedLotForQr.lotId; const lotId = selectedLotForQr.lotId;
// Create stock out line // Create stock out line
const stockOutLineData: CreateStockOutLine = { const stockOutLineData: CreateStockOutLine = {
consoCode: selectedLotForQr.pickOrderConsoCode, // Use pickOrderConsoCode instead of pickOrderCode
consoCode: selectedLotForQr.pickOrderConsoCode, // Use pickOrderConsoCode instead of pickOrderCode
pickOrderLineId: selectedLotForQr.pickOrderLineId, pickOrderLineId: selectedLotForQr.pickOrderLineId,
inventoryLotLineId: selectedLotForQr.lotId, inventoryLotLineId: selectedLotForQr.lotId,
qty: 0.0 qty: 0.0
@@ -620,7 +620,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
...prev, ...prev,
[lotKey]: requiredQty [lotKey]: requiredQty
})); }));
console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
}, 500); }, 500);
// Refresh data // Refresh data
@@ -631,7 +631,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
} }
}, [selectedLotForQr, fetchAllCombinedLotData]); }, [selectedLotForQr, fetchAllCombinedLotData]);


// Outside QR scanning - process QR codes from outside the page automatically
// Outside QR scanning - process QR codes from outside the page automatically
useEffect(() => { useEffect(() => {
if (qrValues.length > 0 && combinedLotData.length > 0) { if (qrValues.length > 0 && combinedLotData.length > 0) {
const latestQr = qrValues[qrValues.length - 1]; const latestQr = qrValues[qrValues.length - 1];
@@ -707,7 +707,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) { if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) {
console.log("Found completed pick orders, auto-assigning next..."); console.log("Found completed pick orders, auto-assigning next...");
// 移除前端的自动分配逻辑,因为后端已经处理了
// 移除前端的自动分配逻辑,因为后端已经处理了
// await handleAutoAssignAndRelease(); // 删除这个函数 // await handleAutoAssignAndRelease(); // 删除这个函数
} }
} catch (error) { } catch (error) {
@@ -715,7 +715,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
} }
}, [currentUserId]); }, [currentUserId]);


// Handle submit pick quantity
// Handle submit pick quantity
const handleSubmitPickQty = useCallback(async (lot: any) => { const handleSubmitPickQty = useCallback(async (lot: any) => {
const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`;
const newQty = pickQtyData[lotKey] || 0; const newQty = pickQtyData[lotKey] || 0;
@@ -759,14 +759,14 @@ const fetchFgPickOrdersData = useCallback(async () => {
}); });
} }
// FIXED: Use the proper API function instead of direct fetch
// FIXED: Use the proper API function instead of direct fetch
if (newStatus === 'completed' && lot.pickOrderConsoCode) { if (newStatus === 'completed' && lot.pickOrderConsoCode) {
console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
try { try {
// Use the imported API function instead of direct fetch
// Use the imported API function instead of direct fetch
const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
console.log(` Pick order completion check result:`, completionResponse);
console.log(` Pick order completion check result:`, completionResponse);
if (completionResponse.code === "SUCCESS") { if (completionResponse.code === "SUCCESS") {
console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`); console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`);
@@ -792,7 +792,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
} }
}, [pickQtyData, fetchAllCombinedLotData, checkAndAutoAssignNext]); }, [pickQtyData, fetchAllCombinedLotData, checkAndAutoAssignNext]);


// Handle reject lot
// Handle reject lot
const handleRejectLot = useCallback(async (lot: any) => { const handleRejectLot = useCallback(async (lot: any) => {
if (!lot.stockOutLineId) { if (!lot.stockOutLineId) {
console.error("No stock out line found for this lot"); console.error("No stock out line found for this lot");
@@ -818,7 +818,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
} }
}, [fetchAllCombinedLotData, checkAndAutoAssignNext]); }, [fetchAllCombinedLotData, checkAndAutoAssignNext]);


// Handle pick execution form
// Handle pick execution form
const handlePickExecutionForm = useCallback((lot: any) => { const handlePickExecutionForm = useCallback((lot: any) => {
console.log("=== Pick Execution Form ==="); console.log("=== Pick Execution Form ===");
console.log("Lot data:", lot); console.log("Lot data:", lot);
@@ -847,7 +847,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
console.log("Pick execution issue recorded:", result); console.log("Pick execution issue recorded:", result);
if (result && result.code === "SUCCESS") { if (result && result.code === "SUCCESS") {
console.log(" Pick execution issue recorded successfully");
console.log(" Pick execution issue recorded successfully");
} else { } else {
console.error("❌ Failed to record pick execution issue:", result); console.error("❌ Failed to record pick execution issue:", result);
} }
@@ -861,7 +861,7 @@ const fetchFgPickOrdersData = useCallback(async () => {
} }
}, [fetchAllCombinedLotData]); }, [fetchAllCombinedLotData]);


// Calculate remaining required quantity
// Calculate remaining required quantity
const calculateRemainingRequiredQty = useCallback((lot: any) => { const calculateRemainingRequiredQty = useCallback((lot: any) => {
const requiredQty = lot.requiredQty || 0; const requiredQty = lot.requiredQty || 0;
const stockOutLineQty = lot.stockOutLineQty || 0; const stockOutLineQty = lot.stockOutLineQty || 0;
@@ -942,7 +942,7 @@ const fetchFgPickOrdersData = useCallback(async () => {


// Pagination data with sorting by routerIndex // Pagination data with sorting by routerIndex
const paginatedData = useMemo(() => { const paginatedData = useMemo(() => {
// Sort by routerIndex first, then by other criteria
// Sort by routerIndex first, then by other criteria
const sortedData = [...combinedLotData].sort((a, b) => { const sortedData = [...combinedLotData].sort((a, b) => {
const aIndex = a.routerIndex || 0; const aIndex = a.routerIndex || 0;
const bIndex = b.routerIndex || 0; const bIndex = b.routerIndex || 0;
@@ -970,14 +970,14 @@ const fetchFgPickOrdersData = useCallback(async () => {


return ( return (
<FormProvider {...formProps}> <FormProvider {...formProps}>
{/* 修复:改进条件渲染逻辑 */}
{/* 修复:改进条件渲染逻辑 */}
{combinedDataLoading || fgPickOrdersLoading ? ( {combinedDataLoading || fgPickOrdersLoading ? (
// 数据加载中,显示加载指示器
// 数据加载中,显示加载指示器
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}> <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress /> <CircularProgress />
</Box> </Box>
) : fgPickOrders.length === 0 ? ( ) : fgPickOrders.length === 0 ? (
// 没有活动订单,显示楼层选择面板
// 没有活动订单,显示楼层选择面板
<FinishedGoodFloorLanePanel <FinishedGoodFloorLanePanel
onPickOrderAssigned={() => { onPickOrderAssigned={() => {
if (currentUserId) { if (currentUserId) {
@@ -987,7 +987,7 @@ return (
}} }}
/> />
) : ( ) : (
// 有活动订单,显示 FG 订单信息
// 有活动订单,显示 FG 订单信息
<Box> <Box>
{fgPickOrders.map((fgOrder) => ( {fgPickOrders.map((fgOrder) => (
<Box key={fgOrder.pickOrderId} sx={{ mb: 2 }}> <Box key={fgOrder.pickOrderId} sx={{ mb: 2 }}>


+ 13
- 13
src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx View File

@@ -53,7 +53,7 @@ interface PickExecutionFormProps {
selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null; selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null;
pickOrderId?: number; pickOrderId?: number;
pickOrderCreateDate: any; pickOrderCreateDate: any;
// Remove these props since we're not handling normal cases
// Remove these props since we're not handling normal cases
// onNormalPickSubmit?: (lineId: number, lotId: number, qty: number) => Promise<void>; // onNormalPickSubmit?: (lineId: number, lotId: number, qty: number) => Promise<void>;
// selectedRowId?: number | null; // selectedRowId?: number | null;
} }
@@ -75,7 +75,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
selectedPickOrderLine, selectedPickOrderLine,
pickOrderId, pickOrderId,
pickOrderCreateDate, pickOrderCreateDate,
// Remove these props
// Remove these props
// onNormalPickSubmit, // onNormalPickSubmit,
// selectedRowId, // selectedRowId,
}) => { }) => {
@@ -86,11 +86,11 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
const [handlers, setHandlers] = useState<Array<{ id: number; name: string }>>([]); const [handlers, setHandlers] = useState<Array<{ id: number; name: string }>>([]);
// 计算剩余可用数量 // 计算剩余可用数量
const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => {
// 直接使用 availableQty,因为 API 没有返回 inQty 和 outQty
// 直接使用 availableQty,因为 API 没有返回 inQty 和 outQty
return lot.availableQty || 0; return lot.availableQty || 0;
}, []); }, []);
const calculateRequiredQty = useCallback((lot: LotPickData) => { const calculateRequiredQty = useCallback((lot: LotPickData) => {
// Use the original required quantity, not subtracting actualPickQty
// Use the original required quantity, not subtracting actualPickQty
// The actualPickQty in the form should be independent of the database value // The actualPickQty in the form should be independent of the database value
return lot.requiredQty || 0; return lot.requiredQty || 0;
}, []); }, []);
@@ -175,7 +175,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
} }
}, [errors]); }, [errors]);


// Update form validation to require either missQty > 0 OR badItemQty > 0
// Update form validation to require either missQty > 0 OR badItemQty > 0
const validateForm = (): boolean => { const validateForm = (): boolean => {
const newErrors: FormErrors = {}; const newErrors: FormErrors = {};
const req = selectedLot?.requiredQty || 0; const req = selectedLot?.requiredQty || 0;
@@ -200,10 +200,10 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
}; };


const handleSubmit = async () => { const handleSubmit = async () => {
// ✅ 先验证表单
// First validate the form
if (!validateForm()) { if (!validateForm()) {
console.error('Form validation failed:', errors); console.error('Form validation failed:', errors);
return; // ✅ 阻止提交,显示验证错误
return; // Prevent submission, show validation errors
} }
if (!formData.pickOrderId) { if (!formData.pickOrderId) {
@@ -214,10 +214,10 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
setLoading(true); setLoading(true);
try { try {
await onSubmit(formData as PickExecutionIssueData); await onSubmit(formData as PickExecutionIssueData);
// ✅ 成功时会自动关闭(由 onClose 处理)
// Automatically closed when successful (handled by onClose)
} catch (error: any) { } catch (error: any) {
console.error('Error submitting pick execution issue:', error); console.error('Error submitting pick execution issue:', error);
// ✅ 显示错误消息(可以通过 props 或 state 传递错误消息到父组件)
// Show error message (can be passed to parent component via props or state)
// 或者在这里显示 toast/alert // 或者在这里显示 toast/alert
alert(t('Failed to submit issue. Please try again.') + (error.message ? `: ${error.message}` : '')); alert(t('Failed to submit issue. Please try again.') + (error.message ? `: ${error.message}` : ''));
} finally { } finally {
@@ -241,11 +241,11 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
return ( return (
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth> <Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
<DialogTitle> <DialogTitle>
{t('Pick Execution Issue Form')} {/* Always show issue form title */}
{t('Pick Execution Issue Form')} {/* Always show issue form title */}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<Box sx={{ mt: 2 }}> <Box sx={{ mt: 2 }}>
{/* Add instruction text */}
{/* Add instruction text */}
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12}> <Grid item xs={12}>
<Box sx={{ p: 2, backgroundColor: '#fff3cd', borderRadius: 1, mb: 2 }}> <Box sx={{ p: 2, backgroundColor: '#fff3cd', borderRadius: 1, mb: 2 }}>
@@ -255,7 +255,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
</Box> </Box>
</Grid> </Grid>
{/* Keep the existing form fields */}
{/* Keep the existing form fields */}
<Grid item xs={6}> <Grid item xs={6}>
<TextField <TextField
fullWidth fullWidth
@@ -317,7 +317,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
/> />
</Grid> </Grid>
{/* Show issue description and handler fields when bad items > 0 */}
{/* Show issue description and handler fields when bad items > 0 */}
{(formData.badItemQty && formData.badItemQty > 0) ? ( {(formData.badItemQty && formData.badItemQty > 0) ? (
<> <>
<Grid item xs={12}> <Grid item xs={12}>


+ 30
- 30
src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx View File

@@ -72,7 +72,7 @@ interface Props {
} }




// 新增:Pick Order 数据接口
// 新增:Pick Order 数据接口
interface PickOrderData { interface PickOrderData {
pickOrderId: number; pickOrderId: number;
pickOrderCode: string; pickOrderCode: string;
@@ -89,20 +89,20 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
const currentUserId = session?.id ? parseInt(session.id) : undefined; const currentUserId = session?.id ? parseInt(session.id) : undefined;
// 新增:已完成 DO Pick Orders 状态
// 新增:已完成 DO Pick Orders 状态
const [completedDoPickOrders, setCompletedDoPickOrders] = useState<CompletedDoPickOrderResponse[]>([]); const [completedDoPickOrders, setCompletedDoPickOrders] = useState<CompletedDoPickOrderResponse[]>([]);
const [completedDoPickOrdersLoading, setCompletedDoPickOrdersLoading] = useState(false); const [completedDoPickOrdersLoading, setCompletedDoPickOrdersLoading] = useState(false);
// 新增:详情视图状态
// 新增:详情视图状态
const [selectedDoPickOrder, setSelectedDoPickOrder] = useState<CompletedDoPickOrderResponse | null>(null); const [selectedDoPickOrder, setSelectedDoPickOrder] = useState<CompletedDoPickOrderResponse | null>(null);
const [showDetailView, setShowDetailView] = useState(false); const [showDetailView, setShowDetailView] = useState(false);
const [detailLotData, setDetailLotData] = useState<any[]>([]); const [detailLotData, setDetailLotData] = useState<any[]>([]);
// 新增:搜索状态
// 新增:搜索状态
const [searchQuery, setSearchQuery] = useState<Record<string, any>>({}); const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
const [filteredDoPickOrders, setFilteredDoPickOrders] = useState<CompletedDoPickOrderResponse[]>([]); const [filteredDoPickOrders, setFilteredDoPickOrders] = useState<CompletedDoPickOrderResponse[]>([]);
// 新增:分页状态
// 新增:分页状态
const [paginationController, setPaginationController] = useState({ const [paginationController, setPaginationController] = useState({
pageNum: 0, pageNum: 0,
pageSize: 10, pageSize: 10,
@@ -307,7 +307,7 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
} }
}, [t]); }, [t]);


// 修改:使用新的 API 获取已完成的 DO Pick Orders
// 修改:使用新的 API 获取已完成的 DO Pick Orders
const fetchCompletedDoPickOrdersData = useCallback(async (searchParams?: CompletedDoPickOrderSearchParams) => { const fetchCompletedDoPickOrdersData = useCallback(async (searchParams?: CompletedDoPickOrderSearchParams) => {
if (!currentUserId) return; if (!currentUserId) return;
@@ -319,7 +319,7 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
setCompletedDoPickOrders(completedDoPickOrders); setCompletedDoPickOrders(completedDoPickOrders);
setFilteredDoPickOrders(completedDoPickOrders); setFilteredDoPickOrders(completedDoPickOrders);
console.log(" Fetched completed DO pick orders:", completedDoPickOrders);
console.log(" Fetched completed DO pick orders:", completedDoPickOrders);
} catch (error) { } catch (error) {
console.error("❌ Error fetching completed DO pick orders:", error); console.error("❌ Error fetching completed DO pick orders:", error);
setCompletedDoPickOrders([]); setCompletedDoPickOrders([]);
@@ -329,14 +329,14 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
} }
}, [currentUserId]); }, [currentUserId]);


// 初始化时获取数据
// 初始化时获取数据
useEffect(() => { useEffect(() => {
if (currentUserId) { if (currentUserId) {
fetchCompletedDoPickOrdersData(); fetchCompletedDoPickOrdersData();
} }
}, [currentUserId, fetchCompletedDoPickOrdersData]); }, [currentUserId, fetchCompletedDoPickOrdersData]);


// 修改:搜索功能使用新的 API
// 修改:搜索功能使用新的 API
const handleSearch = useCallback((query: Record<string, any>) => { const handleSearch = useCallback((query: Record<string, any>) => {
setSearchQuery({ ...query }); setSearchQuery({ ...query });
console.log("Search query:", query); console.log("Search query:", query);
@@ -352,13 +352,13 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
fetchCompletedDoPickOrdersData(searchParams); fetchCompletedDoPickOrdersData(searchParams);
}, [fetchCompletedDoPickOrdersData]); }, [fetchCompletedDoPickOrdersData]);


// 修复:重命名函数避免重复声明
// 修复:重命名函数避免重复声明
const handleSearchReset = useCallback(() => { const handleSearchReset = useCallback(() => {
setSearchQuery({}); setSearchQuery({});
fetchCompletedDoPickOrdersData(); // 重新获取所有数据 fetchCompletedDoPickOrdersData(); // 重新获取所有数据
}, [fetchCompletedDoPickOrdersData]); }, [fetchCompletedDoPickOrdersData]);


// 分页功能
// 分页功能
const handlePageChange = useCallback((event: unknown, newPage: number) => { const handlePageChange = useCallback((event: unknown, newPage: number) => {
setPaginationController(prev => ({ setPaginationController(prev => ({
...prev, ...prev,
@@ -374,14 +374,14 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
}); });
}, []); }, []);


// 分页数据
// 分页数据
const paginatedData = useMemo(() => { const paginatedData = useMemo(() => {
const startIndex = paginationController.pageNum * paginationController.pageSize; const startIndex = paginationController.pageNum * paginationController.pageSize;
const endIndex = startIndex + paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize;
return filteredDoPickOrders.slice(startIndex, endIndex); return filteredDoPickOrders.slice(startIndex, endIndex);
}, [filteredDoPickOrders, paginationController]); }, [filteredDoPickOrders, paginationController]);


// 搜索条件
// 搜索条件
const searchCriteria: Criterion<any>[] = [ const searchCriteria: Criterion<any>[] = [
{ {
label: t("Pick Order Code"), label: t("Pick Order Code"),
@@ -405,11 +405,11 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
setShowDetailView(true); setShowDetailView(true);
try { try {
// 使用 doPickOrderRecordId 而不是 pickOrderId
// 使用 doPickOrderRecordId 而不是 pickOrderId
const hierarchicalData = await fetchLotDetailsByDoPickOrderRecordId(doPickOrder.doPickOrderRecordId); const hierarchicalData = await fetchLotDetailsByDoPickOrderRecordId(doPickOrder.doPickOrderRecordId);
console.log(" Loaded hierarchical lot data:", hierarchicalData);
console.log(" Loaded hierarchical lot data:", hierarchicalData);
// 转换为平铺格式
// 转换为平铺格式
const flatLotData: any[] = []; const flatLotData: any[] = [];


if (hierarchicalData.pickOrders && hierarchicalData.pickOrders.length > 0) { if (hierarchicalData.pickOrders && hierarchicalData.pickOrders.length > 0) {
@@ -465,7 +465,7 @@ if (hierarchicalData.pickOrders && hierarchicalData.pickOrders.length > 0) {
} }
}); });
} else if (lineStockouts.length > 0) { } else if (lineStockouts.length > 0) {
// lots 为空但有 stockouts(如「雞絲碗仔翅」),也要显示
// lots 为空但有 stockouts(如「雞絲碗仔翅」),也要显示
lineStockouts.forEach((so: any) => { lineStockouts.forEach((so: any) => {
flatLotData.push({ flatLotData.push({
pickOrderCode: po.pickOrderCode, pickOrderCode: po.pickOrderCode,
@@ -488,7 +488,7 @@ if (hierarchicalData.pickOrders && hierarchicalData.pickOrders.length > 0) {


setDetailLotData(flatLotData); setDetailLotData(flatLotData);
// 计算完成状态
// 计算完成状态
const allCompleted = flatLotData.length > 0 && flatLotData.every(lot => const allCompleted = flatLotData.length > 0 && flatLotData.every(lot =>
lot.processingStatus === 'completed' lot.processingStatus === 'completed'
); );
@@ -500,7 +500,7 @@ setDetailLotData(flatLotData);
} }
})); }));
} catch (error) { // 添加 catch 块
} catch (error) { // 添加 catch 块
console.error("❌ Error loading detail lot data:", error); console.error("❌ Error loading detail lot data:", error);
setDetailLotData([]); setDetailLotData([]);
@@ -514,13 +514,13 @@ setDetailLotData(flatLotData);
}, []); }, []);




// 返回列表视图
// 返回列表视图
const handleBackToList = useCallback(() => { const handleBackToList = useCallback(() => {
setShowDetailView(false); setShowDetailView(false);
setSelectedDoPickOrder(null); setSelectedDoPickOrder(null);
setDetailLotData([]); setDetailLotData([]);
// 返回列表时禁用打印按钮
// 返回列表时禁用打印按钮
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: { detail: {
allLotsCompleted: false, allLotsCompleted: false,
@@ -530,8 +530,8 @@ setDetailLotData(flatLotData);
}, []); }, []);




// 如果显示详情视图,渲染类似 GoodPickExecution 的表格
// 如果显示详情视图,渲染层级结构
// 如果显示详情视图,渲染类似 GoodPickExecution 的表格
// 如果显示详情视图,渲染层级结构
if (showDetailView && selectedDoPickOrder) { if (showDetailView && selectedDoPickOrder) {
return ( return (
<FormProvider {...formProps}> <FormProvider {...formProps}>
@@ -567,7 +567,7 @@ if (showDetailView && selectedDoPickOrder) {
</Stack> </Stack>
</Paper> </Paper>


{/* 添加:多个 Pick Orders 信息(如果有) */}
{/* 添加:多个 Pick Orders 信息(如果有) */}
{selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 1 && ( {selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 1 && (
<Paper sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5' }}> <Paper sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5' }}>
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}> <Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>
@@ -586,7 +586,7 @@ if (showDetailView && selectedDoPickOrder) {
</Paper> </Paper>
)} )}


{/* 数据检查 */}
{/* 数据检查 */}
{detailLotData.length === 0 ? ( {detailLotData.length === 0 ? (
<Box sx={{ p: 3, textAlign: 'center' }}> <Box sx={{ p: 3, textAlign: 'center' }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
@@ -594,16 +594,16 @@ if (showDetailView && selectedDoPickOrder) {
</Typography> </Typography>
</Box> </Box>
) : ( ) : (
/* 按 Pick Order 分组显示 */
/* 按 Pick Order 分组显示 */
<Stack spacing={2}> <Stack spacing={2}>
{/* 按 pickOrderCode 分组 */}
{/* 按 pickOrderCode 分组 */}
{Object.entries( {Object.entries(
detailLotData.reduce((acc: any, lot: any) => { detailLotData.reduce((acc: any, lot: any) => {
const key = lot.pickOrderCode || 'Unknown'; const key = lot.pickOrderCode || 'Unknown';
if (!acc[key]) { if (!acc[key]) {
acc[key] = { acc[key] = {
lots: [], lots: [],
deliveryOrderCode: lot.deliveryOrderCode || 'N/A' // 保存对应的 deliveryOrderCode
deliveryOrderCode: lot.deliveryOrderCode || 'N/A' // 保存对应的 deliveryOrderCode
}; };
} }
acc[key].lots.push(lot); acc[key].lots.push(lot);
@@ -615,7 +615,7 @@ if (showDetailView && selectedDoPickOrder) {
<Typography variant="subtitle1" fontWeight="bold"> <Typography variant="subtitle1" fontWeight="bold">
{t("Pick Order")}: {pickOrderCode} ({data.lots.length} {t("items")}) {t("Pick Order")}: {pickOrderCode} ({data.lots.length} {t("items")})
{" | "} {" | "}
{t("Delivery Order")}: {data.deliveryOrderCode} {/* 使用保存的 deliveryOrderCode */}
{t("Delivery Order")}: {data.deliveryOrderCode} {/* 使用保存的 deliveryOrderCode */}
</Typography> </Typography>
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
@@ -665,7 +665,7 @@ if (showDetailView && selectedDoPickOrder) {
); );
} }


// 默认列表视图
// 默认列表视图
return ( return (
<FormProvider {...formProps}> <FormProvider {...formProps}>
<Box> <Box>


+ 131
- 131
src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx View File

@@ -32,7 +32,7 @@ import {
createStockOutLine, createStockOutLine,
updateStockOutLine, updateStockOutLine,
recordPickExecutionIssue, recordPickExecutionIssue,
fetchFGPickOrders, // Add this import
fetchFGPickOrders, // Add this import
FGPickOrderResponse, FGPickOrderResponse,
stockReponse, stockReponse,
PickExecutionIssueData, PickExecutionIssueData,
@@ -42,8 +42,8 @@ import {
checkAndCompletePickOrderByConsoCode, checkAndCompletePickOrderByConsoCode,
updateSuggestedLotLineId, updateSuggestedLotLineId,
confirmLotSubstitution, confirmLotSubstitution,
fetchDoPickOrderDetail, // 必须添加
DoPickOrderDetail, // 必须添加
fetchDoPickOrderDetail, // 必须添加
DoPickOrderDetail, // 必须添加
fetchFGPickOrdersByUserId fetchFGPickOrdersByUserId
} from "@/app/api/pickOrder/actions"; } from "@/app/api/pickOrder/actions";


@@ -70,13 +70,13 @@ interface Props {
filterArgs: Record<string, any>; filterArgs: Record<string, any>;
} }


// QR Code Modal Component (from LotTable)
// QR Code Modal Component (from LotTable)
const QrCodeModal: React.FC<{ const QrCodeModal: React.FC<{
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
lot: any | null; lot: any | null;
onQrCodeSubmit: (lotNo: string) => void; onQrCodeSubmit: (lotNo: string) => void;
combinedLotData: any[]; // Add this prop
combinedLotData: any[]; // Add this prop
}> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => { }> = ({ open, onClose, lot, onQrCodeSubmit, combinedLotData }) => {
const { t } = useTranslation("pickOrder"); const { t } = useTranslation("pickOrder");
const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
@@ -116,7 +116,7 @@ const QrCodeModal: React.FC<{
setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number'); setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number');
if (stockInLineInfo.lotNo === lot.lotNo) { if (stockInLineInfo.lotNo === lot.lotNo) {
console.log(` QR Code verified for lot: ${lot.lotNo}`);
console.log(` QR Code verified for lot: ${lot.lotNo}`);
setQrScanSuccess(true); setQrScanSuccess(true);
onQrCodeSubmit(lot.lotNo); onQrCodeSubmit(lot.lotNo);
onClose(); onClose();
@@ -308,7 +308,7 @@ const QrCodeModal: React.FC<{


{qrScanSuccess && ( {qrScanSuccess && (
<Typography variant="caption" color="success" display="block"> <Typography variant="caption" color="success" display="block">
{t("Verified successfully!")}
{t("Verified successfully!")}
</Typography> </Typography>
)} )}
</Box> </Box>
@@ -359,20 +359,20 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false);
const formProps = useForm(); const formProps = useForm();
const errors = formProps.formState.errors; const errors = formProps.formState.errors;


// Add QR modal states
// Add QR modal states
const [qrModalOpen, setQrModalOpen] = useState(false); const [qrModalOpen, setQrModalOpen] = useState(false);
const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null); const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null);
const [lotConfirmationOpen, setLotConfirmationOpen] = useState(false); const [lotConfirmationOpen, setLotConfirmationOpen] = useState(false);
const [expectedLotData, setExpectedLotData] = useState<any>(null); const [expectedLotData, setExpectedLotData] = useState<any>(null);
const [scannedLotData, setScannedLotData] = useState<any>(null); const [scannedLotData, setScannedLotData] = useState<any>(null);
const [isConfirmingLot, setIsConfirmingLot] = useState(false); const [isConfirmingLot, setIsConfirmingLot] = useState(false);
// Add GoodPickExecutionForm states
// Add GoodPickExecutionForm states
const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null); const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null);
const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]); const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]);


const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false); const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false);
// Add these missing state variables after line 352
// Add these missing state variables after line 352
const [isManualScanning, setIsManualScanning] = useState<boolean>(false); const [isManualScanning, setIsManualScanning] = useState<boolean>(false);
const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set()); const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
const [lastProcessedQr, setLastProcessedQr] = useState<string>(''); const [lastProcessedQr, setLastProcessedQr] = useState<string>('');
@@ -381,7 +381,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
// Handle QR code button click
// Handle QR code button click
const handleQrCodeClick = (pickOrderId: number) => { const handleQrCodeClick = (pickOrderId: number) => {
console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); console.log(`QR Code clicked for pick order ID: ${pickOrderId}`);
// TODO: Implement QR code functionality // TODO: Implement QR code functionality
@@ -435,11 +435,11 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
return; return;
} }
// 获取新结构的层级数据
// 获取新结构的层级数据
const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse); const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse);
console.log(" Hierarchical data (new structure):", hierarchicalData);
console.log(" Hierarchical data (new structure):", hierarchicalData);
// 检查数据结构
// 检查数据结构
if (!hierarchicalData.fgInfo || !hierarchicalData.pickOrders || hierarchicalData.pickOrders.length === 0) { if (!hierarchicalData.fgInfo || !hierarchicalData.pickOrders || hierarchicalData.pickOrders.length === 0) {
console.warn("⚠️ No FG info or pick orders found"); console.warn("⚠️ No FG info or pick orders found");
setCombinedLotData([]); setCombinedLotData([]);
@@ -448,10 +448,10 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
return; return;
} }
// 使用合并后的 pick order 对象(现在只有一个对象,包含所有数据)
// 使用合并后的 pick order 对象(现在只有一个对象,包含所有数据)
const mergedPickOrder = hierarchicalData.pickOrders[0]; const mergedPickOrder = hierarchicalData.pickOrders[0];
// 设置 FG info 到 fgPickOrders(用于显示 FG 信息卡片)
// 设置 FG info 到 fgPickOrders(用于显示 FG 信息卡片)
// 修改第 478-509 行的 fgOrder 构建逻辑: // 修改第 478-509 行的 fgOrder 构建逻辑:


const fgOrder: FGPickOrderResponse = { const fgOrder: FGPickOrderResponse = {
@@ -464,7 +464,7 @@ const fgOrder: FGPickOrderResponse = {
DepartureTime: hierarchicalData.fgInfo.departureTime, DepartureTime: hierarchicalData.fgInfo.departureTime,
shopAddress: "", shopAddress: "",
pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
// 兼容字段
// 兼容字段
pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0, pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0,
pickOrderConsoCode: mergedPickOrder.consoCode || "", pickOrderConsoCode: mergedPickOrder.consoCode || "",
pickOrderTargetDate: mergedPickOrder.targetDate || "", pickOrderTargetDate: mergedPickOrder.targetDate || "",
@@ -477,16 +477,16 @@ const fgOrder: FGPickOrderResponse = {
numberOfCartons: mergedPickOrder.pickOrderLines?.length || 0, numberOfCartons: mergedPickOrder.pickOrderLines?.length || 0,
qrCodeData: hierarchicalData.fgInfo.doPickOrderId, qrCodeData: hierarchicalData.fgInfo.doPickOrderId,
// 新增:多个 pick orders 信息 - 保持数组格式,不要 join
// 新增:多个 pick orders 信息 - 保持数组格式,不要 join
numberOfPickOrders: mergedPickOrder.pickOrderIds?.length || 0, numberOfPickOrders: mergedPickOrder.pickOrderIds?.length || 0,
pickOrderIds: mergedPickOrder.pickOrderIds || [], pickOrderIds: mergedPickOrder.pickOrderIds || [],
pickOrderCodes: Array.isArray(mergedPickOrder.pickOrderCodes) pickOrderCodes: Array.isArray(mergedPickOrder.pickOrderCodes)
? mergedPickOrder.pickOrderCodes ? mergedPickOrder.pickOrderCodes
: [], // 改:保持数组
: [], // 改:保持数组
deliveryOrderIds: mergedPickOrder.doOrderIds || [], deliveryOrderIds: mergedPickOrder.doOrderIds || [],
deliveryNos: Array.isArray(mergedPickOrder.deliveryOrderCodes) deliveryNos: Array.isArray(mergedPickOrder.deliveryOrderCodes)
? mergedPickOrder.deliveryOrderCodes ? mergedPickOrder.deliveryOrderCodes
: [], // 改:保持数组
: [], // 改:保持数组
lineCountsPerPickOrder: Array.isArray(mergedPickOrder.lineCountsPerPickOrder) lineCountsPerPickOrder: Array.isArray(mergedPickOrder.lineCountsPerPickOrder)
? mergedPickOrder.lineCountsPerPickOrder ? mergedPickOrder.lineCountsPerPickOrder
: [] : []
@@ -499,38 +499,38 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
// ❌ 移除:不需要 doPickOrderDetail 和 switcher 逻辑 // ❌ 移除:不需要 doPickOrderDetail 和 switcher 逻辑
// if (hierarchicalData.pickOrders.length > 1) { ... } // if (hierarchicalData.pickOrders.length > 1) { ... }
// 直接使用合并后的 pickOrderLines
// 直接使用合并后的 pickOrderLines
console.log("🎯 Displaying merged pick order lines"); console.log("🎯 Displaying merged pick order lines");
// 将层级数据转换为平铺格式(用于表格显示)
// 将层级数据转换为平铺格式(用于表格显示)
const flatLotData: any[] = []; const flatLotData: any[] = [];
mergedPickOrder.pickOrderLines.forEach((line: any) => { mergedPickOrder.pickOrderLines.forEach((line: any) => {
if (line.lots && line.lots.length > 0) { if (line.lots && line.lots.length > 0) {
// 修复:先对 lots 按 lotId 去重并合并 requiredQty
// 修复:先对 lots 按 lotId 去重并合并 requiredQty
const lotMap = new Map<number, any>(); const lotMap = new Map<number, any>();
line.lots.forEach((lot: any) => { line.lots.forEach((lot: any) => {
const lotId = lot.id; const lotId = lot.id;
if (lotMap.has(lotId)) { if (lotMap.has(lotId)) {
// 如果已存在,合并 requiredQty
// 如果已存在,合并 requiredQty
const existingLot = lotMap.get(lotId); const existingLot = lotMap.get(lotId);
existingLot.requiredQty = (existingLot.requiredQty || 0) + (lot.requiredQty || 0); existingLot.requiredQty = (existingLot.requiredQty || 0) + (lot.requiredQty || 0);
// 保留其他字段(使用第一个遇到的 lot 的字段)
// 保留其他字段(使用第一个遇到的 lot 的字段)
} else { } else {
// 首次遇到,添加到 map
// 首次遇到,添加到 map
lotMap.set(lotId, { ...lot }); lotMap.set(lotId, { ...lot });
} }
}); });
// 遍历去重后的 lots
// 遍历去重后的 lots
lotMap.forEach((lot: any) => { lotMap.forEach((lot: any) => {
flatLotData.push({ flatLotData.push({
// 使用合并后的数据
// 使用合并后的数据
pickOrderConsoCode: mergedPickOrder.consoCode, pickOrderConsoCode: mergedPickOrder.consoCode,
pickOrderTargetDate: mergedPickOrder.targetDate, pickOrderTargetDate: mergedPickOrder.targetDate,
pickOrderStatus: mergedPickOrder.status, pickOrderStatus: mergedPickOrder.status,
pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0, // 使用第一个 pickOrderId
pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0, // 使用第一个 pickOrderId
pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
pickOrderLineId: line.id, pickOrderLineId: line.id,
pickOrderLineRequiredQty: line.requiredQty, pickOrderLineRequiredQty: line.requiredQty,
@@ -548,7 +548,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
location: lot.location, location: lot.location,
stockUnit: lot.stockUnit, stockUnit: lot.stockUnit,
availableQty: lot.availableQty, availableQty: lot.availableQty,
requiredQty: lot.requiredQty, // 使用合并后的 requiredQty
requiredQty: lot.requiredQty, // 使用合并后的 requiredQty
actualPickQty: lot.actualPickQty, actualPickQty: lot.actualPickQty,
inQty: lot.inQty, inQty: lot.inQty,
outQty: lot.outQty, outQty: lot.outQty,
@@ -569,16 +569,16 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
}); });
}); });
} else { } else {
// 没有 lots 的情况(null stock)- 从 stockouts 数组中获取 id
// 没有 lots 的情况(null stock)- 从 stockouts 数组中获取 id
const firstStockout = line.stockouts && line.stockouts.length > 0 const firstStockout = line.stockouts && line.stockouts.length > 0
? line.stockouts[0] ? line.stockouts[0]
: null; : null;
flatLotData.push({ flatLotData.push({
pickOrderConsoCode: mergedPickOrder.consoCodes?.[0] || "", // 修复:consoCodes 是数组
pickOrderConsoCode: mergedPickOrder.consoCodes?.[0] || "", // 修复:consoCodes 是数组
pickOrderTargetDate: mergedPickOrder.targetDate, pickOrderTargetDate: mergedPickOrder.targetDate,
pickOrderStatus: mergedPickOrder.status, pickOrderStatus: mergedPickOrder.status,
pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0, // 使用第一个 pickOrderId
pickOrderId: mergedPickOrder.pickOrderIds?.[0] || 0, // 使用第一个 pickOrderId
pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "",
pickOrderLineId: line.id, pickOrderLineId: line.id,
pickOrderLineRequiredQty: line.requiredQty, pickOrderLineRequiredQty: line.requiredQty,
@@ -590,7 +590,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
uomDesc: line.item.uomDesc, uomDesc: line.item.uomDesc,
uomShortDesc: line.item.uomShortDesc, uomShortDesc: line.item.uomShortDesc,
// Null stock 字段 - 从 stockouts 数组中获取
// Null stock 字段 - 从 stockouts 数组中获取
lotId: firstStockout?.lotId || null, lotId: firstStockout?.lotId || null,
lotNo: firstStockout?.lotNo || null, lotNo: firstStockout?.lotNo || null,
expiryDate: null, expiryDate: null,
@@ -606,7 +606,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
lotAvailability: 'insufficient_stock', lotAvailability: 'insufficient_stock',
processingStatus: firstStockout?.status || 'pending', processingStatus: firstStockout?.status || 'pending',
suggestedPickLotId: null, suggestedPickLotId: null,
stockOutLineId: firstStockout?.id || null, // 使用 stockouts 数组中的 id
stockOutLineId: firstStockout?.id || null, // 使用 stockouts 数组中的 id
stockOutLineStatus: firstStockout?.status || null, stockOutLineStatus: firstStockout?.status || null,
stockOutLineQty: firstStockout?.qty || 0, stockOutLineQty: firstStockout?.qty || 0,
@@ -619,7 +619,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
} }
}); });


console.log(" Transformed flat lot data:", flatLotData);
console.log(" Transformed flat lot data:", flatLotData);
console.log("🔍 Total items (including null stock):", flatLotData.length); console.log("🔍 Total items (including null stock):", flatLotData.length);
setCombinedLotData(flatLotData); setCombinedLotData(flatLotData);
@@ -635,25 +635,25 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
setCombinedDataLoading(false); setCombinedDataLoading(false);
} }
}, [currentUserId, checkAllLotsCompleted]); // ❌ 移除 selectedPickOrderId 依赖 }, [currentUserId, checkAllLotsCompleted]); // ❌ 移除 selectedPickOrderId 依赖
// Add effect to check completion when lot data changes
// Add effect to check completion when lot data changes
useEffect(() => { useEffect(() => {
if (combinedLotData.length > 0) { if (combinedLotData.length > 0) {
checkAllLotsCompleted(combinedLotData); checkAllLotsCompleted(combinedLotData);
} }
}, [combinedLotData, checkAllLotsCompleted]); }, [combinedLotData, checkAllLotsCompleted]);


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


// Expose completion status to parent component
// Expose completion status to parent component
useEffect(() => { useEffect(() => {
// Dispatch custom event with completion status // Dispatch custom event with completion status
const event = new CustomEvent('pickOrderCompletionStatus', { const event = new CustomEvent('pickOrderCompletionStatus', {
detail: { detail: {
allLotsCompleted, allLotsCompleted,
tabIndex: 1 // 明确指定这是来自标签页 1 的事件
tabIndex: 1 // 明确指定这是来自标签页 1 的事件
} }
}); });
window.dispatchEvent(event); window.dispatchEvent(event);
@@ -708,22 +708,22 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
} }
}, [expectedLotData, scannedLotData, selectedLotForQr, fetchAllCombinedLotData]); }, [expectedLotData, scannedLotData, selectedLotForQr, fetchAllCombinedLotData]);
const handleQrCodeSubmit = useCallback(async (lotNo: string) => { const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
console.log(` Processing QR Code for lot: ${lotNo}`);
console.log(` Processing QR Code for lot: ${lotNo}`);
// 检查 lotNo 是否为 null 或 undefined(包括字符串 "null")
// 检查 lotNo 是否为 null 或 undefined(包括字符串 "null")
if (!lotNo || lotNo === 'null' || lotNo.trim() === '') { if (!lotNo || lotNo === 'null' || lotNo.trim() === '') {
console.error("❌ Invalid lotNo: null, undefined, or empty"); console.error("❌ Invalid lotNo: null, undefined, or empty");
return; return;
} }
// Use current data without refreshing to avoid infinite loop
// Use current data without refreshing to avoid infinite loop
const currentLotData = combinedLotData; const currentLotData = combinedLotData;
console.log(` Available lots:`, currentLotData.map(lot => lot.lotNo)); console.log(` Available lots:`, currentLotData.map(lot => lot.lotNo));
// 修复:在比较前确保 lotNo 不为 null
// 修复:在比较前确保 lotNo 不为 null
const lotNoLower = lotNo.toLowerCase(); const lotNoLower = lotNo.toLowerCase();
const matchingLots = currentLotData.filter(lot => { const matchingLots = currentLotData.filter(lot => {
if (!lot.lotNo) return false; // 跳过 null lotNo
if (!lot.lotNo) return false; // 跳过 null lotNo
return lot.lotNo === lotNo || lot.lotNo.toLowerCase() === lotNoLower; return lot.lotNo === lotNo || lot.lotNo.toLowerCase() === lotNoLower;
}); });
@@ -736,7 +736,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
return; return;
} }
console.log(` Found ${matchingLots.length} matching lots:`, matchingLots);
console.log(` Found ${matchingLots.length} matching lots:`, matchingLots);
setQrScanError(false); setQrScanError(false);
try { try {
@@ -811,20 +811,20 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
} }
} }
// FIXED: Set refresh flag before refreshing data
// FIXED: Set refresh flag before refreshing data
setIsRefreshingData(true); setIsRefreshingData(true);
console.log("🔄 Refreshing data after QR code processing..."); console.log("🔄 Refreshing data after QR code processing...");
await fetchAllCombinedLotData(); await fetchAllCombinedLotData();
if (successCount > 0) { if (successCount > 0) {
console.log(` QR Code processing completed: ${successCount} updated/created`);
console.log(` QR Code processing completed: ${successCount} updated/created`);
setQrScanSuccess(true); setQrScanSuccess(true);
setQrScanError(false); setQrScanError(false);
setQrScanInput(''); // Clear input after successful processing setQrScanInput(''); // Clear input after successful processing
//setIsManualScanning(false); //setIsManualScanning(false);
// stopScan(); // stopScan();
// resetScan(); // resetScan();
// Clear success state after a delay
// Clear success state after a delay
//setTimeout(() => { //setTimeout(() => {
//setQrScanSuccess(false); //setQrScanSuccess(false);
@@ -834,7 +834,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
setQrScanError(true); setQrScanError(true);
setQrScanSuccess(false); setQrScanSuccess(false);
// Clear error state after a delay
// Clear error state after a delay
// setTimeout(() => { // setTimeout(() => {
// setQrScanError(false); // setQrScanError(false);
//}, 3000); //}, 3000);
@@ -844,16 +844,16 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
setQrScanError(true); setQrScanError(true);
setQrScanSuccess(false); setQrScanSuccess(false);
// Still refresh data even on error
// Still refresh data even on error
setIsRefreshingData(true); setIsRefreshingData(true);
await fetchAllCombinedLotData(); await fetchAllCombinedLotData();
// Clear error state after a delay
// Clear error state after a delay
setTimeout(() => { setTimeout(() => {
setQrScanError(false); setQrScanError(false);
}, 3000); }, 3000);
} finally { } finally {
// Clear refresh flag after a short delay
// Clear refresh flag after a short delay
setTimeout(() => { setTimeout(() => {
setIsRefreshingData(false); setIsRefreshingData(false);
}, 1000); }, 1000);
@@ -914,7 +914,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
return; return;
} }
// FIXED: Find the ACTIVE suggested lot (not rejected lots)
// FIXED: Find the ACTIVE suggested lot (not rejected lots)
const activeSuggestedLots = sameItemLotsInExpected.filter(lot => const activeSuggestedLots = sameItemLotsInExpected.filter(lot =>
lot.lotAvailability !== 'rejected' && lot.lotAvailability !== 'rejected' &&
lot.stockOutLineStatus !== 'rejected' && lot.stockOutLineStatus !== 'rejected' &&
@@ -942,7 +942,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
} }
// Case 2: Item matches but lot number differs -> open confirmation modal // Case 2: Item matches but lot number differs -> open confirmation modal
// FIXED: Use the first ACTIVE suggested lot, not just any lot
// FIXED: Use the first ACTIVE suggested lot, not just any lot
const expectedLot = activeSuggestedLots[0]; const expectedLot = activeSuggestedLots[0];
if (!expectedLot) { if (!expectedLot) {
console.error("Could not determine expected lot for confirmation"); console.error("Could not determine expected lot for confirmation");
@@ -951,7 +951,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
return; return;
} }
// Check if the expected lot is already the scanned lot (after substitution)
// Check if the expected lot is already the scanned lot (after substitution)
if (expectedLot.lotNo === scanned?.lotNo) { if (expectedLot.lotNo === scanned?.lotNo) {
console.log(`Lot already substituted, proceeding with ${scanned.lotNo}`); console.log(`Lot already substituted, proceeding with ${scanned.lotNo}`);
handleQrCodeSubmit(scanned.lotNo); handleQrCodeSubmit(scanned.lotNo);
@@ -981,8 +981,8 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos);
return; return;
} }
}, [combinedLotData, handleQrCodeSubmit, handleLotMismatch]); }, [combinedLotData, handleQrCodeSubmit, handleLotMismatch]);
// Update the outside QR scanning effect to use enhanced processing
// Update the outside QR scanning effect to use enhanced processing
// Update the outside QR scanning effect to use enhanced processing
// Update the outside QR scanning effect to use enhanced processing
useEffect(() => { useEffect(() => {
if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) {
return; return;
@@ -1003,18 +1003,18 @@ useEffect(() => {
processOutsideQrCode(latestQr); processOutsideQrCode(latestQr);
} }
}, [qrValues, isManualScanning, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode, combinedLotData]); }, [qrValues, isManualScanning, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode, combinedLotData]);
// Only fetch existing data when session is ready, no auto-assignment
// Only fetch existing data when session is ready, no auto-assignment
useEffect(() => { useEffect(() => {
if (session && currentUserId && !initializationRef.current) { if (session && currentUserId && !initializationRef.current) {
console.log(" Session loaded, initializing pick order...");
console.log(" Session loaded, initializing pick order...");
initializationRef.current = true; initializationRef.current = true;
// Only fetch existing data, no auto-assignment
// Only fetch existing data, no auto-assignment
fetchAllCombinedLotData(); fetchAllCombinedLotData();
} }
}, [session, currentUserId, fetchAllCombinedLotData]); }, [session, currentUserId, fetchAllCombinedLotData]);


// Add event listener for manual assignment
// Add event listener for manual assignment
useEffect(() => { useEffect(() => {
const handlePickOrderAssigned = () => { const handlePickOrderAssigned = () => {
console.log("🔄 Pick order assigned event received, refreshing data..."); console.log("🔄 Pick order assigned event received, refreshing data...");
@@ -1036,10 +1036,10 @@ useEffect(() => {
} }
}, [qrScanInput, handleQrCodeSubmit]); }, [qrScanInput, handleQrCodeSubmit]);


// Handle QR code submission from modal (internal scanning)
// Handle QR code submission from modal (internal scanning)
const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => { const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => {
if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) { if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) {
console.log(` QR Code verified for lot: ${lotNo}`);
console.log(` QR Code verified for lot: ${lotNo}`);
const requiredQty = selectedLotForQr.requiredQty; const requiredQty = selectedLotForQr.requiredQty;
const lotId = selectedLotForQr.lotId; const lotId = selectedLotForQr.lotId;
@@ -1068,7 +1068,7 @@ useEffect(() => {
...prev, ...prev,
[lotKey]: requiredQty [lotKey]: requiredQty
})); }));
console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
}, 500); }, 500);
// Refresh data // Refresh data
@@ -1117,7 +1117,7 @@ useEffect(() => {
if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) { if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) {
console.log("Found completed pick orders, auto-assigning next..."); console.log("Found completed pick orders, auto-assigning next...");
// 移除前端的自动分配逻辑,因为后端已经处理了
// 移除前端的自动分配逻辑,因为后端已经处理了
// await handleAutoAssignAndRelease(); // 删除这个函数 // await handleAutoAssignAndRelease(); // 删除这个函数
} }
} catch (error) { } catch (error) {
@@ -1125,7 +1125,7 @@ useEffect(() => {
} }
}, [currentUserId]); }, [currentUserId]);


// Handle submit pick quantity
// Handle submit pick quantity
const handleSubmitPickQty = useCallback(async (lot: any) => { const handleSubmitPickQty = useCallback(async (lot: any) => {
const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`;
const newQty = pickQtyData[lotKey] || 0; const newQty = pickQtyData[lotKey] || 0;
@@ -1136,11 +1136,11 @@ useEffect(() => {
} }
try { try {
// FIXED: Calculate cumulative quantity correctly
// FIXED: Calculate cumulative quantity correctly
const currentActualPickQty = lot.actualPickQty || 0; const currentActualPickQty = lot.actualPickQty || 0;
const cumulativeQty = currentActualPickQty + newQty; const cumulativeQty = currentActualPickQty + newQty;
// FIXED: Determine status based on cumulative quantity vs required quantity
// FIXED: Determine status based on cumulative quantity vs required quantity
let newStatus = 'partially_completed'; let newStatus = 'partially_completed';
if (cumulativeQty >= lot.requiredQty) { if (cumulativeQty >= lot.requiredQty) {
@@ -1163,7 +1163,7 @@ useEffect(() => {
await updateStockOutLineStatus({ await updateStockOutLineStatus({
id: lot.stockOutLineId, id: lot.stockOutLineId,
status: newStatus, status: newStatus,
qty: cumulativeQty // Use cumulative quantity
qty: cumulativeQty // Use cumulative quantity
}); });
if (newQty > 0) { if (newQty > 0) {
@@ -1175,13 +1175,13 @@ useEffect(() => {
}); });
} }
// Check if pick order is completed when lot status becomes 'completed'
// Check if pick order is completed when lot status becomes 'completed'
if (newStatus === 'completed' && lot.pickOrderConsoCode) { if (newStatus === 'completed' && lot.pickOrderConsoCode) {
console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
try { try {
const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
console.log(` Pick order completion check result:`, completionResponse);
console.log(` Pick order completion check result:`, completionResponse);
if (completionResponse.code === "SUCCESS") { if (completionResponse.code === "SUCCESS") {
console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`); console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`);
@@ -1207,7 +1207,7 @@ useEffect(() => {
} }
}, [pickQtyData, fetchAllCombinedLotData, checkAndAutoAssignNext]); }, [pickQtyData, fetchAllCombinedLotData, checkAndAutoAssignNext]);


// Handle reject lot
// Handle reject lot
const handleRejectLot = useCallback(async (lot: any) => { const handleRejectLot = useCallback(async (lot: any) => {
if (!lot.stockOutLineId) { if (!lot.stockOutLineId) {
console.error("No stock out line found for this lot"); console.error("No stock out line found for this lot");
@@ -1233,7 +1233,7 @@ useEffect(() => {
} }
}, [fetchAllCombinedLotData, checkAndAutoAssignNext]); }, [fetchAllCombinedLotData, checkAndAutoAssignNext]);


// Handle pick execution form
// Handle pick execution form
const handlePickExecutionForm = useCallback((lot: any) => { const handlePickExecutionForm = useCallback((lot: any) => {
console.log("=== Pick Execution Form ==="); console.log("=== Pick Execution Form ===");
console.log("Lot data:", lot); console.log("Lot data:", lot);
@@ -1264,7 +1264,7 @@ useEffect(() => {
console.log("Pick execution issue recorded:", result); console.log("Pick execution issue recorded:", result);
if (result && result.code === "SUCCESS") { if (result && result.code === "SUCCESS") {
console.log(" Pick execution issue recorded successfully");
console.log(" Pick execution issue recorded successfully");
} else { } else {
console.error("❌ Failed to record pick execution issue:", result); console.error("❌ Failed to record pick execution issue:", result);
} }
@@ -1285,7 +1285,7 @@ useEffect(() => {
} }
}, [fetchAllCombinedLotData]); }, [fetchAllCombinedLotData]);


// Calculate remaining required quantity
// Calculate remaining required quantity
const calculateRemainingRequiredQty = useCallback((lot: any) => { const calculateRemainingRequiredQty = useCallback((lot: any) => {
const requiredQty = lot.requiredQty || 0; const requiredQty = lot.requiredQty || 0;
const stockOutLineQty = lot.stockOutLineQty || 0; const stockOutLineQty = lot.stockOutLineQty || 0;
@@ -1369,7 +1369,7 @@ useEffect(() => {
const paginatedData = useMemo(() => { const paginatedData = useMemo(() => {
const startIndex = paginationController.pageNum * paginationController.pageSize; const startIndex = paginationController.pageNum * paginationController.pageSize;
const endIndex = startIndex + paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize;
return combinedLotData.slice(startIndex, endIndex); // No sorting needed
return combinedLotData.slice(startIndex, endIndex); // No sorting needed
}, [combinedLotData, paginationController]); }, [combinedLotData, paginationController]);
const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: number) => { const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: number) => {
if (!lot.stockOutLineId) { if (!lot.stockOutLineId) {
@@ -1378,11 +1378,11 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
} }
try { try {
// FIXED: Calculate cumulative quantity correctly
// FIXED: Calculate cumulative quantity correctly
const currentActualPickQty = lot.actualPickQty || 0; const currentActualPickQty = lot.actualPickQty || 0;
const cumulativeQty = currentActualPickQty + submitQty; const cumulativeQty = currentActualPickQty + submitQty;
// FIXED: Determine status based on cumulative quantity vs required quantity
// FIXED: Determine status based on cumulative quantity vs required quantity
let newStatus = 'partially_completed'; let newStatus = 'partially_completed';
if (cumulativeQty >= lot.requiredQty) { if (cumulativeQty >= lot.requiredQty) {
@@ -1405,7 +1405,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
await updateStockOutLineStatus({ await updateStockOutLineStatus({
id: lot.stockOutLineId, id: lot.stockOutLineId,
status: newStatus, status: newStatus,
qty: cumulativeQty // Use cumulative quantity
qty: cumulativeQty // Use cumulative quantity
}); });
if (submitQty > 0) { if (submitQty > 0) {
@@ -1417,13 +1417,13 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
}); });
} }
// Check if pick order is completed when lot status becomes 'completed'
// Check if pick order is completed when lot status becomes 'completed'
if (newStatus === 'completed' && lot.pickOrderConsoCode) { if (newStatus === 'completed' && lot.pickOrderConsoCode) {
console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
try { try {
const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
console.log(` Pick order completion check result:`, completionResponse);
console.log(` Pick order completion check result:`, completionResponse);
if (completionResponse.code === "SUCCESS") { if (completionResponse.code === "SUCCESS") {
console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`); console.log(`�� Pick order ${lot.pickOrderConsoCode} completed successfully!`);
@@ -1450,7 +1450,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
}, [fetchAllCombinedLotData, checkAndAutoAssignNext]); }, [fetchAllCombinedLotData, checkAndAutoAssignNext]);




// Add these functions after line 395
// Add these functions after line 395
const handleStartScan = useCallback(() => { const handleStartScan = useCallback(() => {
console.log(" Starting manual QR scan..."); console.log(" Starting manual QR scan...");
setIsManualScanning(true); setIsManualScanning(true);
@@ -1468,7 +1468,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
console.log("🔍 Switching to pick order:", pickOrderId); console.log("🔍 Switching to pick order:", pickOrderId);
setSelectedPickOrderId(pickOrderId); setSelectedPickOrderId(pickOrderId);
// 强制刷新数据,确保显示正确的 pick order 数据
// 强制刷新数据,确保显示正确的 pick order 数据
await fetchAllCombinedLotData(currentUserId, pickOrderId); await fetchAllCombinedLotData(currentUserId, pickOrderId);
} catch (error) { } catch (error) {
console.error("Error switching pick order:", error); console.error("Error switching pick order:", error);
@@ -1487,7 +1487,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
}, [stopScan, resetScan]); }, [stopScan, resetScan]);
// ... existing code around line 1469 ... // ... existing code around line 1469 ...
const handlelotnull = useCallback(async (lot: any) => { const handlelotnull = useCallback(async (lot: any) => {
// 优先使用 stockouts 中的 id,如果没有则使用 stockOutLineId
// 优先使用 stockouts 中的 id,如果没有则使用 stockOutLineId
const stockOutLineId = lot.stockOutLineId; const stockOutLineId = lot.stockOutLineId;
if (!stockOutLineId) { if (!stockOutLineId) {
@@ -1496,14 +1496,14 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
} }
try { try {
// Step 1: Update stock out line status
// Step 1: Update stock out line status
await updateStockOutLineStatus({ await updateStockOutLineStatus({
id: stockOutLineId, id: stockOutLineId,
status: 'completed', status: 'completed',
qty: 0 qty: 0
}); });
// Step 2: Create pick execution issue for no-lot case
// Step 2: Create pick execution issue for no-lot case
// Get pick order ID from fgPickOrders or use 0 if not available // Get pick order ID from fgPickOrders or use 0 if not available
const pickOrderId = lot.pickOrderId || fgPickOrders[0]?.pickOrderId || 0; const pickOrderId = lot.pickOrderId || fgPickOrders[0]?.pickOrderId || 0;
const pickOrderCode = lot.pickOrderCode || fgPickOrders[0]?.pickOrderCode || lot.pickOrderConsoCode || ''; const pickOrderCode = lot.pickOrderCode || fgPickOrders[0]?.pickOrderCode || lot.pickOrderConsoCode || '';
@@ -1512,18 +1512,18 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
type: "Do", // Delivery Order type type: "Do", // Delivery Order type
pickOrderId: pickOrderId, pickOrderId: pickOrderId,
pickOrderCode: pickOrderCode, pickOrderCode: pickOrderCode,
pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), // Use dayjs format
pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), // Use dayjs format
pickExecutionDate: dayjs().format('YYYY-MM-DD'), pickExecutionDate: dayjs().format('YYYY-MM-DD'),
pickOrderLineId: lot.pickOrderLineId, pickOrderLineId: lot.pickOrderLineId,
itemId: lot.itemId, itemId: lot.itemId,
itemCode: lot.itemCode || '', itemCode: lot.itemCode || '',
itemDescription: lot.itemName || '', itemDescription: lot.itemName || '',
lotId: null, // No lot available
lotNo: null, // No lot number
lotId: null, // No lot available
lotNo: null, // No lot number
storeLocation: lot.location || '', storeLocation: lot.location || '',
requiredQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, requiredQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0,
actualPickQty: 0, // No items picked (no lot available)
missQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, // All quantity is missing
actualPickQty: 0, // No items picked (no lot available)
missQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, // All quantity is missing
badItemQty: 0, badItemQty: 0,
issueRemark: `No lot available for this item. Handled via handlelotnull.`, issueRemark: `No lot available for this item. Handled via handlelotnull.`,
pickerName: session?.user?.name || '', pickerName: session?.user?.name || '',
@@ -1531,15 +1531,15 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
}; };
const result = await recordPickExecutionIssue(issueData); const result = await recordPickExecutionIssue(issueData);
console.log(" Pick execution issue created for no-lot item:", result);
console.log(" Pick execution issue created for no-lot item:", result);
if (result && result.code === "SUCCESS") { if (result && result.code === "SUCCESS") {
console.log(" No-lot item handled and issue recorded successfully");
console.log(" No-lot item handled and issue recorded successfully");
} else { } else {
console.error("❌ Failed to record pick execution issue:", result); console.error("❌ Failed to record pick execution issue:", result);
} }
// Step 3: Refresh data
// Step 3: Refresh data
await fetchAllCombinedLotData(); await fetchAllCombinedLotData();
} catch (error) { } catch (error) {
console.error("❌ Error in handlelotnull:", error); console.error("❌ Error in handlelotnull:", error);
@@ -1548,14 +1548,14 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
// ... existing code ... // ... existing code ...
const handleSubmitAllScanned = useCallback(async () => { const handleSubmitAllScanned = useCallback(async () => {
const scannedLots = combinedLotData.filter(lot => { const scannedLots = combinedLotData.filter(lot => {
// 如果是 noLot 情况,检查状态是否为 pending 或 partially_complete
// 如果是 noLot 情况,检查状态是否为 pending 或 partially_complete
if (lot.noLot === true) { if (lot.noLot === true) {
return lot.stockOutLineStatus === 'checked' || return lot.stockOutLineStatus === 'checked' ||
lot.stockOutLineStatus === 'pending' || lot.stockOutLineStatus === 'pending' ||
lot.stockOutLineStatus === 'partially_completed' || lot.stockOutLineStatus === 'partially_completed' ||
lot.stockOutLineStatus === 'PARTIALLY_COMPLETE'; lot.stockOutLineStatus === 'PARTIALLY_COMPLETE';
} }
// 正常情况:只包含 checked 状态
// 正常情况:只包含 checked 状态
return lot.stockOutLineStatus === 'checked'; return lot.stockOutLineStatus === 'checked';
}); });
@@ -1568,35 +1568,35 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`); console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`);
try { try {
// Submit all items in parallel using Promise.all
// Submit all items in parallel using Promise.all
const submitPromises = scannedLots.map(async (lot) => { const submitPromises = scannedLots.map(async (lot) => {
// 检查是否是 noLot 情况
// 检查是否是 noLot 情况
if (lot.noLot === true) { if (lot.noLot === true) {
// 使用 handlelotnull 处理无 lot 的情况
// 使用 handlelotnull 处理无 lot 的情况
console.log(`Submitting no-lot item: ${lot.itemName || lot.itemCode}`); console.log(`Submitting no-lot item: ${lot.itemName || lot.itemCode}`);
await updateStockOutLineStatus({ await updateStockOutLineStatus({
id: lot.stockOutLineId, id: lot.stockOutLineId,
status: 'completed', status: 'completed',
qty: 0 qty: 0
}); });
console.log(` No-lot item completed: ${lot.itemName || lot.itemCode}`);
console.log(` No-lot item completed: ${lot.itemName || lot.itemCode}`);
const pickOrderId = lot.pickOrderId || fgPickOrders[0]?.pickOrderId || 0; const pickOrderId = lot.pickOrderId || fgPickOrders[0]?.pickOrderId || 0;
const pickOrderCode = lot.pickOrderCode || fgPickOrders[0]?.pickOrderCode || lot.pickOrderConsoCode || ''; const pickOrderCode = lot.pickOrderCode || fgPickOrders[0]?.pickOrderCode || lot.pickOrderConsoCode || '';
const issueData: PickExecutionIssueData = { const issueData: PickExecutionIssueData = {
type: "Do", // Delivery Order type type: "Do", // Delivery Order type
pickOrderId: pickOrderId, pickOrderId: pickOrderId,
pickOrderCode: pickOrderCode, pickOrderCode: pickOrderCode,
pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), // Use dayjs format
pickOrderCreateDate: dayjs().format('YYYY-MM-DD'), // Use dayjs format
pickExecutionDate: dayjs().format('YYYY-MM-DD'), pickExecutionDate: dayjs().format('YYYY-MM-DD'),
pickOrderLineId: lot.pickOrderLineId, pickOrderLineId: lot.pickOrderLineId,
itemId: lot.itemId, itemId: lot.itemId,
itemCode: lot.itemCode || '', itemCode: lot.itemCode || '',
itemDescription: lot.itemName || '', itemDescription: lot.itemName || '',
lotId: null, // No lot available
lotNo: null, // No lot number
lotId: null, // No lot available
lotNo: null, // No lot number
storeLocation: lot.location || '', storeLocation: lot.location || '',
requiredQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, requiredQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0,
actualPickQty: 0, // No items picked (no lot available)
actualPickQty: 0, // No items picked (no lot available)
missQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0, missQty: lot.requiredQty || lot.pickOrderLineRequiredQty || 0,
badItemQty: 0, badItemQty: 0,
issueRemark: `No lot available for this item. Handled via handlelotnull.`, issueRemark: `No lot available for this item. Handled via handlelotnull.`,
@@ -1607,7 +1607,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
return { success: true, lotNo: lot.lotNo || 'No Lot', isNoLot: true }; return { success: true, lotNo: lot.lotNo || 'No Lot', isNoLot: true };
} }
// 正常情况:有 lot 的处理逻辑
// 正常情况:有 lot 的处理逻辑
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
const currentActualPickQty = lot.actualPickQty || 0; const currentActualPickQty = lot.actualPickQty || 0;
const cumulativeQty = currentActualPickQty + submitQty; const cumulativeQty = currentActualPickQty + submitQty;
@@ -1644,13 +1644,13 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
return { success: true, lotNo: lot.lotNo }; return { success: true, lotNo: lot.lotNo };
}); });
// Wait for all submissions to complete
// Wait for all submissions to complete
const results = await Promise.all(submitPromises); const results = await Promise.all(submitPromises);
const successCount = results.filter(r => r.success).length; const successCount = results.filter(r => r.success).length;
console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`);
console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`);
// Refresh data once after all submissions
// Refresh data once after all submissions
await fetchAllCombinedLotData(); await fetchAllCombinedLotData();
if (successCount > 0) { if (successCount > 0) {
@@ -1669,11 +1669,11 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
} }
}, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull]); }, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull]);


// Calculate scanned items count
// Calculate scanned items count (should match handleSubmitAllScanned filter logic)
// Calculate scanned items count
// Calculate scanned items count (should match handleSubmitAllScanned filter logic)
const scannedItemsCount = useMemo(() => { const scannedItemsCount = useMemo(() => {
const filtered = combinedLotData.filter(lot => { const filtered = combinedLotData.filter(lot => {
// 如果是 noLot 情况,只要状态不是 completed 或 rejected,就包含
// 如果是 noLot 情况,只要状态不是 completed 或 rejected,就包含
if (lot.noLot === true) { if (lot.noLot === true) {
const status = lot.stockOutLineStatus?.toLowerCase(); const status = lot.stockOutLineStatus?.toLowerCase();
const include = status !== 'completed' && status !== 'rejected'; const include = status !== 'completed' && status !== 'rejected';
@@ -1682,11 +1682,11 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
} }
return include; return include;
} }
// 正常情况:只包含 checked 状态
// 正常情况:只包含 checked 状态
return lot.stockOutLineStatus === 'checked'; return lot.stockOutLineStatus === 'checked';
}); });
// 添加调试日志
// 添加调试日志
const noLotCount = filtered.filter(l => l.noLot === true).length; const noLotCount = filtered.filter(l => l.noLot === true).length;
const normalCount = filtered.filter(l => l.noLot !== true).length; const normalCount = filtered.filter(l => l.noLot !== true).length;
console.log(`📊 scannedItemsCount calculation: total=${filtered.length}, noLot=${noLotCount}, normal=${normalCount}`); console.log(`📊 scannedItemsCount calculation: total=${filtered.length}, noLot=${noLotCount}, normal=${normalCount}`);
@@ -1699,7 +1699,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
return filtered.length; return filtered.length;
}, [combinedLotData]); }, [combinedLotData]);


// ADD THIS: Auto-stop scan when no data available
// ADD THIS: Auto-stop scan when no data available
useEffect(() => { useEffect(() => {
if (isManualScanning && combinedLotData.length === 0) { if (isManualScanning && combinedLotData.length === 0) {
console.log("⏹️ No data available, auto-stopping QR scan..."); console.log("⏹️ No data available, auto-stopping QR scan...");
@@ -1707,7 +1707,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
} }
}, [combinedLotData.length, isManualScanning, handleStopScan]); }, [combinedLotData.length, isManualScanning, handleStopScan]);


// Cleanup effect
// Cleanup effect
useEffect(() => { useEffect(() => {
return () => { return () => {
// Cleanup when component unmounts (e.g., when switching tabs) // Cleanup when component unmounts (e.g., when switching tabs)
@@ -1754,7 +1754,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
{/* 保留:Combined Lot Table - 包含所有 QR 扫描功能 */}
{/* 保留:Combined Lot Table - 包含所有 QR 扫描功能 */}
<Box> <Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="h6" gutterBottom sx={{ mb: 0 }}> <Typography variant="h6" gutterBottom sx={{ mb: 0 }}>
@@ -1784,7 +1784,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
</Button> </Button>
)} )}
{/* 保留:Submit All Scanned Button */}
{/* 保留:Submit All Scanned Button */}
<Button <Button
variant="contained" variant="contained"
color="success" color="success"
@@ -1824,9 +1824,9 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
</Typography> </Typography>
</Stack> </Stack>
{/* 改进:三个字段显示在一起,使用表格式布局 */}
{/* 改进:三个字段合并显示 */}
{/* 改进:表格式显示每个 pick order */}
{/* 改进:三个字段显示在一起,使用表格式布局 */}
{/* 改进:三个字段合并显示 */}
{/* 改进:表格式显示每个 pick order */}
<Box sx={{ <Box sx={{
p: 2, p: 2,
backgroundColor: '#f5f5f5', backgroundColor: '#f5f5f5',
@@ -1861,7 +1861,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
return <Typography variant="body2" color="text.secondary">-</Typography>; return <Typography variant="body2" color="text.secondary">-</Typography>;
} }
// 使用与外部基本信息相同的样式
// 使用与外部基本信息相同的样式
return Array.from({ length: maxLength }, (_, idx) => ( return Array.from({ length: maxLength }, (_, idx) => (
<Stack <Stack
key={idx} key={idx}
@@ -1915,7 +1915,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
) : ( ) : (
// 在第 1797-1938 行之间,将整个 map 函数修改为: // 在第 1797-1938 行之间,将整个 map 函数修改为:
paginatedData.map((lot, index) => { paginatedData.map((lot, index) => {
// 检查是否是 issue lot
// 检查是否是 issue lot
const isIssueLot = lot.stockOutLineStatus === 'rejected' || !lot.lotNo; const isIssueLot = lot.stockOutLineStatus === 'rejected' || !lot.lotNo;
return ( return (
@@ -1961,7 +1961,7 @@ paginatedData.map((lot, index) => {
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
{/* Issue lot 不显示扫描状态 */}
{/* Issue lot 不显示扫描状态 */}
{!isIssueLot && lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? ( {!isIssueLot && lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}> <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Checkbox <Checkbox
@@ -1996,7 +1996,7 @@ paginatedData.map((lot, index) => {
<TableCell align="center"> <TableCell align="center">
<Box sx={{ display: 'flex', justifyContent: 'center' }}> <Box sx={{ display: 'flex', justifyContent: 'center' }}>
{isIssueLot ? ( {isIssueLot ? (
// Issue lot 只显示 Issue 按钮
// Issue lot 只显示 Issue 按钮
<Button <Button
variant="outlined" variant="outlined"
size="small" size="small"
@@ -2017,7 +2017,7 @@ paginatedData.map((lot, index) => {
{t("Issue")} {t("Issue")}
</Button> </Button>
) : ( ) : (
// Normal lot 显示两个按钮
// Normal lot 显示两个按钮
<Stack direction="row" spacing={1} alignItems="center"> <Stack direction="row" spacing={1} alignItems="center">
<Button <Button
variant="contained" variant="contained"
@@ -2090,7 +2090,7 @@ paginatedData.map((lot, index) => {
</Box> </Box>
</Stack> </Stack>
{/* 保留:QR Code Modal */}
{/* 保留:QR Code Modal */}
<QrCodeModal <QrCodeModal
open={qrModalOpen} open={qrModalOpen}
onClose={() => { onClose={() => {
@@ -2104,7 +2104,7 @@ paginatedData.map((lot, index) => {
onQrCodeSubmit={handleQrCodeSubmitFromModal} onQrCodeSubmit={handleQrCodeSubmitFromModal}
/> />
{/* 保留:Lot Confirmation Modal */}
{/* 保留:Lot Confirmation Modal */}
{lotConfirmationOpen && expectedLotData && scannedLotData && ( {lotConfirmationOpen && expectedLotData && scannedLotData && (
<LotConfirmationModal <LotConfirmationModal
open={lotConfirmationOpen} open={lotConfirmationOpen}
@@ -2120,7 +2120,7 @@ paginatedData.map((lot, index) => {
/> />
)} )}
{/* 保留:Good Pick Execution Form Modal */}
{/* 保留:Good Pick Execution Form Modal */}
{pickExecutionFormOpen && selectedLotForExecutionForm && ( {pickExecutionFormOpen && selectedLotForExecutionForm && (
<GoodPickExecutionForm <GoodPickExecutionForm
open={pickExecutionFormOpen} open={pickExecutionFormOpen}


+ 38
- 38
src/components/FinishedGoodSearch/PickQcStockInModalVer3.tsx View File

@@ -33,7 +33,7 @@ import EscalationComponent from "../PoDetail/EscalationComponent";
import { fetchPickOrderQcResult, savePickOrderQcResult } from "@/app/api/qc/actions"; import { fetchPickOrderQcResult, savePickOrderQcResult } from "@/app/api/qc/actions";
import { import {
updateInventoryLotLineStatus updateInventoryLotLineStatus
} from "@/app/api/inventory/actions"; // 导入新的 API
} from "@/app/api/inventory/actions"; // 导入新的 API
import { dayjsToDateTimeString } from "@/app/utils/formatUtil"; import { dayjsToDateTimeString } from "@/app/utils/formatUtil";
import dayjs from "dayjs"; import dayjs from "dayjs";


@@ -42,8 +42,8 @@ interface ExtendedQcItem extends QcItemWithChecks {
qcPassed?: boolean; qcPassed?: boolean;
failQty?: number; failQty?: number;
remarks?: string; remarks?: string;
order?: number; // Add order property
stableId?: string; // Also add stableId for better row identification
order?: number; // Add order property
stableId?: string; // Also add stableId for better row identification
} }
interface Props extends CommonProps { interface Props extends CommonProps {
itemDetail: GetPickOrderLineInfo & { itemDetail: GetPickOrderLineInfo & {
@@ -55,7 +55,7 @@ interface Props extends CommonProps {
selectedLotId?: number; selectedLotId?: number;
onStockOutLineUpdate?: () => void; onStockOutLineUpdate?: () => void;
lotData: LotPickData[]; lotData: LotPickData[];
// Add missing props
// Add missing props
pickQtyData?: PickQtyData; pickQtyData?: PickQtyData;
selectedRowId?: number; selectedRowId?: number;
} }
@@ -104,7 +104,7 @@ interface Props extends CommonProps {
}; };
qcItems: ExtendedQcItem[]; // Change to ExtendedQcItem qcItems: ExtendedQcItem[]; // Change to ExtendedQcItem
setQcItems: Dispatch<SetStateAction<ExtendedQcItem[]>>; // Change to ExtendedQcItem setQcItems: Dispatch<SetStateAction<ExtendedQcItem[]>>; // Change to ExtendedQcItem
// Add props for stock out line update
// Add props for stock out line update
selectedLotId?: number; selectedLotId?: number;
onStockOutLineUpdate?: () => void; onStockOutLineUpdate?: () => void;
lotData: LotPickData[]; lotData: LotPickData[];
@@ -193,7 +193,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
failQty: item.isPassed ? 0 : (item.failQty || 0), // 0 for passed, actual qty for failed failQty: item.isPassed ? 0 : (item.failQty || 0), // 0 for passed, actual qty for failed
type: "pick_order_qc", type: "pick_order_qc",
remarks: item.remarks || "", remarks: item.remarks || "",
qcPassed: item.isPassed, // This will now be included
qcPassed: item.isPassed, // This will now be included
})); }));


// Store the submitted data for debug display // Store the submitted data for debug display
@@ -217,17 +217,17 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
} }
}; };


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


// Add this helper function at the top of the component
// Add this helper function at the top of the component
const safeClose = useCallback(() => { const safeClose = useCallback(() => {
if (onClose) { if (onClose) {
// Create a mock event object that satisfies the Modal onClose signature // Create a mock event object that satisfies the Modal onClose signature
@@ -259,12 +259,12 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
isPersistent: () => false isPersistent: () => false
} as any; } as any;
// Fixed: Pass both event and reason parameters
// Fixed: Pass both event and reason parameters
onClose(mockEvent, 'escapeKeyDown'); // 'escapeKeyDown' is a valid reason onClose(mockEvent, 'escapeKeyDown'); // 'escapeKeyDown' is a valid reason
} }
}, [onClose]); }, [onClose]);


// 修改:移除 alert 弹窗,改为控制台日志
// 修改:移除 alert 弹窗,改为控制台日志
const onSubmitQc = useCallback<SubmitHandler<any>>( const onSubmitQc = useCallback<SubmitHandler<any>>(
async (data, event) => { async (data, event) => {
setIsSubmitting(true); setIsSubmitting(true);
@@ -276,7 +276,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
const validationErrors : string[] = []; const validationErrors : string[] = [];
const selectedLot = lotData.find(lot => lot.stockOutLineId === selectedLotId); const selectedLot = lotData.find(lot => lot.stockOutLineId === selectedLotId);
// Add safety check for selectedLot
// Add safety check for selectedLot
if (!selectedLot) { if (!selectedLot) {
console.error("Selected lot not found"); console.error("Selected lot not found");
return; return;
@@ -313,23 +313,23 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
return; return;
} }
// Handle different QC decisions
// Handle different QC decisions
if (selectedLotId) { if (selectedLotId) {
try { try {
const allPassed = qcData.qcItems.every(item => item.isPassed); const allPassed = qcData.qcItems.every(item => item.isPassed);
if (qcDecision === "2") { if (qcDecision === "2") {
// QC Decision 2: Report and Re-pick
// QC Decision 2: Report and Re-pick
console.log("QC Decision 2 - Report and Re-pick: Rejecting lot and marking as unavailable"); console.log("QC Decision 2 - Report and Re-pick: Rejecting lot and marking as unavailable");
// Inventory lot line status: unavailable
// Inventory lot line status: unavailable
if (selectedLot) { if (selectedLot) {
try { try {
console.log("=== DEBUG: Updating inventory lot line status ==="); console.log("=== DEBUG: Updating inventory lot line status ===");
console.log("Selected lot:", selectedLot); console.log("Selected lot:", selectedLot);
console.log("Selected lot ID:", selectedLotId); console.log("Selected lot ID:", selectedLotId);
// FIX: Only send the fields that the backend expects
// FIX: Only send the fields that the backend expects
const updateData = { const updateData = {
inventoryLotLineId: selectedLot.lotId, inventoryLotLineId: selectedLot.lotId,
status: 'unavailable' status: 'unavailable'
@@ -339,7 +339,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
console.log("Update data:", updateData); console.log("Update data:", updateData);
const result = await updateInventoryLotLineStatus(updateData); const result = await updateInventoryLotLineStatus(updateData);
console.log(" Inventory lot line status updated successfully:", result);
console.log(" Inventory lot line status updated successfully:", result);
} catch (error) { } catch (error) {
console.error("❌ Error updating inventory lot line status:", error); console.error("❌ Error updating inventory lot line status:", error);
@@ -359,28 +359,28 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
return; return;
} }
// Close modal and refresh data
safeClose(); // Fixed: Use safe close function with both parameters
// Close modal and refresh data
safeClose(); // Fixed: Use safe close function with both parameters
if (onStockOutLineUpdate) { if (onStockOutLineUpdate) {
onStockOutLineUpdate(); onStockOutLineUpdate();
} }
} else if (qcDecision === "1") { } else if (qcDecision === "1") {
// QC Decision 1: Accept
// QC Decision 1: Accept
console.log("QC Decision 1 - Accept: QC passed"); console.log("QC Decision 1 - Accept: QC passed");
// Stock out line status: checked (QC completed)
// Stock out line status: checked (QC completed)
await updateStockOutLineStatus({ await updateStockOutLineStatus({
id: selectedLotId, id: selectedLotId,
status: 'checked', status: 'checked',
qty: acceptQty || 0 qty: acceptQty || 0
}); });
// Inventory lot line status: NO CHANGE needed
// Inventory lot line status: NO CHANGE needed
// Keep the existing status from handleSubmitPickQty // Keep the existing status from handleSubmitPickQty
// Close modal and refresh data
safeClose(); // Fixed: Use safe close function with both parameters
// Close modal and refresh data
safeClose(); // Fixed: Use safe close function with both parameters
if (onStockOutLineUpdate) { if (onStockOutLineUpdate) {
onStockOutLineUpdate(); onStockOutLineUpdate();
} }
@@ -399,7 +399,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
console.log("QC results saved successfully!"); console.log("QC results saved successfully!");
// Show warning dialog for failed QC items when accepting
// Show warning dialog for failed QC items when accepting
if (qcDecision === "1" && !qcData.qcItems.every((q) => q.isPassed)) { if (qcDecision === "1" && !qcData.qcItems.every((q) => q.isPassed)) {
submitDialogWithWarning(() => { submitDialogWithWarning(() => {
closeHandler?.({}, 'escapeKeyDown'); closeHandler?.({}, 'escapeKeyDown');
@@ -448,7 +448,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
value={current.qcPassed === undefined ? "" : (current.qcPassed ? "true" : "false")} value={current.qcPassed === undefined ? "" : (current.qcPassed ? "true" : "false")}
onChange={(e) => { onChange={(e) => {
const value = e.target.value === "true"; const value = e.target.value === "true";
// Simple state update
// Simple state update
setQcItems(prev => setQcItems(prev =>
prev.map(item => prev.map(item =>
item.id === params.id item.id === params.id
@@ -490,10 +490,10 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
<TextField <TextField
type="number" type="number"
size="small" size="small"
// 修改:失败项目自动显示 Lot Required Pick Qty
// 修改:失败项目自动显示 Lot Required Pick Qty
value={!params.row.qcPassed ? (0) : 0} value={!params.row.qcPassed ? (0) : 0}
disabled={params.row.qcPassed} disabled={params.row.qcPassed}
// 移除 onChange,因为数量是固定的
// 移除 onChange,因为数量是固定的
// onChange={(e) => { // onChange={(e) => {
// const v = e.target.value; // const v = e.target.value;
// const next = v === "" ? undefined : Number(v); // const next = v === "" ? undefined : Number(v);
@@ -535,7 +535,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
[t], [t],
); );


// Add stable update function
// Add stable update function
const handleQcResultChange = useCallback((itemId: number, qcPassed: boolean) => { const handleQcResultChange = useCallback((itemId: number, qcPassed: boolean) => {
setQcItems(prevItems => setQcItems(prevItems =>
prevItems.map(item => prevItems.map(item =>
@@ -546,16 +546,16 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
); );
}, []); }, []);


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


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


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


{/* Combirne options 2 & 3 into one */}
{/* Combirne options 2 & 3 into one */}
<FormControlLabel <FormControlLabel
value="2" value="2"
control={<Radio />} control={<Radio />}
@@ -649,7 +649,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
</FormControl> </FormControl>
</Grid> </Grid>


{/* Show escalation component when QC Decision = 2 (Report and Re-pick) */}
{/* Show escalation component when QC Decision = 2 (Report and Re-pick) */}


<Grid item xs={12} sx={{ mt: 2 }}> <Grid item xs={12} sx={{ mt: 2 }}>


+ 2
- 2
src/components/FinishedGoodSearch/newcreatitem.tsx View File

@@ -568,7 +568,7 @@ const handleQtyBlur = useCallback((itemId: number) => {
return; return;
} }
// 修复:自动填充 type 为 "Consumable",不再强制用户选择
// 修复:自动填充 type 为 "Consumable",不再强制用户选择
// if (!data.type) { // if (!data.type) {
// alert(t("Please select product type")); // alert(t("Please select product type"));
// return; // return;
@@ -625,7 +625,7 @@ const handleQtyBlur = useCallback((itemId: number) => {
} }
} }
// 修复:自动使用 "Consumable" 作为默认 type
// 修复:自动使用 "Consumable" 作为默认 type
const pickOrderData: SavePickOrderRequest = { const pickOrderData: SavePickOrderRequest = {
type: data.type || "Consumable", // 如果用户选择了 type 就用用户的,否则默认 "Consumable" type: data.type || "Consumable", // 如果用户选择了 type 就用用户的,否则默认 "Consumable"
targetDate: formattedTargetDate, targetDate: formattedTargetDate,


+ 3
- 3
src/components/Jodetail/CombinedLotTable.tsx View File

@@ -34,7 +34,7 @@ interface CombinedLotTableProps {
onPageSizeChange: (event: React.ChangeEvent<HTMLInputElement>) => void; onPageSizeChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
} }


// Simple helper function to check if item is completed
// Simple helper function to check if item is completed
const isItemCompleted = (lot: any) => { const isItemCompleted = (lot: any) => {
const actualPickQty = Number(lot.actualPickQty) || 0; const actualPickQty = Number(lot.actualPickQty) || 0;
const requiredQty = Number(lot.requiredQty) || 0; const requiredQty = Number(lot.requiredQty) || 0;
@@ -60,7 +60,7 @@ const CombinedLotTable: React.FC<CombinedLotTableProps> = ({
}) => { }) => {
const { t } = useTranslation("pickOrder"); const { t } = useTranslation("pickOrder");


// Paginated data
// Paginated data
const paginatedLotData = useMemo(() => { const paginatedLotData = useMemo(() => {
const startIndex = paginationController.pageNum * paginationController.pageSize; const startIndex = paginationController.pageNum * paginationController.pageSize;
const endIndex = startIndex + paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize;
@@ -113,7 +113,7 @@ const CombinedLotTable: React.FC<CombinedLotTableProps> = ({
const isCompleted = isItemCompleted(lot); const isCompleted = isItemCompleted(lot);
const isRejected = isItemRejected(lot); const isRejected = isItemRejected(lot);
// Green text color for completed items
// Green text color for completed items
const textColor = isCompleted ? 'success.main' : isRejected ? 'error.main' : 'inherit'; const textColor = isCompleted ? 'success.main' : isRejected ? 'error.main' : 'inherit';
return ( return (


+ 30
- 30
src/components/Jodetail/FInishedJobOrderRecord.tsx View File

@@ -24,7 +24,7 @@ import {
Accordion, Accordion,
AccordionSummary, AccordionSummary,
AccordionDetails, AccordionDetails,
Checkbox, // Add Checkbox import
Checkbox, // Add Checkbox import
} from "@mui/material"; } from "@mui/material";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useCallback, useEffect, useState, useRef, useMemo } from "react"; import { useCallback, useEffect, useState, useRef, useMemo } from "react";
@@ -47,7 +47,7 @@ interface Props {
filterArgs: Record<string, any>; filterArgs: Record<string, any>;
} }


// 修改:已完成的 Job Order Pick Order 接口
// 修改:已完成的 Job Order Pick Order 接口
interface CompletedJobOrderPickOrder { interface CompletedJobOrderPickOrder {
id: number; id: number;
pickOrderId: number; pickOrderId: number;
@@ -68,7 +68,7 @@ interface CompletedJobOrderPickOrder {
completedItems: number; completedItems: number;
} }


// 新增:Lot 详情接口
// 新增:Lot 详情接口
interface LotDetail { interface LotDetail {
lotId: number; lotId: number;
lotNo: string; lotNo: string;
@@ -104,21 +104,21 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
const currentUserId = session?.id ? parseInt(session.id) : undefined; const currentUserId = session?.id ? parseInt(session.id) : undefined;
// 修改:已完成 Job Order Pick Orders 状态
// 修改:已完成 Job Order Pick Orders 状态
const [completedJobOrderPickOrders, setCompletedJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]); const [completedJobOrderPickOrders, setCompletedJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]);
const [completedJobOrderPickOrdersLoading, setCompletedJobOrderPickOrdersLoading] = useState(false); const [completedJobOrderPickOrdersLoading, setCompletedJobOrderPickOrdersLoading] = useState(false);
// 修改:详情视图状态
// 修改:详情视图状态
const [selectedJobOrderPickOrder, setSelectedJobOrderPickOrder] = useState<CompletedJobOrderPickOrder | null>(null); const [selectedJobOrderPickOrder, setSelectedJobOrderPickOrder] = useState<CompletedJobOrderPickOrder | null>(null);
const [showDetailView, setShowDetailView] = useState(false); const [showDetailView, setShowDetailView] = useState(false);
const [detailLotData, setDetailLotData] = useState<LotDetail[]>([]); const [detailLotData, setDetailLotData] = useState<LotDetail[]>([]);
const [detailLotDataLoading, setDetailLotDataLoading] = useState(false); const [detailLotDataLoading, setDetailLotDataLoading] = useState(false);
// 修改:搜索状态
// 修改:搜索状态
const [searchQuery, setSearchQuery] = useState<Record<string, any>>({}); const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
const [filteredJobOrderPickOrders, setFilteredJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]); const [filteredJobOrderPickOrders, setFilteredJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]);
// 修改:分页状态
// 修改:分页状态
const [paginationController, setPaginationController] = useState({ const [paginationController, setPaginationController] = useState({
pageNum: 0, pageNum: 0,
pageSize: 10, pageSize: 10,
@@ -127,7 +127,7 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
const formProps = useForm(); const formProps = useForm();
const errors = formProps.formState.errors; const errors = formProps.formState.errors;


// 修改:使用新的 Job Order API 获取已完成的 Job Order Pick Orders
// 修改:使用新的 Job Order API 获取已完成的 Job Order Pick Orders
const fetchCompletedJobOrderPickOrdersData = useCallback(async () => { const fetchCompletedJobOrderPickOrdersData = useCallback(async () => {
if (!currentUserId) return; if (!currentUserId) return;
@@ -139,7 +139,7 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
setCompletedJobOrderPickOrders(completedJobOrderPickOrders); setCompletedJobOrderPickOrders(completedJobOrderPickOrders);
setFilteredJobOrderPickOrders(completedJobOrderPickOrders); setFilteredJobOrderPickOrders(completedJobOrderPickOrders);
console.log(" Fetched completed Job Order pick orders:", completedJobOrderPickOrders);
console.log(" Fetched completed Job Order pick orders:", completedJobOrderPickOrders);
} catch (error) { } catch (error) {
console.error("❌ Error fetching completed Job Order pick orders:", error); console.error("❌ Error fetching completed Job Order pick orders:", error);
setCompletedJobOrderPickOrders([]); setCompletedJobOrderPickOrders([]);
@@ -149,7 +149,7 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
} }
}, [currentUserId]); }, [currentUserId]);


// 新增:获取 lot 详情数据
// 新增:获取 lot 详情数据
const fetchLotDetailsData = useCallback(async (pickOrderId: number) => { const fetchLotDetailsData = useCallback(async (pickOrderId: number) => {
setDetailLotDataLoading(true); setDetailLotDataLoading(true);
try { try {
@@ -158,7 +158,7 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
const lotDetails = await fetchCompletedJobOrderPickOrderLotDetails(pickOrderId); const lotDetails = await fetchCompletedJobOrderPickOrderLotDetails(pickOrderId);
setDetailLotData(lotDetails); setDetailLotData(lotDetails);
console.log(" Fetched lot details:", lotDetails);
console.log(" Fetched lot details:", lotDetails);
} catch (error) { } catch (error) {
console.error("❌ Error fetching lot details:", error); console.error("❌ Error fetching lot details:", error);
setDetailLotData([]); setDetailLotData([]);
@@ -167,14 +167,14 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
} }
}, []); }, []);


// 修改:初始化时获取数据
// 修改:初始化时获取数据
useEffect(() => { useEffect(() => {
if (currentUserId) { if (currentUserId) {
fetchCompletedJobOrderPickOrdersData(); fetchCompletedJobOrderPickOrdersData();
} }
}, [currentUserId, fetchCompletedJobOrderPickOrdersData]); }, [currentUserId, fetchCompletedJobOrderPickOrdersData]);


// 修改:搜索功能
// 修改:搜索功能
const handleSearch = useCallback((query: Record<string, any>) => { const handleSearch = useCallback((query: Record<string, any>) => {
setSearchQuery({ ...query }); setSearchQuery({ ...query });
console.log("Search query:", query); console.log("Search query:", query);
@@ -196,13 +196,13 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
console.log("Filtered Job Order pick orders count:", filtered.length); console.log("Filtered Job Order pick orders count:", filtered.length);
}, [completedJobOrderPickOrders]); }, [completedJobOrderPickOrders]);


// 修改:重置搜索
// 修改:重置搜索
const handleSearchReset = useCallback(() => { const handleSearchReset = useCallback(() => {
setSearchQuery({}); setSearchQuery({});
setFilteredJobOrderPickOrders(completedJobOrderPickOrders); setFilteredJobOrderPickOrders(completedJobOrderPickOrders);
}, [completedJobOrderPickOrders]); }, [completedJobOrderPickOrders]);


// 修改:分页功能
// 修改:分页功能
const handlePageChange = useCallback((event: unknown, newPage: number) => { const handlePageChange = useCallback((event: unknown, newPage: number) => {
setPaginationController(prev => ({ setPaginationController(prev => ({
...prev, ...prev,
@@ -218,14 +218,14 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
}); });
}, []); }, []);


// 修改:分页数据
// 修改:分页数据
const paginatedData = useMemo(() => { const paginatedData = useMemo(() => {
const startIndex = paginationController.pageNum * paginationController.pageSize; const startIndex = paginationController.pageNum * paginationController.pageSize;
const endIndex = startIndex + paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize;
return filteredJobOrderPickOrders.slice(startIndex, endIndex); return filteredJobOrderPickOrders.slice(startIndex, endIndex);
}, [filteredJobOrderPickOrders, paginationController]); }, [filteredJobOrderPickOrders, paginationController]);


// 修改:搜索条件
// 修改:搜索条件
const searchCriteria: Criterion<any>[] = [ const searchCriteria: Criterion<any>[] = [
{ {
label: t("Pick Order Code"), label: t("Pick Order Code"),
@@ -244,34 +244,34 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
} }
]; ];


// 修改:详情点击处理
// 修改:详情点击处理
const handleDetailClick = useCallback(async (jobOrderPickOrder: CompletedJobOrderPickOrder) => { const handleDetailClick = useCallback(async (jobOrderPickOrder: CompletedJobOrderPickOrder) => {
setSelectedJobOrderPickOrder(jobOrderPickOrder); setSelectedJobOrderPickOrder(jobOrderPickOrder);
setShowDetailView(true); setShowDetailView(true);
// 获取 lot 详情数据
// 获取 lot 详情数据
await fetchLotDetailsData(jobOrderPickOrder.pickOrderId); await fetchLotDetailsData(jobOrderPickOrder.pickOrderId);
// 触发打印按钮状态更新 - 基于详情数据
// 触发打印按钮状态更新 - 基于详情数据
const allCompleted = jobOrderPickOrder.secondScanCompleted; const allCompleted = jobOrderPickOrder.secondScanCompleted;
// 发送事件,包含标签页信息
// 发送事件,包含标签页信息
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: { detail: {
allLotsCompleted: allCompleted, allLotsCompleted: allCompleted,
tabIndex: 2 // 明确指定这是来自标签页 2 的事件
tabIndex: 2 // 明确指定这是来自标签页 2 的事件
} }
})); }));
}, [fetchLotDetailsData]); }, [fetchLotDetailsData]);


// 修改:返回列表视图
// 修改:返回列表视图
const handleBackToList = useCallback(() => { const handleBackToList = useCallback(() => {
setShowDetailView(false); setShowDetailView(false);
setSelectedJobOrderPickOrder(null); setSelectedJobOrderPickOrder(null);
setDetailLotData([]); setDetailLotData([]);
// 返回列表时禁用打印按钮
// 返回列表时禁用打印按钮
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: { detail: {
allLotsCompleted: false, allLotsCompleted: false,
@@ -280,7 +280,7 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
})); }));
}, []); }, []);


// 修改:如果显示详情视图,渲染 Job Order 详情和 Lot 信息
// 修改:如果显示详情视图,渲染 Job Order 详情和 Lot 信息
if (showDetailView && selectedJobOrderPickOrder) { if (showDetailView && selectedJobOrderPickOrder) {
return ( return (
<FormProvider {...formProps}> <FormProvider {...formProps}>
@@ -322,7 +322,7 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
</CardContent> </CardContent>
</Card> </Card>


{/* 修改:Lot 详情表格 - 添加复选框列 */}
{/* 修改:Lot 详情表格 - 添加复选框列 */}
<Card> <Card>
<CardContent> <CardContent>
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
@@ -353,7 +353,7 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
<TableBody> <TableBody>
{detailLotData.length === 0 ? ( {detailLotData.length === 0 ? (
<TableRow> <TableRow>
<TableCell colSpan={10} align="center"> {/* 恢复原来的 colSpan */}
<TableCell colSpan={10} align="center"> {/* 恢复原来的 colSpan */}
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{t("No lot details available")} {t("No lot details available")}
</Typography> </Typography>
@@ -382,7 +382,7 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
<TableCell align="right"> <TableCell align="right">
{lot.actualPickQty?.toLocaleString() || 0} ({lot.uomShortDesc}) {lot.actualPickQty?.toLocaleString() || 0} ({lot.uomShortDesc})
</TableCell> </TableCell>
{/* 修改:Processing Status 使用复选框 */}
{/* 修改:Processing Status 使用复选框 */}
<TableCell align="center"> <TableCell align="center">
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
@@ -409,7 +409,7 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
/> />
</Box> </Box>
</TableCell> </TableCell>
{/* 修改:Second Scan Status 使用复选框 */}
{/* 修改:Second Scan Status 使用复选框 */}
<TableCell align="center"> <TableCell align="center">
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
@@ -450,7 +450,7 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
); );
} }


// 修改:默认列表视图
// 修改:默认列表视图
return ( return (
<FormProvider {...formProps}> <FormProvider {...formProps}>
<Box> <Box>


+ 90
- 90
src/components/Jodetail/JobPickExecution.tsx View File

@@ -36,7 +36,7 @@ import {
checkAndCompletePickOrderByConsoCode, checkAndCompletePickOrderByConsoCode,
confirmLotSubstitution confirmLotSubstitution
} from "@/app/api/pickOrder/actions"; } from "@/app/api/pickOrder/actions";
// 修改:使用 Job Order API
// 修改:使用 Job Order API
import { import {
fetchJobOrderLotsHierarchical, fetchJobOrderLotsHierarchical,
fetchUnassignedJobOrderPickOrders, fetchUnassignedJobOrderPickOrders,
@@ -62,7 +62,7 @@ interface Props {
filterArgs: Record<string, any>; filterArgs: Record<string, any>;
} }


// QR Code Modal Component (from GoodPickExecution)
// QR Code Modal Component (from GoodPickExecution)
const QrCodeModal: React.FC<{ const QrCodeModal: React.FC<{
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
@@ -108,7 +108,7 @@ const QrCodeModal: React.FC<{
setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number'); setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number');
if (stockInLineInfo.lotNo === lot.lotNo) { if (stockInLineInfo.lotNo === lot.lotNo) {
console.log(` QR Code verified for lot: ${lot.lotNo}`);
console.log(` QR Code verified for lot: ${lot.lotNo}`);
setQrScanSuccess(true); setQrScanSuccess(true);
onQrCodeSubmit(lot.lotNo); onQrCodeSubmit(lot.lotNo);
onClose(); onClose();
@@ -300,7 +300,7 @@ const QrCodeModal: React.FC<{


{qrScanSuccess && ( {qrScanSuccess && (
<Typography variant="caption" color="success" display="block"> <Typography variant="caption" color="success" display="block">
{t("Verified successfully!")}
{t("Verified successfully!")}
</Typography> </Typography>
)} )}
</Box> </Box>
@@ -323,13 +323,13 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
const currentUserId = session?.id ? parseInt(session.id) : undefined; const currentUserId = session?.id ? parseInt(session.id) : undefined;
// 修改:使用 Job Order 数据结构
// 修改:使用 Job Order 数据结构
const [jobOrderData, setJobOrderData] = useState<any>(null); const [jobOrderData, setJobOrderData] = useState<any>(null);
const [combinedLotData, setCombinedLotData] = useState<any[]>([]); const [combinedLotData, setCombinedLotData] = useState<any[]>([]);
const [combinedDataLoading, setCombinedDataLoading] = useState(false); const [combinedDataLoading, setCombinedDataLoading] = useState(false);
const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]); const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]);
// 添加未分配订单状态
// 添加未分配订单状态
const [unassignedOrders, setUnassignedOrders] = useState<any[]>([]); const [unassignedOrders, setUnassignedOrders] = useState<any[]>([]);
const [isLoadingUnassigned, setIsLoadingUnassigned] = useState(false); const [isLoadingUnassigned, setIsLoadingUnassigned] = useState(false);
@@ -359,23 +359,23 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
const errors = formProps.formState.errors; const errors = formProps.formState.errors;
const [isSubmittingAll, setIsSubmittingAll] = useState<boolean>(false); const [isSubmittingAll, setIsSubmittingAll] = useState<boolean>(false);


// Add QR modal states
// Add QR modal states
const [qrModalOpen, setQrModalOpen] = useState(false); const [qrModalOpen, setQrModalOpen] = useState(false);
const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null); const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null);


// Add GoodPickExecutionForm states
// Add GoodPickExecutionForm states
const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null); const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null);
const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]); const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]);
const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false); const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false);


// Add these missing state variables
// Add these missing state variables
const [isManualScanning, setIsManualScanning] = useState<boolean>(false); const [isManualScanning, setIsManualScanning] = useState<boolean>(false);
const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set()); const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
const [lastProcessedQr, setLastProcessedQr] = useState<string>(''); const [lastProcessedQr, setLastProcessedQr] = useState<string>('');
const [isRefreshingData, setIsRefreshingData] = useState<boolean>(false); const [isRefreshingData, setIsRefreshingData] = useState<boolean>(false);


// 修改:加载未分配的 Job Order 订单
// 修改:加载未分配的 Job Order 订单
const loadUnassignedOrders = useCallback(async () => { const loadUnassignedOrders = useCallback(async () => {
setIsLoadingUnassigned(true); setIsLoadingUnassigned(true);
try { try {
@@ -388,7 +388,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, []); }, []);


// 修改:分配订单给当前用户
// 修改:分配订单给当前用户
const handleAssignOrder = useCallback(async (pickOrderId: number) => { const handleAssignOrder = useCallback(async (pickOrderId: number) => {
if (!currentUserId) { if (!currentUserId) {
console.error("Missing user id in session"); console.error("Missing user id in session");
@@ -398,7 +398,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
try { try {
const result = await assignJobOrderPickOrder(pickOrderId, currentUserId); const result = await assignJobOrderPickOrder(pickOrderId, currentUserId);
if (result.message === "Successfully assigned") { if (result.message === "Successfully assigned") {
console.log(" Successfully assigned pick order");
console.log(" Successfully assigned pick order");
// 刷新数据 // 刷新数据
window.dispatchEvent(new CustomEvent('pickOrderAssigned')); window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
// 重新加载未分配订单列表 // 重新加载未分配订单列表
@@ -437,7 +437,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
const allFgPickOrders = fgPickOrdersResults.flat(); const allFgPickOrders = fgPickOrdersResults.flat();
setFgPickOrders(allFgPickOrders); setFgPickOrders(allFgPickOrders);
console.log(" Fetched FG pick orders:", allFgPickOrders);
console.log(" Fetched FG pick orders:", allFgPickOrders);
} catch (error) { } catch (error) {
console.error("❌ Error fetching FG pick orders:", error); console.error("❌ Error fetching FG pick orders:", error);
setFgPickOrders([]); setFgPickOrders([]);
@@ -452,13 +452,13 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [combinedLotData, fetchFgPickOrdersData]); }, [combinedLotData, fetchFgPickOrdersData]);


// Handle QR code button click
// Handle QR code button click
const handleQrCodeClick = (pickOrderId: number) => { const handleQrCodeClick = (pickOrderId: number) => {
console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); console.log(`QR Code clicked for pick order ID: ${pickOrderId}`);
// TODO: Implement QR code functionality // TODO: Implement QR code functionality
}; };


// 修改:使用 Job Order API 获取数据
// 修改:使用 Job Order API 获取数据
const fetchJobOrderData = useCallback(async (userId?: number) => { const fetchJobOrderData = useCallback(async (userId?: number) => {
setCombinedDataLoading(true); setCombinedDataLoading(true);
try { try {
@@ -479,13 +479,13 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
tabIndex: 0 tabIndex: 0
} }
})); }));
// 使用 Job Order API
// 使用 Job Order API
const jobOrderData = await fetchJobOrderLotsHierarchical(userIdToUse); const jobOrderData = await fetchJobOrderLotsHierarchical(userIdToUse);
console.log(" Job Order data:", jobOrderData);
console.log(" Job Order data:", jobOrderData);
setJobOrderData(jobOrderData); setJobOrderData(jobOrderData);
// Transform hierarchical data to flat structure for the table
// Transform hierarchical data to flat structure for the table
const flatLotData: any[] = []; const flatLotData: any[] = [];
if (jobOrderData.pickOrder && jobOrderData.pickOrderLines) { if (jobOrderData.pickOrder && jobOrderData.pickOrderLines) {
@@ -541,7 +541,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
}); });
} }
console.log(" Transformed flat lot data:", flatLotData);
console.log(" Transformed flat lot data:", flatLotData);
setCombinedLotData(flatLotData); setCombinedLotData(flatLotData);
setOriginalCombinedData(flatLotData); setOriginalCombinedData(flatLotData);
const hasData = flatLotData.length > 0; const hasData = flatLotData.length > 0;
@@ -551,16 +551,16 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
tabIndex: 0 tabIndex: 0
} }
})); }));
// 计算完成状态并发送事件
// 计算完成状态并发送事件
const allCompleted = flatLotData.length > 0 && flatLotData.every((lot: any) => const allCompleted = flatLotData.length > 0 && flatLotData.every((lot: any) =>
lot.processingStatus === 'completed' lot.processingStatus === 'completed'
); );
// 发送完成状态事件,包含标签页信息
// 发送完成状态事件,包含标签页信息
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: { detail: {
allLotsCompleted: allCompleted, allLotsCompleted: allCompleted,
tabIndex: 0 // 明确指定这是来自标签页 0 的事件
tabIndex: 0 // 明确指定这是来自标签页 0 的事件
} }
})); }));
@@ -570,7 +570,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
setCombinedLotData([]); setCombinedLotData([]);
setOriginalCombinedData([]); setOriginalCombinedData([]);
// 如果加载失败,禁用打印按钮
// 如果加载失败,禁用打印按钮
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: { detail: {
allLotsCompleted: false, allLotsCompleted: false,
@@ -582,10 +582,10 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [currentUserId]); }, [currentUserId]);


// 修改:初始化时加载数据
// 修改:初始化时加载数据
useEffect(() => { useEffect(() => {
if (session && currentUserId && !initializationRef.current) { if (session && currentUserId && !initializationRef.current) {
console.log(" Session loaded, initializing job order...");
console.log(" Session loaded, initializing job order...");
initializationRef.current = true; initializationRef.current = true;
// 加载 Job Order 数据 // 加载 Job Order 数据
@@ -595,7 +595,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [session, currentUserId, fetchJobOrderData, loadUnassignedOrders]); }, [session, currentUserId, fetchJobOrderData, loadUnassignedOrders]);


// Add event listener for manual assignment
// Add event listener for manual assignment
useEffect(() => { useEffect(() => {
const handlePickOrderAssigned = () => { const handlePickOrderAssigned = () => {
console.log("🔄 Pick order assigned event received, refreshing data..."); console.log("🔄 Pick order assigned event received, refreshing data...");
@@ -609,11 +609,11 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
}; };
}, [fetchJobOrderData]); }, [fetchJobOrderData]);


// Handle QR code submission for matched lot (external scanning)
// Handle QR code submission for matched lot (external scanning)
const handleQrCodeSubmit = useCallback(async (lotNo: string) => { const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
console.log(` Processing QR Code for lot: ${lotNo}`);
console.log(` Processing QR Code for lot: ${lotNo}`);
// Use current data without refreshing to avoid infinite loop
// Use current data without refreshing to avoid infinite loop
const currentLotData = combinedLotData; const currentLotData = combinedLotData;
console.log(`🔍 Available lots:`, currentLotData.map(lot => lot.lotNo)); console.log(`🔍 Available lots:`, currentLotData.map(lot => lot.lotNo));
@@ -631,7 +631,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
return; return;
} }
console.log(` Found ${matchingLots.length} matching lots:`, matchingLots);
console.log(` Found ${matchingLots.length} matching lots:`, matchingLots);
setQrScanError(false); setQrScanError(false);
try { try {
@@ -706,13 +706,13 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
} }
// FIXED: Set refresh flag before refreshing data
// FIXED: Set refresh flag before refreshing data
setIsRefreshingData(true); setIsRefreshingData(true);
console.log("🔄 Refreshing data after QR code processing..."); console.log("🔄 Refreshing data after QR code processing...");
await fetchJobOrderData(); await fetchJobOrderData();
if (successCount > 0) { if (successCount > 0) {
console.log(` QR Code processing completed: ${successCount} updated/created`);
console.log(` QR Code processing completed: ${successCount} updated/created`);
setQrScanSuccess(true); setQrScanSuccess(true);
setQrScanError(false); setQrScanError(false);
setQrScanInput(''); // Clear input after successful processing setQrScanInput(''); // Clear input after successful processing
@@ -727,11 +727,11 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
setQrScanError(true); setQrScanError(true);
setQrScanSuccess(false); setQrScanSuccess(false);
// Still refresh data even on error
// Still refresh data even on error
setIsRefreshingData(true); setIsRefreshingData(true);
await fetchJobOrderData(); await fetchJobOrderData();
} finally { } finally {
// Clear refresh flag after a short delay
// Clear refresh flag after a short delay
setTimeout(() => { setTimeout(() => {
setIsRefreshingData(false); setIsRefreshingData(false);
}, 1000); }, 1000);
@@ -744,7 +744,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
setLotConfirmationOpen(true); setLotConfirmationOpen(true);
}, []); }, []);


// Add handleLotConfirmation function
// Add handleLotConfirmation function
const handleLotConfirmation = useCallback(async () => { const handleLotConfirmation = useCallback(async () => {
if (!expectedLotData || !scannedLotData || !selectedLotForQr) return; if (!expectedLotData || !scannedLotData || !selectedLotForQr) return;
setIsConfirmingLot(true); setIsConfirmingLot(true);
@@ -767,7 +767,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
console.log("Lot ID (fallback):", selectedLotForQr.lotId); console.log("Lot ID (fallback):", selectedLotForQr.lotId);
console.log("New Inventory Lot Line ID:", newLotLineId); console.log("New Inventory Lot Line ID:", newLotLineId);


// Call confirmLotSubstitution to update the suggested lot
// Call confirmLotSubstitution to update the suggested lot
const substitutionResult = await confirmLotSubstitution({ const substitutionResult = await confirmLotSubstitution({
pickOrderLineId: selectedLotForQr.pickOrderLineId, pickOrderLineId: selectedLotForQr.pickOrderLineId,
stockOutLineId: selectedLotForQr.stockOutLineId, stockOutLineId: selectedLotForQr.stockOutLineId,
@@ -775,52 +775,52 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
newInventoryLotLineId: newLotLineId newInventoryLotLineId: newLotLineId
}); });
console.log(" Lot substitution result:", substitutionResult);
console.log(" Lot substitution result:", substitutionResult);
// Update stock out line status to 'checked' after substitution
// Update stock out line status to 'checked' after substitution
if(selectedLotForQr?.stockOutLineId){ if(selectedLotForQr?.stockOutLineId){
await updateStockOutLineStatus({ await updateStockOutLineStatus({
id: selectedLotForQr.stockOutLineId, id: selectedLotForQr.stockOutLineId,
status: 'checked', status: 'checked',
qty: 0 qty: 0
}); });
console.log(" Stock out line status updated to 'checked'");
console.log(" Stock out line status updated to 'checked'");
} }
// Close modal and clean up state BEFORE refreshing
// Close modal and clean up state BEFORE refreshing
setLotConfirmationOpen(false); setLotConfirmationOpen(false);
setExpectedLotData(null); setExpectedLotData(null);
setScannedLotData(null); setScannedLotData(null);
setSelectedLotForQr(null); setSelectedLotForQr(null);
// Clear QR processing state but DON'T clear processedQrCodes yet
// Clear QR processing state but DON'T clear processedQrCodes yet
setQrScanError(false); setQrScanError(false);
setQrScanSuccess(true); setQrScanSuccess(true);
setQrScanInput(''); setQrScanInput('');
// Set refreshing flag to prevent QR processing during refresh
// Set refreshing flag to prevent QR processing during refresh
setIsRefreshingData(true); setIsRefreshingData(true);
// Refresh data to show updated lot
// Refresh data to show updated lot
console.log("🔄 Refreshing job order data..."); console.log("🔄 Refreshing job order data...");
await fetchJobOrderData(); await fetchJobOrderData();
console.log(" Lot substitution confirmed and data refreshed");
console.log(" Lot substitution confirmed and data refreshed");
// Clear processed QR codes and flags immediately after refresh
// Clear processed QR codes and flags immediately after refresh
// This allows new QR codes to be processed right away // This allows new QR codes to be processed right away
setTimeout(() => { setTimeout(() => {
console.log(" Clearing processed QR codes and resuming scan");
console.log(" Clearing processed QR codes and resuming scan");
setProcessedQrCodes(new Set()); setProcessedQrCodes(new Set());
setLastProcessedQr(''); setLastProcessedQr('');
setQrScanSuccess(false); setQrScanSuccess(false);
setIsRefreshingData(false); setIsRefreshingData(false);
}, 500); // Reduced from 3000ms to 500ms - just enough for UI update
}, 500); // Reduced from 3000ms to 500ms - just enough for UI update
} catch (error) { } catch (error) {
console.error("Error confirming lot substitution:", error); console.error("Error confirming lot substitution:", error);
setQrScanError(true); setQrScanError(true);
setQrScanSuccess(false); setQrScanSuccess(false);
// Clear refresh flag on error
// Clear refresh flag on error
setIsRefreshingData(false); setIsRefreshingData(false);
} finally { } finally {
setIsConfirmingLot(false); setIsConfirmingLot(false);
@@ -828,7 +828,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
}, [expectedLotData, scannedLotData, selectedLotForQr, fetchJobOrderData]); }, [expectedLotData, scannedLotData, selectedLotForQr, fetchJobOrderData]);


const processOutsideQrCode = useCallback(async (latestQr: string) => { const processOutsideQrCode = useCallback(async (latestQr: string) => {
// Don't process if confirmation modal is open
// Don't process if confirmation modal is open
if (lotConfirmationOpen) { if (lotConfirmationOpen) {
console.log("⏸️ Confirmation modal is open, skipping QR processing"); console.log("⏸️ Confirmation modal is open, skipping QR processing");
return; return;
@@ -857,7 +857,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
return; return;
} }
// First, fetch stock in line info to get the lot number
// First, fetch stock in line info to get the lot number
let stockInLineInfo: any; let stockInLineInfo: any;
try { try {
stockInLineInfo = await fetchStockInLineInfo(qrData.stockInLineId); stockInLineInfo = await fetchStockInLineInfo(qrData.stockInLineId);
@@ -923,7 +923,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
// 2) Check if the scanned lot matches exactly // 2) Check if the scanned lot matches exactly
if (scanned?.lotNo === expectedLot.lotNo) { if (scanned?.lotNo === expectedLot.lotNo) {
// Case 1: Exact match - process normally // Case 1: Exact match - process normally
console.log(` Exact lot match: ${scanned.lotNo}`);
console.log(` Exact lot match: ${scanned.lotNo}`);
await handleQrCodeSubmit(scanned.lotNo); await handleQrCodeSubmit(scanned.lotNo);
return; return;
} }
@@ -931,7 +931,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
// Case 2: Same item, different lot - show confirmation modal // Case 2: Same item, different lot - show confirmation modal
console.log(`🔍 Lot mismatch: Expected ${expectedLot.lotNo}, Scanned ${scanned?.lotNo}`); console.log(`🔍 Lot mismatch: Expected ${expectedLot.lotNo}, Scanned ${scanned?.lotNo}`);
// DON'T stop scanning - just pause QR processing by showing modal
// DON'T stop scanning - just pause QR processing by showing modal
setSelectedLotForQr(expectedLot); setSelectedLotForQr(expectedLot);
handleLotMismatch( handleLotMismatch(
{ {
@@ -962,10 +962,10 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [qrScanInput, handleQrCodeSubmit]); }, [qrScanInput, handleQrCodeSubmit]);


// Handle QR code submission from modal (internal scanning)
// Handle QR code submission from modal (internal scanning)
const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => { const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => {
if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) { if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) {
console.log(` QR Code verified for lot: ${lotNo}`);
console.log(` QR Code verified for lot: ${lotNo}`);
const requiredQty = selectedLotForQr.requiredQty; const requiredQty = selectedLotForQr.requiredQty;
const lotId = selectedLotForQr.lotId; const lotId = selectedLotForQr.lotId;
@@ -993,7 +993,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
...prev, ...prev,
[lotKey]: requiredQty [lotKey]: requiredQty
})); }));
console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
}, 500); }, 500);
// Refresh data // Refresh data
@@ -1006,7 +1006,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {


useEffect(() => { useEffect(() => {
// Add isManualScanning check
// Add isManualScanning check
if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData || lotConfirmationOpen) { if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData || lotConfirmationOpen) {
return; return;
} }
@@ -1064,7 +1064,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) { if (completionResponse.code === "SUCCESS" && completionResponse.entity?.hasCompletedOrders) {
console.log("Found completed pick orders, auto-assigning next..."); console.log("Found completed pick orders, auto-assigning next...");
// 移除前端的自动分配逻辑,因为后端已经处理了
// 移除前端的自动分配逻辑,因为后端已经处理了
// await handleAutoAssignAndRelease(); // 删除这个函数 // await handleAutoAssignAndRelease(); // 删除这个函数
} }
} catch (error) { } catch (error) {
@@ -1072,7 +1072,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [currentUserId]); }, [currentUserId]);


// Handle submit pick quantity
// Handle submit pick quantity
const handleSubmitPickQty = useCallback(async (lot: any) => { const handleSubmitPickQty = useCallback(async (lot: any) => {
const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`;
const newQty = pickQtyData[lotKey] || 0; const newQty = pickQtyData[lotKey] || 0;
@@ -1116,14 +1116,14 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
}); });
} }
// FIXED: Use the proper API function instead of direct fetch
// FIXED: Use the proper API function instead of direct fetch
if (newStatus === 'completed' && lot.pickOrderConsoCode) { if (newStatus === 'completed' && lot.pickOrderConsoCode) {
console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
try { try {
// Use the imported API function instead of direct fetch
// Use the imported API function instead of direct fetch
const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
console.log(` Pick order completion check result:`, completionResponse);
console.log(` Pick order completion check result:`, completionResponse);
if (completionResponse.code === "SUCCESS") { if (completionResponse.code === "SUCCESS") {
console.log(` Pick order ${lot.pickOrderConsoCode} completed successfully!`); console.log(` Pick order ${lot.pickOrderConsoCode} completed successfully!`);
@@ -1155,11 +1155,11 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
try { try {
// FIXED: Calculate cumulative quantity correctly
// FIXED: Calculate cumulative quantity correctly
const currentActualPickQty = lot.actualPickQty || 0; const currentActualPickQty = lot.actualPickQty || 0;
const cumulativeQty = currentActualPickQty + submitQty; const cumulativeQty = currentActualPickQty + submitQty;
// FIXED: Determine status based on cumulative quantity vs required quantity
// FIXED: Determine status based on cumulative quantity vs required quantity
let newStatus = 'partially_completed'; let newStatus = 'partially_completed';
if (cumulativeQty >= lot.requiredQty) { if (cumulativeQty >= lot.requiredQty) {
@@ -1182,7 +1182,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
await updateStockOutLineStatus({ await updateStockOutLineStatus({
id: lot.stockOutLineId, id: lot.stockOutLineId,
status: newStatus, status: newStatus,
qty: cumulativeQty // Use cumulative quantity
qty: cumulativeQty // Use cumulative quantity
}); });
if (submitQty > 0) { if (submitQty > 0) {
@@ -1194,13 +1194,13 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
}); });
} }
// Check if pick order is completed when lot status becomes 'completed'
// Check if pick order is completed when lot status becomes 'completed'
if (newStatus === 'completed' && lot.pickOrderConsoCode) { if (newStatus === 'completed' && lot.pickOrderConsoCode) {
console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
console.log(` Lot ${lot.lotNo} completed, checking if pick order ${lot.pickOrderConsoCode} is complete...`);
try { try {
const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode); const completionResponse = await checkAndCompletePickOrderByConsoCode(lot.pickOrderConsoCode);
console.log(` Pick order completion check result:`, completionResponse);
console.log(` Pick order completion check result:`, completionResponse);
if (completionResponse.code === "SUCCESS") { if (completionResponse.code === "SUCCESS") {
console.log(` Pick order ${lot.pickOrderConsoCode} completed successfully!`); console.log(` Pick order ${lot.pickOrderConsoCode} completed successfully!`);
@@ -1239,7 +1239,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`); console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`);
try { try {
// Submit all items in parallel using Promise.all
// Submit all items in parallel using Promise.all
const submitPromises = scannedLots.map(async (lot) => { const submitPromises = scannedLots.map(async (lot) => {
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
const currentActualPickQty = lot.actualPickQty || 0; const currentActualPickQty = lot.actualPickQty || 0;
@@ -1277,13 +1277,13 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
return { success: true, lotNo: lot.lotNo }; return { success: true, lotNo: lot.lotNo };
}); });
// Wait for all submissions to complete
// Wait for all submissions to complete
const results = await Promise.all(submitPromises); const results = await Promise.all(submitPromises);
const successCount = results.filter(r => r.success).length; const successCount = results.filter(r => r.success).length;
console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`);
console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`);
// Refresh data once after all submissions
// Refresh data once after all submissions
await fetchJobOrderData(); await fetchJobOrderData();
if (successCount > 0) { if (successCount > 0) {
@@ -1302,11 +1302,11 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [combinedLotData, fetchJobOrderData, checkAndAutoAssignNext]); }, [combinedLotData, fetchJobOrderData, checkAndAutoAssignNext]);


// Calculate scanned items count
// Calculate scanned items count
const scannedItemsCount = useMemo(() => { const scannedItemsCount = useMemo(() => {
return combinedLotData.filter(lot => lot.stockOutLineStatus === 'checked').length; return combinedLotData.filter(lot => lot.stockOutLineStatus === 'checked').length;
}, [combinedLotData]); }, [combinedLotData]);
// Handle reject lot
// Handle reject lot
const handleRejectLot = useCallback(async (lot: any) => { const handleRejectLot = useCallback(async (lot: any) => {
if (!lot.stockOutLineId) { if (!lot.stockOutLineId) {
console.error("No stock out line found for this lot"); console.error("No stock out line found for this lot");
@@ -1332,7 +1332,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [fetchJobOrderData, checkAndAutoAssignNext]); }, [fetchJobOrderData, checkAndAutoAssignNext]);


// Handle pick execution form
// Handle pick execution form
const handlePickExecutionForm = useCallback((lot: any) => { const handlePickExecutionForm = useCallback((lot: any) => {
console.log("=== Pick Execution Form ==="); console.log("=== Pick Execution Form ===");
console.log("Lot data:", lot); console.log("Lot data:", lot);
@@ -1362,7 +1362,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
console.log("Pick execution issue recorded:", result); console.log("Pick execution issue recorded:", result);
if (result && result.code === "SUCCESS") { if (result && result.code === "SUCCESS") {
console.log(" Pick execution issue recorded successfully");
console.log(" Pick execution issue recorded successfully");
} else { } else {
console.error("❌ Failed to record pick execution issue:", result); console.error("❌ Failed to record pick execution issue:", result);
} }
@@ -1376,7 +1376,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [fetchJobOrderData]); }, [fetchJobOrderData]);


// Calculate remaining required quantity
// Calculate remaining required quantity
const calculateRemainingRequiredQty = useCallback((lot: any) => { const calculateRemainingRequiredQty = useCallback((lot: any) => {
const requiredQty = lot.requiredQty || 0; const requiredQty = lot.requiredQty || 0;
const stockOutLineQty = lot.stockOutLineQty || 0; const stockOutLineQty = lot.stockOutLineQty || 0;
@@ -1457,7 +1457,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {


// Pagination data with sorting by routerIndex // Pagination data with sorting by routerIndex
const paginatedData = useMemo(() => { const paginatedData = useMemo(() => {
// Sort by routerIndex first, then by other criteria
// Sort by routerIndex first, then by other criteria
const sortedData = [...combinedLotData].sort((a, b) => { const sortedData = [...combinedLotData].sort((a, b) => {
const aIndex = a.routerIndex || 0; const aIndex = a.routerIndex || 0;
const bIndex = b.routerIndex || 0; const bIndex = b.routerIndex || 0;
@@ -1481,7 +1481,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
return sortedData.slice(startIndex, endIndex); return sortedData.slice(startIndex, endIndex);
}, [combinedLotData, paginationController]); }, [combinedLotData, paginationController]);


// Add these functions for manual scanning
// Add these functions for manual scanning
const handleStartScan = useCallback(() => { const handleStartScan = useCallback(() => {
console.log(" Starting manual QR scan..."); console.log(" Starting manual QR scan...");
setIsManualScanning(true); setIsManualScanning(true);
@@ -1517,7 +1517,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [combinedLotData.length, isManualScanning, handleStopScan]); }, [combinedLotData.length, isManualScanning, handleStopScan]);


// Cleanup effect
// Cleanup effect
useEffect(() => { useEffect(() => {
return () => { return () => {
// Cleanup when component unmounts (e.g., when switching tabs) // Cleanup when component unmounts (e.g., when switching tabs)
@@ -1605,7 +1605,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
{t("Stop QR Scan")} {t("Stop QR Scan")}
</Button> </Button>
)} )}
{/* ADD THIS: Submit All Scanned Button */}
{/* ADD THIS: Submit All Scanned Button */}
<Button <Button
variant="contained" variant="contained"
color="success" color="success"
@@ -1750,7 +1750,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
lot.lotAvailability === 'status_unavailable' || lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected') || lot.lotAvailability === 'rejected') ||
lot.stockOutLineStatus === 'completed' || lot.stockOutLineStatus === 'completed' ||
lot.stockOutLineStatus === 'pending' // Disable when QR scan not passed
lot.stockOutLineStatus === 'pending' // Disable when QR scan not passed
} }
sx={{ sx={{
fontSize: '0.75rem', fontSize: '0.75rem',
@@ -1770,8 +1770,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
(lot.lotAvailability === 'expired' || (lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' || lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected') || lot.lotAvailability === 'rejected') ||
lot.stockOutLineStatus === 'completed' || // Disable when finished
lot.stockOutLineStatus === 'pending' // Disable when QR scan not passed
lot.stockOutLineStatus === 'completed' || // Disable when finished
lot.stockOutLineStatus === 'pending' // Disable when QR scan not passed
} }
sx={{ sx={{
fontSize: '0.7rem', fontSize: '0.7rem',
@@ -1811,7 +1811,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
</Box> </Box>
</Stack> </Stack>


{/* QR Code Modal */}
{/* QR Code Modal */}
{!lotConfirmationOpen && ( {!lotConfirmationOpen && (
<QrCodeModal <QrCodeModal
open={qrModalOpen} open={qrModalOpen}
@@ -1826,7 +1826,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
onQrCodeSubmit={handleQrCodeSubmitFromModal} onQrCodeSubmit={handleQrCodeSubmitFromModal}
/> />
)} )}
{/* Add Lot Confirmation Modal */}
{/* Add Lot Confirmation Modal */}
{lotConfirmationOpen && expectedLotData && scannedLotData && ( {lotConfirmationOpen && expectedLotData && scannedLotData && (
<LotConfirmationModal <LotConfirmationModal
open={lotConfirmationOpen} open={lotConfirmationOpen}
@@ -1843,7 +1843,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
isLoading={isConfirmingLot} isLoading={isConfirmingLot}
/> />
)} )}
{/* Pick Execution Form Modal */}
{/* Pick Execution Form Modal */}
{pickExecutionFormOpen && selectedLotForExecutionForm && ( {pickExecutionFormOpen && selectedLotForExecutionForm && (
<GoodPickExecutionForm <GoodPickExecutionForm
open={pickExecutionFormOpen} open={pickExecutionFormOpen}
@@ -1859,13 +1859,13 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
itemCode: selectedLotForExecutionForm.itemCode, itemCode: selectedLotForExecutionForm.itemCode,
itemName: selectedLotForExecutionForm.itemName, itemName: selectedLotForExecutionForm.itemName,
pickOrderCode: selectedLotForExecutionForm.pickOrderCode, pickOrderCode: selectedLotForExecutionForm.pickOrderCode,
// Add missing required properties from GetPickOrderLineInfo interface
// Add missing required properties from GetPickOrderLineInfo interface
availableQty: selectedLotForExecutionForm.availableQty || 0, availableQty: selectedLotForExecutionForm.availableQty || 0,
requiredQty: selectedLotForExecutionForm.requiredQty || 0, requiredQty: selectedLotForExecutionForm.requiredQty || 0,
uomCode: selectedLotForExecutionForm.uomCode || '', uomCode: selectedLotForExecutionForm.uomCode || '',
uomDesc: selectedLotForExecutionForm.uomDesc || '', uomDesc: selectedLotForExecutionForm.uomDesc || '',
pickedQty: selectedLotForExecutionForm.actualPickQty || 0, // Use pickedQty instead of actualPickQty
suggestedList: [] // Add required suggestedList property
pickedQty: selectedLotForExecutionForm.actualPickQty || 0, // Use pickedQty instead of actualPickQty
suggestedList: [] // Add required suggestedList property
}} }}
pickOrderId={selectedLotForExecutionForm.pickOrderId} pickOrderId={selectedLotForExecutionForm.pickOrderId}
pickOrderCreateDate={new Date()} pickOrderCreateDate={new Date()}


+ 20
- 20
src/components/Jodetail/JobPickExecutionForm.tsx View File

@@ -54,7 +54,7 @@ interface PickExecutionFormProps {
selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null; selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null;
pickOrderId?: number; pickOrderId?: number;
pickOrderCreateDate: any; pickOrderCreateDate: any;
// Remove these props since we're not handling normal cases
// Remove these props since we're not handling normal cases
// onNormalPickSubmit?: (lineId: number, lotId: number, qty: number) => Promise<void>; // onNormalPickSubmit?: (lineId: number, lotId: number, qty: number) => Promise<void>;
// selectedRowId?: number | null; // selectedRowId?: number | null;
} }
@@ -76,7 +76,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
selectedPickOrderLine, selectedPickOrderLine,
pickOrderId, pickOrderId,
pickOrderCreateDate, pickOrderCreateDate,
// Remove these props
// Remove these props
// onNormalPickSubmit, // onNormalPickSubmit,
// selectedRowId, // selectedRowId,
}) => { }) => {
@@ -91,7 +91,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
return lot.availableQty || 0; return lot.availableQty || 0;
}, []); }, []);
const calculateRequiredQty = useCallback((lot: LotPickData) => { const calculateRequiredQty = useCallback((lot: LotPickData) => {
// Use the original required quantity, not subtracting actualPickQty
// Use the original required quantity, not subtracting actualPickQty
// The actualPickQty in the form should be independent of the database value // The actualPickQty in the form should be independent of the database value
return lot.requiredQty || 0; return lot.requiredQty || 0;
}, []); }, []);
@@ -126,7 +126,7 @@ useEffect(() => {
} }
}; };


// Initialize verified quantity to the received quantity (actualPickQty)
// Initialize verified quantity to the received quantity (actualPickQty)
const initialVerifiedQty = selectedLot.actualPickQty || 0; const initialVerifiedQty = selectedLot.actualPickQty || 0;
setVerifiedQty(initialVerifiedQty); setVerifiedQty(initialVerifiedQty);
@@ -155,14 +155,14 @@ useEffect(() => {
handledBy: undefined, handledBy: undefined,
}); });
} }
// 只在 open 状态改变时重新初始化,移除其他依赖
// 只在 open 状态改变时重新初始化,移除其他依赖
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]); }, [open]);


const handleInputChange = useCallback((field: keyof PickExecutionIssueData, value: any) => { const handleInputChange = useCallback((field: keyof PickExecutionIssueData, value: any) => {
setFormData(prev => ({ ...prev, [field]: value })); setFormData(prev => ({ ...prev, [field]: value }));
// Update verified quantity state when actualPickQty changes
// Update verified quantity state when actualPickQty changes
if (field === 'actualPickQty') { if (field === 'actualPickQty') {
setVerifiedQty(value); setVerifiedQty(value);
} }
@@ -173,7 +173,7 @@ useEffect(() => {
} }
}, [errors]); }, [errors]);


// Update form validation to require either missQty > 0 OR badItemQty > 0
// Update form validation to require either missQty > 0 OR badItemQty > 0
const validateForm = (): boolean => { const validateForm = (): boolean => {
const newErrors: FormErrors = {}; const newErrors: FormErrors = {};
@@ -185,16 +185,16 @@ useEffect(() => {
newErrors.actualPickQty = t('Qty is required'); newErrors.actualPickQty = t('Qty is required');
} }
// 移除接收数量检查,因为在 JobPickExecution 阶段 receivedQty 总是 0
// 移除接收数量检查,因为在 JobPickExecution 阶段 receivedQty 总是 0
// if (verifiedQty > receivedQty) { ... } ← 删除 // if (verifiedQty > receivedQty) { ... } ← 删除
// 只检查总和是否等于需求数量
// 只检查总和是否等于需求数量
const totalQty = verifiedQty + badItemQty + missQty; const totalQty = verifiedQty + badItemQty + missQty;
if (totalQty !== requiredQty) { if (totalQty !== requiredQty) {
newErrors.actualPickQty = t('Total (Verified + Bad + Missing) must equal Required quantity'); newErrors.actualPickQty = t('Total (Verified + Bad + Missing) must equal Required quantity');
} }
// Require either missQty > 0 OR badItemQty > 0
// Require either missQty > 0 OR badItemQty > 0
const hasMissQty = formData.missQty && formData.missQty > 0; const hasMissQty = formData.missQty && formData.missQty > 0;
const hasBadItemQty = formData.badItemQty && formData.badItemQty > 0; const hasBadItemQty = formData.badItemQty && formData.badItemQty > 0;
@@ -213,7 +213,7 @@ useEffect(() => {


setLoading(true); setLoading(true);
try { try {
// Use the verified quantity in the submission
// Use the verified quantity in the submission
const submissionData = { const submissionData = {
...formData, ...formData,
actualPickQty: verifiedQty, actualPickQty: verifiedQty,
@@ -249,11 +249,11 @@ useEffect(() => {
return ( return (
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth> <Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
<DialogTitle> <DialogTitle>
{t('Pick Execution Issue Form')} {/* Always show issue form title */}
{t('Pick Execution Issue Form')} {/* Always show issue form title */}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<Box sx={{ mt: 2 }}> <Box sx={{ mt: 2 }}>
{/* Add instruction text */}
{/* Add instruction text */}
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12}> <Grid item xs={12}>
<Box sx={{ p: 2, backgroundColor: '#fff3cd', borderRadius: 1, mb: 2 }}> <Box sx={{ p: 2, backgroundColor: '#fff3cd', borderRadius: 1, mb: 2 }}>
@@ -263,7 +263,7 @@ useEffect(() => {
</Box> </Box>
</Grid> </Grid>
{/* Keep the existing form fields */}
{/* Keep the existing form fields */}
<Grid item xs={6}> <Grid item xs={6}>
<TextField <TextField
fullWidth fullWidth
@@ -297,7 +297,7 @@ useEffect(() => {
// handleInputChange('actualPickQty', newValue); // handleInputChange('actualPickQty', newValue);
}} }}
error={!!errors.actualPickQty} error={!!errors.actualPickQty}
helperText={errors.actualPickQty || `${t('Max')}: ${selectedLot?.actualPickQty || 0}`} // 使用原始接收数量
helperText={errors.actualPickQty || `${t('Max')}: ${selectedLot?.actualPickQty || 0}`} // 使用原始接收数量
variant="outlined" variant="outlined"
/> />
</Grid> </Grid>
@@ -311,7 +311,7 @@ useEffect(() => {
onChange={(e) => { onChange={(e) => {
const newMissQty = parseFloat(e.target.value) || 0; const newMissQty = parseFloat(e.target.value) || 0;
handleInputChange('missQty', newMissQty); handleInputChange('missQty', newMissQty);
// 不要自动修改其他字段
// 不要自动修改其他字段
}} }}
error={!!errors.missQty} error={!!errors.missQty}
helperText={errors.missQty} helperText={errors.missQty}
@@ -328,7 +328,7 @@ useEffect(() => {
onChange={(e) => { onChange={(e) => {
const newBadItemQty = parseFloat(e.target.value) || 0; const newBadItemQty = parseFloat(e.target.value) || 0;
handleInputChange('badItemQty', newBadItemQty); handleInputChange('badItemQty', newBadItemQty);
// 不要自动修改其他字段
// 不要自动修改其他字段
}} }}
error={!!errors.badItemQty} error={!!errors.badItemQty}
helperText={errors.badItemQty} helperText={errors.badItemQty}
@@ -336,7 +336,7 @@ useEffect(() => {
/> />
</Grid> </Grid>
{/* Show issue description and handler fields when bad items > 0 */}
{/* Show issue description and handler fields when bad items > 0 */}
{(formData.badItemQty && formData.badItemQty > 0) ? ( {(formData.badItemQty && formData.badItemQty > 0) ? (
<> <>
<Grid item xs={12}> <Grid item xs={12}>
@@ -349,7 +349,7 @@ useEffect(() => {
value={formData.issueRemark || ''} value={formData.issueRemark || ''}
onChange={(e) => { onChange={(e) => {
handleInputChange('issueRemark', e.target.value); handleInputChange('issueRemark', e.target.value);
// Don't reset badItemQty when typing in issue remark
// Don't reset badItemQty when typing in issue remark
}} }}
error={!!errors.issueRemark} error={!!errors.issueRemark}
helperText={errors.issueRemark} helperText={errors.issueRemark}
@@ -365,7 +365,7 @@ useEffect(() => {
value={formData.handledBy ? formData.handledBy.toString() : ''} value={formData.handledBy ? formData.handledBy.toString() : ''}
onChange={(e) => { onChange={(e) => {
handleInputChange('handledBy', e.target.value ? parseInt(e.target.value) : undefined); handleInputChange('handledBy', e.target.value ? parseInt(e.target.value) : undefined);
// Don't reset badItemQty when selecting handler
// Don't reset badItemQty when selecting handler
}} }}
label={t('handler')} label={t('handler')}
> >


+ 73
- 73
src/components/Jodetail/JobPickExecutionsecondscan.tsx View File

@@ -24,7 +24,7 @@ import { useCallback, useEffect, useState, useRef, useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";


// 修改:使用 Job Order API
// 修改:使用 Job Order API
import { import {
fetchCompletedJobOrderPickOrders, fetchCompletedJobOrderPickOrders,
fetchUnassignedJobOrderPickOrders, fetchUnassignedJobOrderPickOrders,
@@ -54,7 +54,7 @@ interface Props {
filterArgs: Record<string, any>; filterArgs: Record<string, any>;
} }


// QR Code Modal Component (from GoodPickExecution)
// QR Code Modal Component (from GoodPickExecution)
const QrCodeModal: React.FC<{ const QrCodeModal: React.FC<{
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
@@ -101,7 +101,7 @@ const QrCodeModal: React.FC<{
setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number'); setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number');
if (stockInLineInfo.lotNo === lot.lotNo) { if (stockInLineInfo.lotNo === lot.lotNo) {
console.log(` QR Code verified for lot: ${lot.lotNo}`);
console.log(` QR Code verified for lot: ${lot.lotNo}`);
setQrScanSuccess(true); setQrScanSuccess(true);
onQrCodeSubmit(lot.lotNo); onQrCodeSubmit(lot.lotNo);
onClose(); onClose();
@@ -293,7 +293,7 @@ const QrCodeModal: React.FC<{


{qrScanSuccess && ( {qrScanSuccess && (
<Typography variant="caption" color="success" display="block"> <Typography variant="caption" color="success" display="block">
{t("Verified successfully!")}
{t("Verified successfully!")}
</Typography> </Typography>
)} )}
</Box> </Box>
@@ -316,13 +316,13 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
const currentUserId = session?.id ? parseInt(session.id) : undefined; const currentUserId = session?.id ? parseInt(session.id) : undefined;
// 修改:使用 Job Order 数据结构
// 修改:使用 Job Order 数据结构
const [jobOrderData, setJobOrderData] = useState<any>(null); const [jobOrderData, setJobOrderData] = useState<any>(null);
const [combinedLotData, setCombinedLotData] = useState<any[]>([]); const [combinedLotData, setCombinedLotData] = useState<any[]>([]);
const [combinedDataLoading, setCombinedDataLoading] = useState(false); const [combinedDataLoading, setCombinedDataLoading] = useState(false);
const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]); const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]);
// 添加未分配订单状态
// 添加未分配订单状态
const [unassignedOrders, setUnassignedOrders] = useState<any[]>([]); const [unassignedOrders, setUnassignedOrders] = useState<any[]>([]);
const [isLoadingUnassigned, setIsLoadingUnassigned] = useState(false); const [isLoadingUnassigned, setIsLoadingUnassigned] = useState(false);
@@ -348,20 +348,20 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
const formProps = useForm(); const formProps = useForm();
const errors = formProps.formState.errors; const errors = formProps.formState.errors;


// Add QR modal states
// Add QR modal states
const [qrModalOpen, setQrModalOpen] = useState(false); const [qrModalOpen, setQrModalOpen] = useState(false);
const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null); const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null);


// Add GoodPickExecutionForm states
// Add GoodPickExecutionForm states
const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null); const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null);
// Add these missing state variables
// Add these missing state variables
const [isManualScanning, setIsManualScanning] = useState<boolean>(false); const [isManualScanning, setIsManualScanning] = useState<boolean>(false);
const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set()); const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
const [lastProcessedQr, setLastProcessedQr] = useState<string>(''); const [lastProcessedQr, setLastProcessedQr] = useState<string>('');
const [isRefreshingData, setIsRefreshingData] = useState<boolean>(false); const [isRefreshingData, setIsRefreshingData] = useState<boolean>(false);
// 修改:加载未分配的 Job Order 订单
// 修改:加载未分配的 Job Order 订单
const loadUnassignedOrders = useCallback(async () => { const loadUnassignedOrders = useCallback(async () => {
setIsLoadingUnassigned(true); setIsLoadingUnassigned(true);
try { try {
@@ -374,7 +374,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, []); }, []);


// 修改:分配订单给当前用户
// 修改:分配订单给当前用户
const handleAssignOrder = useCallback(async (pickOrderId: number) => { const handleAssignOrder = useCallback(async (pickOrderId: number) => {
if (!currentUserId) { if (!currentUserId) {
console.error("Missing user id in session"); console.error("Missing user id in session");
@@ -384,7 +384,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
try { try {
const result = await assignJobOrderPickOrder(pickOrderId, currentUserId); const result = await assignJobOrderPickOrder(pickOrderId, currentUserId);
if (result.message === "Successfully assigned") { if (result.message === "Successfully assigned") {
console.log(" Successfully assigned pick order");
console.log(" Successfully assigned pick order");
// 刷新数据 // 刷新数据
window.dispatchEvent(new CustomEvent('pickOrderAssigned')); window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
// 重新加载未分配订单列表 // 重新加载未分配订单列表
@@ -400,13 +400,13 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
}, [currentUserId, loadUnassignedOrders]); }, [currentUserId, loadUnassignedOrders]);




// Handle QR code button click
// Handle QR code button click
const handleQrCodeClick = (pickOrderId: number) => { const handleQrCodeClick = (pickOrderId: number) => {
console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); console.log(`QR Code clicked for pick order ID: ${pickOrderId}`);
// TODO: Implement QR code functionality // TODO: Implement QR code functionality
}; };


// 修改:使用 Job Order API 获取数据
// 修改:使用 Job Order API 获取数据
const fetchJobOrderData = useCallback(async (userId?: number) => { const fetchJobOrderData = useCallback(async (userId?: number) => {
setCombinedDataLoading(true); setCombinedDataLoading(true);
try { try {
@@ -427,15 +427,15 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
tabIndex: 1 tabIndex: 1
} }
})); }));
// 使用 Job Order API
// 使用 Job Order API
const jobOrderData = await fetchCompletedJobOrderPickOrders(userIdToUse); const jobOrderData = await fetchCompletedJobOrderPickOrders(userIdToUse);
console.log(" Job Order data:", jobOrderData);
console.log(" Pick Order Code from API:", jobOrderData.pickOrder?.code);
console.log(" Expected Pick Order Code: P-20251003-001");
console.log(" Job Order data:", jobOrderData);
console.log(" Pick Order Code from API:", jobOrderData.pickOrder?.code);
console.log(" Expected Pick Order Code: P-20251003-001");
setJobOrderData(jobOrderData); setJobOrderData(jobOrderData);
// Transform hierarchical data to flat structure for the table
// Transform hierarchical data to flat structure for the table
const flatLotData: any[] = []; const flatLotData: any[] = [];
if (jobOrderData.pickOrder && jobOrderData.pickOrderLines) { if (jobOrderData.pickOrder && jobOrderData.pickOrderLines) {
@@ -491,7 +491,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
}); });
} }
console.log(" Transformed flat lot data:", flatLotData);
console.log(" Transformed flat lot data:", flatLotData);
setCombinedLotData(flatLotData); setCombinedLotData(flatLotData);
setOriginalCombinedData(flatLotData); setOriginalCombinedData(flatLotData);
const hasData = flatLotData.length > 0; const hasData = flatLotData.length > 0;
@@ -501,7 +501,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
tabIndex: 1 tabIndex: 1
} }
})); }));
// 计算完成状态并发送事件
// 计算完成状态并发送事件
const allCompleted = flatLotData.length > 0 && flatLotData.every((lot: any) => const allCompleted = flatLotData.length > 0 && flatLotData.every((lot: any) =>
lot.processingStatus === 'completed' lot.processingStatus === 'completed'
); );
@@ -511,11 +511,11 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
tabIndex: 1 tabIndex: 1
} }
})); }));
// 发送完成状态事件,包含标签页信息
// 发送完成状态事件,包含标签页信息
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: { detail: {
allLotsCompleted: allCompleted, allLotsCompleted: allCompleted,
tabIndex: 0 // 明确指定这是来自标签页 0 的事件
tabIndex: 0 // 明确指定这是来自标签页 0 的事件
} }
})); }));
@@ -525,7 +525,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
setCombinedLotData([]); setCombinedLotData([]);
setOriginalCombinedData([]); setOriginalCombinedData([]);
// 如果加载失败,禁用打印按钮
// 如果加载失败,禁用打印按钮
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: { detail: {
allLotsCompleted: false, allLotsCompleted: false,
@@ -550,7 +550,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`); console.log(`📦 Submitting ${scannedLots.length} scanned items in parallel...`);
try { try {
// Submit all items in parallel using Promise.all
// Submit all items in parallel using Promise.all
const submitPromises = scannedLots.map(async (lot) => { const submitPromises = scannedLots.map(async (lot) => {
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty;
@@ -570,13 +570,13 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
return { success: result.code === "SUCCESS", itemCode: lot.itemCode }; return { success: result.code === "SUCCESS", itemCode: lot.itemCode };
}); });
// Wait for all submissions to complete
// Wait for all submissions to complete
const results = await Promise.all(submitPromises); const results = await Promise.all(submitPromises);
const successCount = results.filter(r => r.success).length; const successCount = results.filter(r => r.success).length;
console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`);
console.log(` Batch submit completed: ${successCount}/${scannedLots.length} items submitted`);
// Refresh data once after all submissions
// Refresh data once after all submissions
await fetchJobOrderData(); await fetchJobOrderData();
if (successCount > 0) { if (successCount > 0) {
@@ -592,15 +592,15 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [combinedLotData, fetchJobOrderData]); }, [combinedLotData, fetchJobOrderData]);


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


// 修改:初始化时加载数据
// 修改:初始化时加载数据
useEffect(() => { useEffect(() => {
if (session && currentUserId && !initializationRef.current) { if (session && currentUserId && !initializationRef.current) {
console.log(" Session loaded, initializing job order...");
console.log(" Session loaded, initializing job order...");
initializationRef.current = true; initializationRef.current = true;
// 加载 Job Order 数据 // 加载 Job Order 数据
@@ -610,7 +610,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [session, currentUserId, fetchJobOrderData, loadUnassignedOrders]); }, [session, currentUserId, fetchJobOrderData, loadUnassignedOrders]);


// Add event listener for manual assignment
// Add event listener for manual assignment
useEffect(() => { useEffect(() => {
const handlePickOrderAssigned = () => { const handlePickOrderAssigned = () => {
console.log("🔄 Pick order assigned event received, refreshing data..."); console.log("🔄 Pick order assigned event received, refreshing data...");
@@ -624,11 +624,11 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
}; };
}, [fetchJobOrderData]); }, [fetchJobOrderData]);


// Handle QR code submission for matched lot (external scanning)
// Handle QR code submission for matched lot (external scanning)
const handleQrCodeSubmit = useCallback(async (lotNo: string) => { const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
console.log(` Processing Second QR Code for lot: ${lotNo}`);
console.log(` Processing Second QR Code for lot: ${lotNo}`);
// Check if this lot was already processed recently
// Check if this lot was already processed recently
const lotKey = `${lotNo}_${Date.now()}`; const lotKey = `${lotNo}_${Date.now()}`;
if (processedQrCodes.has(lotNo)) { if (processedQrCodes.has(lotNo)) {
console.log(`⏭️ Lot ${lotNo} already processed, skipping...`); console.log(`⏭️ Lot ${lotNo} already processed, skipping...`);
@@ -652,26 +652,26 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
let successCount = 0; let successCount = 0;
for (const matchingLot of matchingLots) { for (const matchingLot of matchingLots) {
// Check if this specific item was already processed
// Check if this specific item was already processed
const itemKey = `${matchingLot.pickOrderId}_${matchingLot.itemId}`; const itemKey = `${matchingLot.pickOrderId}_${matchingLot.itemId}`;
if (processedQrCodes.has(itemKey)) { if (processedQrCodes.has(itemKey)) {
console.log(`⏭️ Item ${matchingLot.itemId} already processed, skipping...`); console.log(`⏭️ Item ${matchingLot.itemId} already processed, skipping...`);
continue; continue;
} }
// Use the new second scan API
// Use the new second scan API
const result = await updateSecondQrScanStatus( const result = await updateSecondQrScanStatus(
matchingLot.pickOrderId, matchingLot.pickOrderId,
matchingLot.itemId, matchingLot.itemId,
currentUserId || 0, currentUserId || 0,
matchingLot.requiredQty || 1 // 传递实际的 required quantity
matchingLot.requiredQty || 1 // 传递实际的 required quantity
); );
if (result.code === "SUCCESS") { if (result.code === "SUCCESS") {
successCount++; successCount++;
// Mark this item as processed
// Mark this item as processed
setProcessedQrCodes(prev => new Set(prev).add(itemKey)); setProcessedQrCodes(prev => new Set(prev).add(itemKey));
console.log(` Second QR scan status updated for item ${matchingLot.itemId}`);
console.log(` Second QR scan status updated for item ${matchingLot.itemId}`);
} else { } else {
console.error(`❌ Failed to update second QR scan status: ${result.message}`); console.error(`❌ Failed to update second QR scan status: ${result.message}`);
} }
@@ -681,11 +681,11 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
setQrScanSuccess(true); setQrScanSuccess(true);
setQrScanError(false); setQrScanError(false);
// Set refreshing flag briefly to prevent duplicate processing
// Set refreshing flag briefly to prevent duplicate processing
setIsRefreshingData(true); setIsRefreshingData(true);
await fetchJobOrderData(); // Refresh data await fetchJobOrderData(); // Refresh data
// Clear refresh flag and success message after a short delay
// Clear refresh flag and success message after a short delay
setTimeout(() => { setTimeout(() => {
setQrScanSuccess(false); setQrScanSuccess(false);
setIsRefreshingData(false); setIsRefreshingData(false);
@@ -703,20 +703,20 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
useEffect(() => { useEffect(() => {
// Add isManualScanning and isRefreshingData checks
// Add isManualScanning and isRefreshingData checks
if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) {
return; return;
} }
const latestQr = qrValues[qrValues.length - 1]; const latestQr = qrValues[qrValues.length - 1];
// Check if this QR was already processed recently
// Check if this QR was already processed recently
if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) { if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) {
console.log("⏭️ QR code already processed, skipping..."); console.log("⏭️ QR code already processed, skipping...");
return; return;
} }
// Mark as processed
// Mark as processed
setProcessedQrCodes(prev => new Set(prev).add(latestQr)); setProcessedQrCodes(prev => new Set(prev).add(latestQr));
setLastProcessedQr(latestQr); setLastProcessedQr(latestQr);
@@ -752,7 +752,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [qrValues, combinedLotData, handleQrCodeSubmit, processedQrCodes, lastProcessedQr, isManualScanning, isRefreshingData]); }, [qrValues, combinedLotData, handleQrCodeSubmit, processedQrCodes, lastProcessedQr, isManualScanning, isRefreshingData]);


// ADD THIS: Cleanup effect
// ADD THIS: Cleanup effect
useEffect(() => { useEffect(() => {
return () => { return () => {
// Cleanup when component unmounts (e.g., when switching tabs) // Cleanup when component unmounts (e.g., when switching tabs)
@@ -769,10 +769,10 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [qrScanInput, handleQrCodeSubmit]); }, [qrScanInput, handleQrCodeSubmit]);


// Handle QR code submission from modal (internal scanning)
// Handle QR code submission from modal (internal scanning)
const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => { const handleQrCodeSubmitFromModal = useCallback(async (lotNo: string) => {
if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) { if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) {
console.log(` QR Code verified for lot: ${lotNo}`);
console.log(` QR Code verified for lot: ${lotNo}`);
const requiredQty = selectedLotForQr.requiredQty; const requiredQty = selectedLotForQr.requiredQty;
const lotId = selectedLotForQr.lotId; const lotId = selectedLotForQr.lotId;
@@ -800,7 +800,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
...prev, ...prev,
[lotKey]: requiredQty [lotKey]: requiredQty
})); }));
console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
}, 500); }, 500);
// Refresh data // Refresh data
@@ -811,7 +811,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [selectedLotForQr, fetchJobOrderData]); }, [selectedLotForQr, fetchJobOrderData]);


// Outside QR scanning - process QR codes from outside the page automatically
// Outside QR scanning - process QR codes from outside the page automatically


const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => { const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => {
@@ -847,7 +847,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {


const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: number) => { const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: number) => {
try { try {
// Use the new second scan submit API
// Use the new second scan submit API
const result = await submitSecondScanQuantity( const result = await submitSecondScanQuantity(
lot.pickOrderId, lot.pickOrderId,
lot.itemId, lot.itemId,
@@ -855,13 +855,13 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
qty: submitQty, qty: submitQty,
isMissing: false, isMissing: false,
isBad: false, isBad: false,
reason: undefined // Fix TypeScript error
reason: undefined // Fix TypeScript error
} }
); );
if (result.code === "SUCCESS") { if (result.code === "SUCCESS") {
console.log(` Second scan quantity submitted: ${submitQty}`);
console.log(` Second scan quantity submitted: ${submitQty}`);
await fetchJobOrderData(); // Refresh data await fetchJobOrderData(); // Refresh data
} else { } else {
console.error(`❌ Failed to submit second scan quantity: ${result.message}`); console.error(`❌ Failed to submit second scan quantity: ${result.message}`);
@@ -870,13 +870,13 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
console.error("Error submitting second scan quantity:", error); console.error("Error submitting second scan quantity:", error);
} }
}, [fetchJobOrderData]); }, [fetchJobOrderData]);
// Handle reject lot
// Handle reject lot


// Handle pick execution form
// Handle pick execution form
const handlePickExecutionForm = useCallback((lot: any) => { const handlePickExecutionForm = useCallback((lot: any) => {
console.log("=== Pick Execution Form ==="); console.log("=== Pick Execution Form ===");
console.log("Lot data:", lot); console.log("Lot data:", lot);
console.log("lot.pickOrderCode:", lot.pickOrderCode); // 添加
console.log("lot.pickOrderCode:", lot.pickOrderCode); // 添加
console.log("lot.pickOrderId:", lot.pickOrderId); console.log("lot.pickOrderId:", lot.pickOrderId);
if (!lot) { if (!lot) {
@@ -904,8 +904,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
selectedLotForExecutionForm.itemId, selectedLotForExecutionForm.itemId,
{ {
qty: data.actualPickQty, // verified qty qty: data.actualPickQty, // verified qty
missQty: data.missQty || 0, // 添加:实际的 miss qty
badItemQty: data.badItemQty || 0, // 添加:实际的 bad item qty
missQty: data.missQty || 0, // 添加:实际的 miss qty
badItemQty: data.badItemQty || 0, // 添加:实际的 bad item qty
isMissing: data.missQty > 0, isMissing: data.missQty > 0,
isBad: data.badItemQty > 0, isBad: data.badItemQty > 0,
reason: data.issueRemark || '', reason: data.issueRemark || '',
@@ -916,7 +916,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
console.log("Pick execution issue recorded:", result); console.log("Pick execution issue recorded:", result);
if (result && result.code === "SUCCESS") { if (result && result.code === "SUCCESS") {
console.log(" Pick execution issue recorded successfully");
console.log(" Pick execution issue recorded successfully");
} else { } else {
console.error("❌ Failed to record pick execution issue:", result); console.error("❌ Failed to record pick execution issue:", result);
} }
@@ -930,7 +930,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [currentUserId, selectedLotForExecutionForm, fetchJobOrderData,]); }, [currentUserId, selectedLotForExecutionForm, fetchJobOrderData,]);


// Calculate remaining required quantity
// Calculate remaining required quantity
const calculateRemainingRequiredQty = useCallback((lot: any) => { const calculateRemainingRequiredQty = useCallback((lot: any) => {
const requiredQty = lot.requiredQty || 0; const requiredQty = lot.requiredQty || 0;
const stockOutLineQty = lot.stockOutLineQty || 0; const stockOutLineQty = lot.stockOutLineQty || 0;
@@ -1011,7 +1011,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs }) => {


// Pagination data with sorting by routerIndex // Pagination data with sorting by routerIndex
const paginatedData = useMemo(() => { const paginatedData = useMemo(() => {
// Sort by routerIndex first, then by other criteria
// Sort by routerIndex first, then by other criteria
const sortedData = [...combinedLotData].sort((a, b) => { const sortedData = [...combinedLotData].sort((a, b) => {
const aIndex = a.routerIndex || 0; const aIndex = a.routerIndex || 0;
const bIndex = b.routerIndex || 0; const bIndex = b.routerIndex || 0;
@@ -1035,7 +1035,7 @@ const paginatedData = useMemo(() => {
return sortedData.slice(startIndex, endIndex); return sortedData.slice(startIndex, endIndex);
}, [combinedLotData, paginationController]); }, [combinedLotData, paginationController]);


// Add these functions for manual scanning
// Add these functions for manual scanning
const handleStartScan = useCallback(() => { const handleStartScan = useCallback(() => {
console.log(" Starting manual QR scan..."); console.log(" Starting manual QR scan...");
setIsManualScanning(true); setIsManualScanning(true);
@@ -1061,7 +1061,7 @@ const paginatedData = useMemo(() => {
} }
}, [combinedLotData.length, isManualScanning, handleStopScan]); }, [combinedLotData.length, isManualScanning, handleStopScan]);


// Cleanup effect
// Cleanup effect
useEffect(() => { useEffect(() => {
return () => { return () => {
// Cleanup when component unmounts (e.g., when switching tabs) // Cleanup when component unmounts (e.g., when switching tabs)
@@ -1151,7 +1151,7 @@ const paginatedData = useMemo(() => {
</Button> </Button>
)} )}
{/* ADD THIS: Submit All Scanned Button */}
{/* ADD THIS: Submit All Scanned Button */}
<Button <Button
variant="contained" variant="contained"
color="success" color="success"
@@ -1259,12 +1259,12 @@ const paginatedData = useMemo(() => {
height: '100%' height: '100%'
}}> }}>
<Checkbox <Checkbox
checked={true} // 改为 true
checked={true} // 改为 true
disabled={true} disabled={true}
readOnly={true} readOnly={true}
size="large" size="large"
sx={{ sx={{
color: 'success.main', // 固定为绿色
color: 'success.main', // 固定为绿色
'&.Mui-checked': { '&.Mui-checked': {
color: 'success.main', color: 'success.main',
}, },
@@ -1296,7 +1296,7 @@ const paginatedData = useMemo(() => {
handleSubmitPickQtyWithQty(lot, submitQty); handleSubmitPickQtyWithQty(lot, submitQty);
}} }}
disabled={ disabled={
// 修复:只有扫描过但未完成的才能提交
// 修复:只有扫描过但未完成的才能提交
lot.matchStatus !== 'scanned' || // 只有 scanned 状态才能提交 lot.matchStatus !== 'scanned' || // 只有 scanned 状态才能提交
lot.lotAvailability === 'expired' || lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' || lot.lotAvailability === 'status_unavailable' ||
@@ -1317,7 +1317,7 @@ const paginatedData = useMemo(() => {
size="small" size="small"
onClick={() => handlePickExecutionForm(lot)} onClick={() => handlePickExecutionForm(lot)}
disabled={ disabled={
// 修复:只有扫描过但未完成的才能报告问题
// 修复:只有扫描过但未完成的才能报告问题
lot.matchStatus !== 'scanned' || // 只有 scanned 状态才能报告问题 lot.matchStatus !== 'scanned' || // 只有 scanned 状态才能报告问题
lot.lotAvailability === 'expired' || lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' || lot.lotAvailability === 'status_unavailable' ||
@@ -1361,7 +1361,7 @@ const paginatedData = useMemo(() => {
</Box> </Box>
</Stack> </Stack>


{/* QR Code Modal */}
{/* QR Code Modal */}
<QrCodeModal <QrCodeModal
open={qrModalOpen} open={qrModalOpen}
onClose={() => { onClose={() => {
@@ -1375,7 +1375,7 @@ const paginatedData = useMemo(() => {
onQrCodeSubmit={handleQrCodeSubmitFromModal} onQrCodeSubmit={handleQrCodeSubmitFromModal}
/> />


{/* Pick Execution Form Modal */}
{/* Pick Execution Form Modal */}
{pickExecutionFormOpen && selectedLotForExecutionForm && ( {pickExecutionFormOpen && selectedLotForExecutionForm && (
<GoodPickExecutionForm <GoodPickExecutionForm
open={pickExecutionFormOpen} open={pickExecutionFormOpen}
@@ -1391,13 +1391,13 @@ const paginatedData = useMemo(() => {
itemCode: selectedLotForExecutionForm.itemCode, itemCode: selectedLotForExecutionForm.itemCode,
itemName: selectedLotForExecutionForm.itemName, itemName: selectedLotForExecutionForm.itemName,
pickOrderCode: selectedLotForExecutionForm.pickOrderCode, pickOrderCode: selectedLotForExecutionForm.pickOrderCode,
// Add missing required properties from GetPickOrderLineInfo interface
// Add missing required properties from GetPickOrderLineInfo interface
availableQty: selectedLotForExecutionForm.availableQty || 0, availableQty: selectedLotForExecutionForm.availableQty || 0,
requiredQty: selectedLotForExecutionForm.requiredQty || 0, requiredQty: selectedLotForExecutionForm.requiredQty || 0,
uomCode: selectedLotForExecutionForm.uomCode || '', uomCode: selectedLotForExecutionForm.uomCode || '',
uomDesc: selectedLotForExecutionForm.uomDesc || '', uomDesc: selectedLotForExecutionForm.uomDesc || '',
pickedQty: selectedLotForExecutionForm.actualPickQty || 0, // Use pickedQty instead of actualPickQty
suggestedList: [] // Add required suggestedList property
pickedQty: selectedLotForExecutionForm.actualPickQty || 0, // Use pickedQty instead of actualPickQty
suggestedList: [] // Add required suggestedList property
}} }}
pickOrderId={selectedLotForExecutionForm.pickOrderId} pickOrderId={selectedLotForExecutionForm.pickOrderId}
pickOrderCreateDate={new Date()} pickOrderCreateDate={new Date()}


+ 21
- 21
src/components/Jodetail/JobmatchForm.tsx View File

@@ -54,7 +54,7 @@ interface PickExecutionFormProps {
selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null; selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null;
pickOrderId?: number; pickOrderId?: number;
pickOrderCreateDate: any; pickOrderCreateDate: any;
// Remove these props since we're not handling normal cases
// Remove these props since we're not handling normal cases
// onNormalPickSubmit?: (lineId: number, lotId: number, qty: number) => Promise<void>; // onNormalPickSubmit?: (lineId: number, lotId: number, qty: number) => Promise<void>;
// selectedRowId?: number | null; // selectedRowId?: number | null;
} }
@@ -76,7 +76,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
selectedPickOrderLine, selectedPickOrderLine,
pickOrderId, pickOrderId,
pickOrderCreateDate, pickOrderCreateDate,
// Remove these props
// Remove these props
// onNormalPickSubmit, // onNormalPickSubmit,
// selectedRowId, // selectedRowId,
}) => { }) => {
@@ -93,7 +93,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
return Math.max(0, remainingQty); return Math.max(0, remainingQty);
}, []); }, []);
const calculateRequiredQty = useCallback((lot: LotPickData) => { const calculateRequiredQty = useCallback((lot: LotPickData) => {
// Use the original required quantity, not subtracting actualPickQty
// Use the original required quantity, not subtracting actualPickQty
// The actualPickQty in the form should be independent of the database value // The actualPickQty in the form should be independent of the database value
return lot.requiredQty || 0; return lot.requiredQty || 0;
}, []); }, []);
@@ -128,7 +128,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
} }
}; };


// Initialize verified quantity to the received quantity (actualPickQty)
// Initialize verified quantity to the received quantity (actualPickQty)
const initialVerifiedQty = selectedLot.actualPickQty || 0; const initialVerifiedQty = selectedLot.actualPickQty || 0;
setVerifiedQty(initialVerifiedQty); setVerifiedQty(initialVerifiedQty);
@@ -157,14 +157,14 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
handledBy: undefined, handledBy: undefined,
}); });
} }
// 修复:只在 open 状态改变时重新初始化,移除其他依赖
// 修复:只在 open 状态改变时重新初始化,移除其他依赖
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]); }, [open]);


const handleInputChange = useCallback((field: keyof PickExecutionIssueData, value: any) => { const handleInputChange = useCallback((field: keyof PickExecutionIssueData, value: any) => {
setFormData(prev => ({ ...prev, [field]: value })); setFormData(prev => ({ ...prev, [field]: value }));
// Update verified quantity state when actualPickQty changes
// Update verified quantity state when actualPickQty changes
if (field === 'actualPickQty') { if (field === 'actualPickQty') {
setVerifiedQty(value); setVerifiedQty(value);
} }
@@ -175,11 +175,11 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
} }
}, [errors]); }, [errors]);


// Update form validation to require either missQty > 0 OR badItemQty > 0
// Update form validation to require either missQty > 0 OR badItemQty > 0
const validateForm = (): boolean => { const validateForm = (): boolean => {
const newErrors: FormErrors = {}; const newErrors: FormErrors = {};
// 使用原始的接收数量,而不是 formData 中的
// 使用原始的接收数量,而不是 formData 中的
const receivedQty = selectedLot?.actualPickQty || 0; const receivedQty = selectedLot?.actualPickQty || 0;
const requiredQty = selectedLot?.requiredQty || 0; const requiredQty = selectedLot?.requiredQty || 0;
const badItemQty = formData.badItemQty || 0; const badItemQty = formData.badItemQty || 0;
@@ -189,18 +189,18 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
newErrors.actualPickQty = t('Qty is required'); newErrors.actualPickQty = t('Qty is required');
} }
// 验证数量不能超过原始接收数量
// 验证数量不能超过原始接收数量
if (verifiedQty > receivedQty) { if (verifiedQty > receivedQty) {
newErrors.actualPickQty = t('Verified quantity cannot exceed received quantity'); newErrors.actualPickQty = t('Verified quantity cannot exceed received quantity');
} }
// 只检查总和是否等于需求数量
// 只检查总和是否等于需求数量
const totalQty = verifiedQty + badItemQty + missQty; const totalQty = verifiedQty + badItemQty + missQty;
if (totalQty !== requiredQty) { if (totalQty !== requiredQty) {
newErrors.actualPickQty = t('Total (Verified + Bad + Missing) must equal Required quantity'); newErrors.actualPickQty = t('Total (Verified + Bad + Missing) must equal Required quantity');
} }
// Require either missQty > 0 OR badItemQty > 0
// Require either missQty > 0 OR badItemQty > 0
const hasMissQty = formData.missQty && formData.missQty > 0; const hasMissQty = formData.missQty && formData.missQty > 0;
const hasBadItemQty = formData.badItemQty && formData.badItemQty > 0; const hasBadItemQty = formData.badItemQty && formData.badItemQty > 0;
@@ -219,7 +219,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({


setLoading(true); setLoading(true);
try { try {
// Use the verified quantity in the submission
// Use the verified quantity in the submission
const submissionData = { const submissionData = {
...formData, ...formData,
actualPickQty: verifiedQty, actualPickQty: verifiedQty,
@@ -257,11 +257,11 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
return ( return (
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth> <Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
<DialogTitle> <DialogTitle>
{t('Pick Execution Issue Form')} {/* Always show issue form title */}
{t('Pick Execution Issue Form')} {/* Always show issue form title */}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<Box sx={{ mt: 2 }}> <Box sx={{ mt: 2 }}>
{/* Add instruction text */}
{/* Add instruction text */}
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12}> <Grid item xs={12}>
<Box sx={{ p: 2, backgroundColor: '#fff3cd', borderRadius: 1, mb: 2 }}> <Box sx={{ p: 2, backgroundColor: '#fff3cd', borderRadius: 1, mb: 2 }}>
@@ -271,7 +271,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
</Box> </Box>
</Grid> </Grid>
{/* Keep the existing form fields */}
{/* Keep the existing form fields */}
<Grid item xs={6}> <Grid item xs={6}>
<TextField <TextField
fullWidth fullWidth
@@ -306,7 +306,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
//handleInputChange('actualPickQty', newValue); //handleInputChange('actualPickQty', newValue);
}} }}
error={!!errors.actualPickQty} error={!!errors.actualPickQty}
helperText={errors.actualPickQty || `${t('Max')}: ${selectedLot?.actualPickQty || 0}`} // 使用原始接收数量
helperText={errors.actualPickQty || `${t('Max')}: ${selectedLot?.actualPickQty || 0}`} // 使用原始接收数量
variant="outlined" variant="outlined"
/> />
</Grid> </Grid>
@@ -320,7 +320,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
onChange={(e) => { onChange={(e) => {
const newMissQty = parseFloat(e.target.value) || 0; const newMissQty = parseFloat(e.target.value) || 0;
handleInputChange('missQty', newMissQty); handleInputChange('missQty', newMissQty);
// 不要自动修改其他字段
// 不要自动修改其他字段
}} }}
error={!!errors.missQty} error={!!errors.missQty}
helperText={errors.missQty} helperText={errors.missQty}
@@ -337,7 +337,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
onChange={(e) => { onChange={(e) => {
const newBadItemQty = parseFloat(e.target.value) || 0; const newBadItemQty = parseFloat(e.target.value) || 0;
handleInputChange('badItemQty', newBadItemQty); handleInputChange('badItemQty', newBadItemQty);
// 不要自动修改其他字段
// 不要自动修改其他字段
}} }}
error={!!errors.badItemQty} error={!!errors.badItemQty}
helperText={errors.badItemQty} helperText={errors.badItemQty}
@@ -345,7 +345,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
/> />
</Grid> </Grid>
{/* Show issue description and handler fields when bad items > 0 */}
{/* Show issue description and handler fields when bad items > 0 */}
{(formData.badItemQty && formData.badItemQty > 0) ? ( {(formData.badItemQty && formData.badItemQty > 0) ? (
<> <>
<Grid item xs={12}> <Grid item xs={12}>
@@ -358,7 +358,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
value={formData.issueRemark || ''} value={formData.issueRemark || ''}
onChange={(e) => { onChange={(e) => {
handleInputChange('issueRemark', e.target.value); handleInputChange('issueRemark', e.target.value);
// Don't reset badItemQty when typing in issue remark
// Don't reset badItemQty when typing in issue remark
}} }}
error={!!errors.issueRemark} error={!!errors.issueRemark}
helperText={errors.issueRemark} helperText={errors.issueRemark}
@@ -374,7 +374,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
value={formData.handledBy ? formData.handledBy.toString() : ''} value={formData.handledBy ? formData.handledBy.toString() : ''}
onChange={(e) => { onChange={(e) => {
handleInputChange('handledBy', e.target.value ? parseInt(e.target.value) : undefined); handleInputChange('handledBy', e.target.value ? parseInt(e.target.value) : undefined);
// Don't reset badItemQty when selecting handler
// Don't reset badItemQty when selecting handler
}} }}
label={t('handler')} label={t('handler')}
> >


+ 10
- 10
src/components/Jodetail/JodetailSearch.tsx View File

@@ -106,7 +106,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
const handleCompletionStatusChange = (event: CustomEvent) => { const handleCompletionStatusChange = (event: CustomEvent) => {
const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail; const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail;
// 修复:根据标签页和事件来源决定是否更新打印按钮状态
// 修复:根据标签页和事件来源决定是否更新打印按钮状态
if (eventTabIndex === undefined || eventTabIndex === tabIndex) { if (eventTabIndex === undefined || eventTabIndex === tabIndex) {
setPrintButtonsEnabled(allLotsCompleted); setPrintButtonsEnabled(allLotsCompleted);
console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted); console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted);
@@ -118,9 +118,9 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
return () => { return () => {
window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener); window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
}; };
}, [tabIndex]); // 添加 tabIndex 依赖
}, [tabIndex]); // 添加 tabIndex 依赖


// 新增:处理标签页切换时的打印按钮状态重置
// 新增:处理标签页切换时的打印按钮状态重置
useEffect(() => { useEffect(() => {
// 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态 // 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态
if (tabIndex === 2) { if (tabIndex === 2) {
@@ -141,14 +141,14 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
const res = await autoAssignAndReleasePickOrderByStore(currentUserId, storeId); const res = await autoAssignAndReleasePickOrderByStore(currentUserId, storeId);
console.log("Assign by store result:", res); console.log("Assign by store result:", res);
// Handle different response codes
// Handle different response codes
if (res.code === "SUCCESS") { if (res.code === "SUCCESS") {
console.log(" Successfully assigned pick order to store", storeId);
// Trigger refresh to show newly assigned data
console.log(" Successfully assigned pick order to store", storeId);
// Trigger refresh to show newly assigned data
window.dispatchEvent(new CustomEvent('pickOrderAssigned')); window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
} else if (res.code === "USER_BUSY") { } else if (res.code === "USER_BUSY") {
console.warn("⚠️ User already has pick orders in progress:", res.message); console.warn("⚠️ User already has pick orders in progress:", res.message);
// Show warning but still refresh to show existing orders
// Show warning but still refresh to show existing orders
alert(`Warning: ${res.message}`); alert(`Warning: ${res.message}`);
window.dispatchEvent(new CustomEvent('pickOrderAssigned')); window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
} else if (res.code === "NO_ORDERS") { } else if (res.code === "NO_ORDERS") {
@@ -165,7 +165,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
setIsAssigning(false); setIsAssigning(false);
} }
}; };
// Manual assignment handler - uses the action function
// Manual assignment handler - uses the action function
const loadUnassignedOrders = useCallback(async () => { const loadUnassignedOrders = useCallback(async () => {
setIsLoadingUnassigned(true); setIsLoadingUnassigned(true);
try { try {
@@ -189,7 +189,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
try { try {
const result = await assignJobOrderPickOrder(pickOrderId, currentUserId); const result = await assignJobOrderPickOrder(pickOrderId, currentUserId);
if (result.message === "Successfully assigned") { if (result.message === "Successfully assigned") {
console.log(" Successfully assigned pick order");
console.log(" Successfully assigned pick order");
// 刷新数据 // 刷新数据
window.dispatchEvent(new CustomEvent('pickOrderAssigned')); window.dispatchEvent(new CustomEvent('pickOrderAssigned'));
// 重新加载未分配订单列表 // 重新加载未分配订单列表
@@ -448,7 +448,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
</Stack> </Stack>
</Box> </Box>


{/* Tabs section - Move the click handler here */}
{/* Tabs section - Move the click handler here */}
<Box sx={{ <Box sx={{
borderBottom: '1px solid #e0e0e0' borderBottom: '1px solid #e0e0e0'
}}> }}>


+ 32
- 32
src/components/Jodetail/completeJobOrderRecord.tsx View File

@@ -49,7 +49,7 @@ interface Props {
filterArgs: Record<string, any>; filterArgs: Record<string, any>;
} }


// 修改:已完成的 Job Order Pick Order 接口
// 修改:已完成的 Job Order Pick Order 接口
interface CompletedJobOrderPickOrder { interface CompletedJobOrderPickOrder {
id: number; id: number;
pickOrderId: number; pickOrderId: number;
@@ -70,7 +70,7 @@ interface CompletedJobOrderPickOrder {
completedItems: number; completedItems: number;
} }


// 新增:Lot 详情接口
// 新增:Lot 详情接口
interface LotDetail { interface LotDetail {
lotId: number; lotId: number;
lotNo: string; lotNo: string;
@@ -106,21 +106,21 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
const currentUserId = session?.id ? parseInt(session.id) : undefined; const currentUserId = session?.id ? parseInt(session.id) : undefined;
// 修改:已完成 Job Order Pick Orders 状态
// 修改:已完成 Job Order Pick Orders 状态
const [completedJobOrderPickOrders, setCompletedJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]); const [completedJobOrderPickOrders, setCompletedJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]);
const [completedJobOrderPickOrdersLoading, setCompletedJobOrderPickOrdersLoading] = useState(false); const [completedJobOrderPickOrdersLoading, setCompletedJobOrderPickOrdersLoading] = useState(false);
// 修改:详情视图状态
// 修改:详情视图状态
const [selectedJobOrderPickOrder, setSelectedJobOrderPickOrder] = useState<CompletedJobOrderPickOrder | null>(null); const [selectedJobOrderPickOrder, setSelectedJobOrderPickOrder] = useState<CompletedJobOrderPickOrder | null>(null);
const [showDetailView, setShowDetailView] = useState(false); const [showDetailView, setShowDetailView] = useState(false);
const [detailLotData, setDetailLotData] = useState<LotDetail[]>([]); const [detailLotData, setDetailLotData] = useState<LotDetail[]>([]);
const [detailLotDataLoading, setDetailLotDataLoading] = useState(false); const [detailLotDataLoading, setDetailLotDataLoading] = useState(false);
// 修改:搜索状态
// 修改:搜索状态
const [searchQuery, setSearchQuery] = useState<Record<string, any>>({}); const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
const [filteredJobOrderPickOrders, setFilteredJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]); const [filteredJobOrderPickOrders, setFilteredJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]);
// 修改:分页状态
// 修改:分页状态
const [paginationController, setPaginationController] = useState({ const [paginationController, setPaginationController] = useState({
pageNum: 0, pageNum: 0,
pageSize: 10, pageSize: 10,
@@ -129,7 +129,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
const formProps = useForm(); const formProps = useForm();
const errors = formProps.formState.errors; const errors = formProps.formState.errors;


// 修改:使用新的 Job Order API 获取已完成的 Job Order Pick Orders(仅完成pick的)
// 修改:使用新的 Job Order API 获取已完成的 Job Order Pick Orders(仅完成pick的)
const fetchCompletedJobOrderPickOrdersData = useCallback(async () => { const fetchCompletedJobOrderPickOrdersData = useCallback(async () => {
if (!currentUserId) return; if (!currentUserId) return;
@@ -139,12 +139,12 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
const completedJobOrderPickOrders = await fetchCompletedJobOrderPickOrdersrecords(currentUserId); const completedJobOrderPickOrders = await fetchCompletedJobOrderPickOrdersrecords(currentUserId);
// Fix: Ensure the data is always an array
// Fix: Ensure the data is always an array
const safeData = Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : []; const safeData = Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : [];
setCompletedJobOrderPickOrders(safeData); setCompletedJobOrderPickOrders(safeData);
setFilteredJobOrderPickOrders(safeData); setFilteredJobOrderPickOrders(safeData);
console.log(" Fetched completed Job Order pick orders:", safeData);
console.log(" Fetched completed Job Order pick orders:", safeData);
} catch (error) { } catch (error) {
console.error("❌ Error fetching completed Job Order pick orders:", error); console.error("❌ Error fetching completed Job Order pick orders:", error);
setCompletedJobOrderPickOrders([]); setCompletedJobOrderPickOrders([]);
@@ -154,7 +154,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
} }
}, [currentUserId]); }, [currentUserId]);


// 新增:获取 lot 详情数据(使用新的API)
// 新增:获取 lot 详情数据(使用新的API)
const fetchLotDetailsData = useCallback(async (pickOrderId: number) => { const fetchLotDetailsData = useCallback(async (pickOrderId: number) => {
setDetailLotDataLoading(true); setDetailLotDataLoading(true);
try { try {
@@ -163,7 +163,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
const lotDetails = await fetchCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId); const lotDetails = await fetchCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId);
setDetailLotData(lotDetails); setDetailLotData(lotDetails);
console.log(" Fetched lot details:", lotDetails);
console.log(" Fetched lot details:", lotDetails);
} catch (error) { } catch (error) {
console.error("❌ Error fetching lot details:", error); console.error("❌ Error fetching lot details:", error);
setDetailLotData([]); setDetailLotData([]);
@@ -172,19 +172,19 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
} }
}, []); }, []);


// 修改:初始化时获取数据
// 修改:初始化时获取数据
useEffect(() => { useEffect(() => {
if (currentUserId) { if (currentUserId) {
fetchCompletedJobOrderPickOrdersData(); fetchCompletedJobOrderPickOrdersData();
} }
}, [currentUserId, fetchCompletedJobOrderPickOrdersData]); }, [currentUserId, fetchCompletedJobOrderPickOrdersData]);


// 修改:搜索功能
// 修改:搜索功能
const handleSearch = useCallback((query: Record<string, any>) => { const handleSearch = useCallback((query: Record<string, any>) => {
setSearchQuery({ ...query }); setSearchQuery({ ...query });
console.log("Search query:", query); console.log("Search query:", query);
// Fix: Ensure completedJobOrderPickOrders is an array before filtering
// Fix: Ensure completedJobOrderPickOrders is an array before filtering
if (!Array.isArray(completedJobOrderPickOrders)) { if (!Array.isArray(completedJobOrderPickOrders)) {
setFilteredJobOrderPickOrders([]); setFilteredJobOrderPickOrders([]);
return; return;
@@ -207,14 +207,14 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
console.log("Filtered Job Order pick orders count:", filtered.length); console.log("Filtered Job Order pick orders count:", filtered.length);
}, [completedJobOrderPickOrders]); }, [completedJobOrderPickOrders]);


// 修改:重置搜索
// 修改:重置搜索
const handleSearchReset = useCallback(() => { const handleSearchReset = useCallback(() => {
setSearchQuery({}); setSearchQuery({});
// Fix: Ensure completedJobOrderPickOrders is an array before setting
// Fix: Ensure completedJobOrderPickOrders is an array before setting
setFilteredJobOrderPickOrders(Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : []); setFilteredJobOrderPickOrders(Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : []);
}, [completedJobOrderPickOrders]); }, [completedJobOrderPickOrders]);


// 修改:分页功能
// 修改:分页功能
const handlePageChange = useCallback((event: unknown, newPage: number) => { const handlePageChange = useCallback((event: unknown, newPage: number) => {
setPaginationController(prev => ({ setPaginationController(prev => ({
...prev, ...prev,
@@ -230,9 +230,9 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
}); });
}, []); }, []);


// 修改:分页数据
// 修改:分页数据
const paginatedData = useMemo(() => { const paginatedData = useMemo(() => {
// Fix: Ensure filteredJobOrderPickOrders is an array before calling slice
// Fix: Ensure filteredJobOrderPickOrders is an array before calling slice
if (!Array.isArray(filteredJobOrderPickOrders)) { if (!Array.isArray(filteredJobOrderPickOrders)) {
return []; return [];
} }
@@ -242,7 +242,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
return filteredJobOrderPickOrders.slice(startIndex, endIndex); return filteredJobOrderPickOrders.slice(startIndex, endIndex);
}, [filteredJobOrderPickOrders, paginationController]); }, [filteredJobOrderPickOrders, paginationController]);


// 修改:搜索条件
// 修改:搜索条件
const searchCriteria: Criterion<any>[] = [ const searchCriteria: Criterion<any>[] = [
{ {
label: t("Pick Order Code"), label: t("Pick Order Code"),
@@ -261,34 +261,34 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
} }
]; ];


// 修改:详情点击处理
// 修改:详情点击处理
const handleDetailClick = useCallback(async (jobOrderPickOrder: CompletedJobOrderPickOrder) => { const handleDetailClick = useCallback(async (jobOrderPickOrder: CompletedJobOrderPickOrder) => {
setSelectedJobOrderPickOrder(jobOrderPickOrder); setSelectedJobOrderPickOrder(jobOrderPickOrder);
setShowDetailView(true); setShowDetailView(true);
// 获取 lot 详情数据(使用新的API)
// 获取 lot 详情数据(使用新的API)
await fetchLotDetailsData(jobOrderPickOrder.pickOrderId); await fetchLotDetailsData(jobOrderPickOrder.pickOrderId);
// 触发打印按钮状态更新 - 基于详情数据
// 触发打印按钮状态更新 - 基于详情数据
const allCompleted = jobOrderPickOrder.secondScanCompleted; const allCompleted = jobOrderPickOrder.secondScanCompleted;
// 发送事件,包含标签页信息
// 发送事件,包含标签页信息
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: { detail: {
allLotsCompleted: allCompleted, allLotsCompleted: allCompleted,
tabIndex: 3 // 明确指定这是来自标签页 3 的事件
tabIndex: 3 // 明确指定这是来自标签页 3 的事件
} }
})); }));
}, [fetchLotDetailsData]); }, [fetchLotDetailsData]);


// 修改:返回列表视图
// 修改:返回列表视图
const handleBackToList = useCallback(() => { const handleBackToList = useCallback(() => {
setShowDetailView(false); setShowDetailView(false);
setSelectedJobOrderPickOrder(null); setSelectedJobOrderPickOrder(null);
setDetailLotData([]); setDetailLotData([]);
// 返回列表时禁用打印按钮
// 返回列表时禁用打印按钮
window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', { window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
detail: { detail: {
allLotsCompleted: false, allLotsCompleted: false,
@@ -335,7 +335,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {


},[t, selectedJobOrderPickOrder]); },[t, selectedJobOrderPickOrder]);


// 修改:如果显示详情视图,渲染 Job Order 详情和 Lot 信息
// 修改:如果显示详情视图,渲染 Job Order 详情和 Lot 信息
if (showDetailView && selectedJobOrderPickOrder) { if (showDetailView && selectedJobOrderPickOrder) {
return ( return (
<FormProvider {...formProps}> <FormProvider {...formProps}>
@@ -386,7 +386,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
</CardContent> </CardContent>
</Card> </Card>


{/* 修改:Lot 详情表格 - 添加复选框列 */}
{/* 修改:Lot 详情表格 - 添加复选框列 */}
<Card> <Card>
<CardContent> <CardContent>
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
@@ -446,7 +446,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
<TableCell align="right"> <TableCell align="right">
{lot.actualPickQty?.toLocaleString() || 0} ({lot.uomShortDesc}) {lot.actualPickQty?.toLocaleString() || 0} ({lot.uomShortDesc})
</TableCell> </TableCell>
{/* 修改:Processing Status 使用复选框 */}
{/* 修改:Processing Status 使用复选框 */}
<TableCell align="center"> <TableCell align="center">
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
@@ -473,7 +473,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
/> />
</Box> </Box>
</TableCell> </TableCell>
{/* 修改:Second Scan Status 使用复选框 */}
{/* 修改:Second Scan Status 使用复选框 */}
<TableCell align="center"> <TableCell align="center">
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
@@ -514,7 +514,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs }) => {
); );
} }


// 修改:默认列表视图
// 修改:默认列表视图
return ( return (
<FormProvider {...formProps}> <FormProvider {...formProps}>
<Box> <Box>


+ 66
- 66
src/components/PickOrderSearch/LotTable.tsx View File

@@ -24,7 +24,7 @@ import { GetPickOrderLineInfo, recordPickExecutionIssue } from "@/app/api/pickOr
import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider'; import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider';
import { updateInventoryLotLineStatus } from "@/app/api/inventory/actions"; import { updateInventoryLotLineStatus } from "@/app/api/inventory/actions";
import { updateStockOutLineStatus } from "@/app/api/pickOrder/actions"; import { updateStockOutLineStatus } from "@/app/api/pickOrder/actions";
import { fetchStockInLineInfo } from "@/app/api/po/actions"; // Add this import
import { fetchStockInLineInfo } from "@/app/api/po/actions"; // Add this import
import PickExecutionForm from "./PickExecutionForm"; import PickExecutionForm from "./PickExecutionForm";
interface LotPickData { interface LotPickData {
id: number; id: number;
@@ -41,7 +41,7 @@ interface LotPickData {
outQty: number; outQty: number;
holdQty: number; holdQty: number;
totalPickedByAllPickOrders: number; totalPickedByAllPickOrders: number;
lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable' | 'rejected'; // 添加 'rejected'
lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable' | 'rejected'; // 添加 'rejected'
stockOutLineId?: number; stockOutLineId?: number;
stockOutLineStatus?: string; stockOutLineStatus?: string;
stockOutLineQty?: number; stockOutLineQty?: number;
@@ -56,7 +56,7 @@ interface PickQtyData {
interface LotTableProps { interface LotTableProps {
lotData: LotPickData[]; lotData: LotPickData[];
selectedRowId: number | null; selectedRowId: number | null;
selectedRow: (GetPickOrderLineInfo & { pickOrderCode: string; pickOrderId: number }) | null; // 添加 pickOrderId
selectedRow: (GetPickOrderLineInfo & { pickOrderCode: string; pickOrderId: number }) | null; // 添加 pickOrderId
pickQtyData: PickQtyData; pickQtyData: PickQtyData;
selectedLotRowId: string | null; selectedLotRowId: string | null;
selectedLotId: number | null; selectedLotId: number | null;
@@ -77,7 +77,7 @@ interface LotTableProps {
onLotDataRefresh: () => Promise<void>; onLotDataRefresh: () => Promise<void>;
} }


// QR Code Modal Component
// QR Code Modal Component
const QrCodeModal: React.FC<{ const QrCodeModal: React.FC<{
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
@@ -88,53 +88,53 @@ const QrCodeModal: React.FC<{
const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
const [manualInput, setManualInput] = useState<string>(''); const [manualInput, setManualInput] = useState<string>('');
const [validationErrors, setValidationErrors] = useState<{[key: string]: string}>({}); const [validationErrors, setValidationErrors] = useState<{[key: string]: string}>({});
// Add state to track manual input submission
// Add state to track manual input submission
const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false); const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false);
const [manualInputError, setManualInputError] = useState<boolean>(false); const [manualInputError, setManualInputError] = useState<boolean>(false);
const [isProcessingQr, setIsProcessingQr] = useState<boolean>(false); const [isProcessingQr, setIsProcessingQr] = useState<boolean>(false);
const [qrScanFailed, setQrScanFailed] = useState<boolean>(false); const [qrScanFailed, setQrScanFailed] = useState<boolean>(false);
const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false); const [qrScanSuccess, setQrScanSuccess] = useState<boolean>(false);
// Add state to track processed QR codes to prevent re-processing
// Add state to track processed QR codes to prevent re-processing
const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set()); const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
// Add state to store the scanned QR result
// Add state to store the scanned QR result
const [scannedQrResult, setScannedQrResult] = useState<string>(''); const [scannedQrResult, setScannedQrResult] = useState<string>('');


// Process scanned QR codes with new format
// Process scanned QR codes with new format
useEffect(() => { useEffect(() => {
if (qrValues.length > 0 && lot && !isProcessingQr && !qrScanSuccess) { if (qrValues.length > 0 && lot && !isProcessingQr && !qrScanSuccess) {
const latestQr = qrValues[qrValues.length - 1]; const latestQr = qrValues[qrValues.length - 1];
// Check if this QR code has already been processed
// Check if this QR code has already been processed
if (processedQrCodes.has(latestQr)) { if (processedQrCodes.has(latestQr)) {
console.log("QR code already processed, skipping..."); console.log("QR code already processed, skipping...");
return; return;
} }
// Add to processed set immediately to prevent re-processing
// Add to processed set immediately to prevent re-processing
setProcessedQrCodes(prev => new Set(prev).add(latestQr)); setProcessedQrCodes(prev => new Set(prev).add(latestQr));
try { try {
// Parse QR code as JSON
// Parse QR code as JSON
const qrData = JSON.parse(latestQr); const qrData = JSON.parse(latestQr);
// Check if it has the expected structure
// Check if it has the expected structure
if (qrData.stockInLineId && qrData.itemId) { if (qrData.stockInLineId && qrData.itemId) {
setIsProcessingQr(true); setIsProcessingQr(true);
setQrScanFailed(false); setQrScanFailed(false);
// Fetch stock in line info to get lotNo
// Fetch stock in line info to get lotNo
fetchStockInLineInfo(qrData.stockInLineId) fetchStockInLineInfo(qrData.stockInLineId)
.then((stockInLineInfo) => { .then((stockInLineInfo) => {
console.log("Stock in line info:", stockInLineInfo); console.log("Stock in line info:", stockInLineInfo);
// Store the scanned result for display
// Store the scanned result for display
setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number'); setScannedQrResult(stockInLineInfo.lotNo || 'Unknown lot number');
// Compare lotNo from API with expected lotNo
// Compare lotNo from API with expected lotNo
if (stockInLineInfo.lotNo === lot.lotNo) { if (stockInLineInfo.lotNo === lot.lotNo) {
console.log(` QR Code verified for lot: ${lot.lotNo}`);
console.log(` QR Code verified for lot: ${lot.lotNo}`);
setQrScanSuccess(true); setQrScanSuccess(true);
onQrCodeSubmit(lot.lotNo); onQrCodeSubmit(lot.lotNo);
onClose(); onClose();
@@ -144,7 +144,7 @@ const QrCodeModal: React.FC<{
setQrScanFailed(true); setQrScanFailed(true);
setManualInputError(true); setManualInputError(true);
setManualInputSubmitted(true); setManualInputSubmitted(true);
// DON'T stop scanning - allow new QR codes to be processed
// DON'T stop scanning - allow new QR codes to be processed
} }
}) })
.catch((error) => { .catch((error) => {
@@ -153,16 +153,16 @@ const QrCodeModal: React.FC<{
setQrScanFailed(true); setQrScanFailed(true);
setManualInputError(true); setManualInputError(true);
setManualInputSubmitted(true); setManualInputSubmitted(true);
// DON'T stop scanning - allow new QR codes to be processed
// DON'T stop scanning - allow new QR codes to be processed
}) })
.finally(() => { .finally(() => {
setIsProcessingQr(false); setIsProcessingQr(false);
}); });
} else { } else {
// Fallback to old format (direct lotNo comparison)
// Fallback to old format (direct lotNo comparison)
const qrContent = latestQr.replace(/[{}]/g, ''); const qrContent = latestQr.replace(/[{}]/g, '');
// Store the scanned result for display
// Store the scanned result for display
setScannedQrResult(qrContent); setScannedQrResult(qrContent);
if (qrContent === lot.lotNo) { if (qrContent === lot.lotNo) {
@@ -174,15 +174,15 @@ const QrCodeModal: React.FC<{
setQrScanFailed(true); setQrScanFailed(true);
setManualInputError(true); setManualInputError(true);
setManualInputSubmitted(true); setManualInputSubmitted(true);
// DON'T stop scanning - allow new QR codes to be processed
// DON'T stop scanning - allow new QR codes to be processed
} }
} }
} catch (error) { } catch (error) {
// If JSON parsing fails, fallback to old format
// If JSON parsing fails, fallback to old format
console.log("QR code is not JSON format, trying direct comparison"); console.log("QR code is not JSON format, trying direct comparison");
const qrContent = latestQr.replace(/[{}]/g, ''); const qrContent = latestQr.replace(/[{}]/g, '');
// Store the scanned result for display
// Store the scanned result for display
setScannedQrResult(qrContent); setScannedQrResult(qrContent);
if (qrContent === lot.lotNo) { if (qrContent === lot.lotNo) {
@@ -194,13 +194,13 @@ const QrCodeModal: React.FC<{
setQrScanFailed(true); setQrScanFailed(true);
setManualInputError(true); setManualInputError(true);
setManualInputSubmitted(true); setManualInputSubmitted(true);
// DON'T stop scanning - allow new QR codes to be processed
// DON'T stop scanning - allow new QR codes to be processed
} }
} }
} }
}, [qrValues, lot, onQrCodeSubmit, onClose, resetScan, isProcessingQr, qrScanSuccess, processedQrCodes, stopScan]); }, [qrValues, lot, onQrCodeSubmit, onClose, resetScan, isProcessingQr, qrScanSuccess, processedQrCodes, stopScan]);


// Clear states when modal opens or lot changes
// Clear states when modal opens or lot changes
useEffect(() => { useEffect(() => {
if (open) { if (open) {
setManualInput(''); setManualInput('');
@@ -209,8 +209,8 @@ const QrCodeModal: React.FC<{
setIsProcessingQr(false); setIsProcessingQr(false);
setQrScanFailed(false); setQrScanFailed(false);
setQrScanSuccess(false); setQrScanSuccess(false);
setScannedQrResult(''); // Clear scanned result
// Clear processed QR codes when modal opens
setScannedQrResult(''); // Clear scanned result
// Clear processed QR codes when modal opens
setProcessedQrCodes(new Set()); setProcessedQrCodes(new Set());
} }
}, [open]); }, [open]);
@@ -223,13 +223,13 @@ const QrCodeModal: React.FC<{
setIsProcessingQr(false); setIsProcessingQr(false);
setQrScanFailed(false); setQrScanFailed(false);
setQrScanSuccess(false); setQrScanSuccess(false);
setScannedQrResult(''); // Clear scanned result
// Clear processed QR codes when lot changes
setScannedQrResult(''); // Clear scanned result
// Clear processed QR codes when lot changes
setProcessedQrCodes(new Set()); setProcessedQrCodes(new Set());
} }
}, [lot]); }, [lot]);


// Auto-submit manual input when it matches (but only if QR scan hasn't failed)
// Auto-submit manual input when it matches (but only if QR scan hasn't failed)
useEffect(() => { useEffect(() => {
if (manualInput.trim() === lot?.lotNo && manualInput.trim() !== '' && !qrScanFailed && !qrScanSuccess) { if (manualInput.trim() === lot?.lotNo && manualInput.trim() !== '' && !qrScanFailed && !qrScanSuccess) {
console.log('🔄 Auto-submitting manual input:', manualInput.trim()); console.log('🔄 Auto-submitting manual input:', manualInput.trim());
@@ -247,7 +247,7 @@ const QrCodeModal: React.FC<{
} }
}, [manualInput, lot, onQrCodeSubmit, onClose, qrScanFailed, qrScanSuccess]); }, [manualInput, lot, onQrCodeSubmit, onClose, qrScanFailed, qrScanSuccess]);


// Add the missing handleManualSubmit function
// Add the missing handleManualSubmit function
const handleManualSubmit = () => { const handleManualSubmit = () => {
if (manualInput.trim() === lot?.lotNo) { if (manualInput.trim() === lot?.lotNo) {
setQrScanSuccess(true); setQrScanSuccess(true);
@@ -261,7 +261,7 @@ const QrCodeModal: React.FC<{
} }
}; };
// Add function to restart scanning after manual input error
// Add function to restart scanning after manual input error
const handleRestartScan = () => { const handleRestartScan = () => {
setQrScanFailed(false); setQrScanFailed(false);
setManualInputError(false); setManualInputError(false);
@@ -292,7 +292,7 @@ const QrCodeModal: React.FC<{
{t("QR Code Scan for Lot")}: {lot?.lotNo} {t("QR Code Scan for Lot")}: {lot?.lotNo}
</Typography> </Typography>
{/* Show processing status */}
{/* Show processing status */}
{isProcessingQr && ( {isProcessingQr && (
<Box sx={{ mb: 2, p: 2, backgroundColor: '#e3f2fd', borderRadius: 1 }}> <Box sx={{ mb: 2, p: 2, backgroundColor: '#e3f2fd', borderRadius: 1 }}>
<Typography variant="body2" color="primary"> <Typography variant="body2" color="primary">
@@ -312,7 +312,7 @@ const QrCodeModal: React.FC<{
value={manualInput} value={manualInput}
onChange={(e) => { onChange={(e) => {
setManualInput(e.target.value); setManualInput(e.target.value);
// Reset error states when user starts typing
// Reset error states when user starts typing
if (qrScanFailed || manualInputError) { if (qrScanFailed || manualInputError) {
setQrScanFailed(false); setQrScanFailed(false);
setManualInputError(false); setManualInputError(false);
@@ -352,7 +352,7 @@ const QrCodeModal: React.FC<{


{qrScanSuccess && ( {qrScanSuccess && (
<Typography variant="caption" color="success" display="block"> <Typography variant="caption" color="success" display="block">
{t("Verified successfully!")}
{t("Verified successfully!")}
</Typography> </Typography>
)} )}
</Box> </Box>
@@ -395,10 +395,10 @@ const LotTable: React.FC<LotTableProps> = ({
const stockOutLineQty = lot.stockOutLineQty || 0; const stockOutLineQty = lot.stockOutLineQty || 0;
return Math.max(0, requiredQty - stockOutLineQty); return Math.max(0, requiredQty - stockOutLineQty);
}, []); }, []);
// Add QR scanner context
// Add QR scanner context
const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
const [validationErrors, setValidationErrors] = useState<{[key: string]: string}>({}); const [validationErrors, setValidationErrors] = useState<{[key: string]: string}>({});
// Add state for QR input modal
// Add state for QR input modal
const [qrModalOpen, setQrModalOpen] = useState(false); const [qrModalOpen, setQrModalOpen] = useState(false);
const [selectedLotForQr, setSelectedLotForQr] = useState<LotPickData | null>(null); const [selectedLotForQr, setSelectedLotForQr] = useState<LotPickData | null>(null);
const [manualQrInput, setManualQrInput] = useState<string>(''); const [manualQrInput, setManualQrInput] = useState<string>('');
@@ -409,7 +409,7 @@ const LotTable: React.FC<LotTableProps> = ({
pageSize: 10, pageSize: 10,
}); });


// 添加状态消息生成函数
// 添加状态消息生成函数
const getStatusMessage = useCallback((lot: LotPickData) => { const getStatusMessage = useCallback((lot: LotPickData) => {


switch (lot.stockOutLineStatus?.toLowerCase()) { switch (lot.stockOutLineStatus?.toLowerCase()) {
@@ -483,45 +483,45 @@ const LotTable: React.FC<LotTableProps> = ({
return null; return null;
}, [calculateRemainingAvailableQty, calculateRemainingRequiredQty, t]); }, [calculateRemainingAvailableQty, calculateRemainingRequiredQty, t]);


// Handle QR code submission
// Handle QR code submission
const handleQrCodeSubmit = useCallback(async (lotNo: string) => { const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) { if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) {
console.log(` QR Code verified for lot: ${lotNo}`);
console.log(` QR Code verified for lot: ${lotNo}`);
if (!selectedLotForQr.stockOutLineId) { if (!selectedLotForQr.stockOutLineId) {
console.error("No stock out line ID found for this lot"); console.error("No stock out line ID found for this lot");
alert("No stock out line found for this lot. Please contact administrator."); alert("No stock out line found for this lot. Please contact administrator.");
return; return;
} }
// Store the required quantity before creating stock out line
// Store the required quantity before creating stock out line
const requiredQty = selectedLotForQr.requiredQty; const requiredQty = selectedLotForQr.requiredQty;
const lotId = selectedLotForQr.lotId; const lotId = selectedLotForQr.lotId;
try { try {
// Update stock out line status to 'checked' (QR scan completed)
// Update stock out line status to 'checked' (QR scan completed)
const stockOutLineUpdate = await updateStockOutLineStatus({ const stockOutLineUpdate = await updateStockOutLineStatus({
id: selectedLotForQr.stockOutLineId, id: selectedLotForQr.stockOutLineId,
status: 'checked', status: 'checked',
qty: selectedLotForQr.stockOutLineQty || 0 qty: selectedLotForQr.stockOutLineQty || 0
}); });
console.log(" Stock out line updated to 'checked':", stockOutLineUpdate);
console.log(" Stock out line updated to 'checked':", stockOutLineUpdate);
// Close modal
// Close modal
setQrModalOpen(false); setQrModalOpen(false);
setSelectedLotForQr(null); setSelectedLotForQr(null);
if (onLotDataRefresh) { if (onLotDataRefresh) {
await onLotDataRefresh(); await onLotDataRefresh();
} }
// Set pick quantity AFTER stock out line update is complete
// Set pick quantity AFTER stock out line update is complete
if (selectedRowId) { if (selectedRowId) {
// Add a small delay to ensure the data refresh is complete // Add a small delay to ensure the data refresh is complete
setTimeout(() => { setTimeout(() => {
onPickQtyChange(selectedRowId, lotId, requiredQty); onPickQtyChange(selectedRowId, lotId, requiredQty);
console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
}, 500); // 500ms delay to ensure refresh is complete }, 500); // 500ms delay to ensure refresh is complete
} }
// Show success message
// Show success message
console.log("Stock out line updated successfully!"); console.log("Stock out line updated successfully!");
} catch (error) { } catch (error) {
@@ -529,17 +529,17 @@ const LotTable: React.FC<LotTableProps> = ({
alert("Failed to update lot status. Please try again."); alert("Failed to update lot status. Please try again.");
} }
} else { } else {
// Handle case where lot numbers don't match
// Handle case where lot numbers don't match
console.error("QR scan mismatch:", { scanned: lotNo, expected: selectedLotForQr?.lotNo }); console.error("QR scan mismatch:", { scanned: lotNo, expected: selectedLotForQr?.lotNo });
alert(`QR scan mismatch! Expected: ${selectedLotForQr?.lotNo}, Scanned: ${lotNo}`); alert(`QR scan mismatch! Expected: ${selectedLotForQr?.lotNo}, Scanned: ${lotNo}`);
} }
}, [selectedLotForQr, selectedRowId, onPickQtyChange]); }, [selectedLotForQr, selectedRowId, onPickQtyChange]);


// 添加 PickExecutionForm 相关的状态
// 添加 PickExecutionForm 相关的状态
const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<LotPickData | null>(null); const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<LotPickData | null>(null);


// 添加处理函数
// 添加处理函数
const handlePickExecutionForm = useCallback((lot: LotPickData) => { const handlePickExecutionForm = useCallback((lot: LotPickData) => {
console.log("=== Pick Execution Form ==="); console.log("=== Pick Execution Form ===");
console.log("Lot data:", lot); console.log("Lot data:", lot);
@@ -561,12 +561,12 @@ const LotTable: React.FC<LotTableProps> = ({
try { try {
console.log("Pick execution form submitted:", data); console.log("Pick execution form submitted:", data);
// 调用 API 提交数据
// 调用 API 提交数据
const result = await recordPickExecutionIssue(data); const result = await recordPickExecutionIssue(data);
console.log("Pick execution issue recorded:", result); console.log("Pick execution issue recorded:", result);
if (result && result.code === "SUCCESS") { if (result && result.code === "SUCCESS") {
console.log(" Pick execution issue recorded successfully");
console.log(" Pick execution issue recorded successfully");
} else { } else {
console.error("❌ Failed to record pick execution issue:", result); console.error("❌ Failed to record pick execution issue:", result);
} }
@@ -574,7 +574,7 @@ const LotTable: React.FC<LotTableProps> = ({
setPickExecutionFormOpen(false); setPickExecutionFormOpen(false);
setSelectedLotForExecutionForm(null); setSelectedLotForExecutionForm(null);
// 刷新数据
// 刷新数据
if (onDataRefresh) { if (onDataRefresh) {
await onDataRefresh(); await onDataRefresh();
} }
@@ -636,10 +636,10 @@ const LotTable: React.FC<LotTableProps> = ({
<Checkbox <Checkbox
checked={selectedLotRowId === `row_${index}`} checked={selectedLotRowId === `row_${index}`}
onChange={() => onLotSelection(`row_${index}`, lot.lotId)} onChange={() => onLotSelection(`row_${index}`, lot.lotId)}
// 禁用 rejected、expired 和 status_unavailable 的批次
// 禁用 rejected、expired 和 status_unavailable 的批次
disabled={lot.lotAvailability === 'expired' || disabled={lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' || lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected'} // 添加 rejected
lot.lotAvailability === 'rejected'} // 添加 rejected
value={`row_${index}`} value={`row_${index}`}
name="lot-selection" name="lot-selection"
/> />
@@ -659,7 +659,7 @@ const LotTable: React.FC<LotTableProps> = ({
<Typography variant="caption" color="error" display="block"> <Typography variant="caption" color="error" display="block">
({lot.lotAvailability === 'expired' ? 'Expired' : ({lot.lotAvailability === 'expired' ? 'Expired' :
lot.lotAvailability === 'insufficient_stock' ? 'Insufficient' : lot.lotAvailability === 'insufficient_stock' ? 'Insufficient' :
lot.lotAvailability === 'rejected' ? 'Rejected' : // 添加 rejected 显示
lot.lotAvailability === 'rejected' ? 'Rejected' : // 添加 rejected 显示
'Unavailable'}) 'Unavailable'})
</Typography> </Typography>
)} */} )} */}
@@ -718,13 +718,13 @@ const LotTable: React.FC<LotTableProps> = ({
direction="row" direction="row"
spacing={1} spacing={1}
alignItems="center" alignItems="center"
justifyContent="center" // 添加水平居中
justifyContent="center" // 添加水平居中
sx={{ sx={{
width: '100%', // 确保占满整个单元格宽度
minHeight: '40px' // 设置最小高度确保垂直居中
width: '100%', // 确保占满整个单元格宽度
minHeight: '40px' // 设置最小高度确保垂直居中
}} }}
> >
{/* 恢复 TextField 用于正常数量输入 */}
{/* 恢复 TextField 用于正常数量输入 */}
<TextField <TextField
type="number" type="number"
size="small" size="small"
@@ -763,7 +763,7 @@ const LotTable: React.FC<LotTableProps> = ({
placeholder="0" placeholder="0"
/> />
{/* 添加 Pick Form 按钮用于问题情况 */}
{/* 添加 Pick Form 按钮用于问题情况 */}
<Button <Button
variant="outlined" variant="outlined"
size="small" size="small"
@@ -806,12 +806,12 @@ const LotTable: React.FC<LotTableProps> = ({
disabled={ disabled={
(lot.lotAvailability === 'expired' || (lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' || lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected') || // 添加 rejected
lot.lotAvailability === 'rejected') || // 添加 rejected
!pickQtyData[selectedRowId!]?.[lot.lotId] || !pickQtyData[selectedRowId!]?.[lot.lotId] ||
!lot.stockOutLineStatus || !lot.stockOutLineStatus ||
!['pending','checked', 'partially_completed'].includes(lot.stockOutLineStatus.toLowerCase()) !['pending','checked', 'partially_completed'].includes(lot.stockOutLineStatus.toLowerCase())
} }
// Allow submission for available AND insufficient_stock lots
// Allow submission for available AND insufficient_stock lots
sx={{ sx={{
fontSize: '0.75rem', fontSize: '0.75rem',
py: 0.5, py: 0.5,
@@ -829,7 +829,7 @@ const LotTable: React.FC<LotTableProps> = ({
</Table> </Table>
</TableContainer> </TableContainer>
{/* Status Messages Display */}
{/* Status Messages Display */}
{paginatedLotTableData.length > 0 && ( {paginatedLotTableData.length > 0 && (
<Box sx={{ mt: 2, p: 2, backgroundColor: 'grey.50', borderRadius: 1 }}> <Box sx={{ mt: 2, p: 2, backgroundColor: 'grey.50', borderRadius: 1 }}>
{paginatedLotTableData.map((lot, index) => ( {paginatedLotTableData.map((lot, index) => (
@@ -858,7 +858,7 @@ const LotTable: React.FC<LotTableProps> = ({
} }
/> />
{/* QR Code Modal */}
{/* QR Code Modal */}
<QrCodeModal <QrCodeModal
open={qrModalOpen} open={qrModalOpen}
onClose={() => { onClose={() => {
@@ -871,7 +871,7 @@ const LotTable: React.FC<LotTableProps> = ({
onQrCodeSubmit={handleQrCodeSubmit} onQrCodeSubmit={handleQrCodeSubmit}
/> />


{/* Pick Execution Form Modal */}
{/* Pick Execution Form Modal */}
{pickExecutionFormOpen && selectedLotForExecutionForm && selectedRow && ( {pickExecutionFormOpen && selectedLotForExecutionForm && selectedRow && (
<PickExecutionForm <PickExecutionForm
open={pickExecutionFormOpen} open={pickExecutionFormOpen}


+ 20
- 20
src/components/PickOrderSearch/PickExecution.tsx View File

@@ -69,7 +69,7 @@ import SearchBox, { Criterion } from "../SearchBox";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { CreateStockOutLine } from "@/app/api/pickOrder/actions"; import { CreateStockOutLine } from "@/app/api/pickOrder/actions";
import LotTable from './LotTable'; import LotTable from './LotTable';
import PickOrderDetailsTable from './PickOrderDetailsTable'; // Import the new component
import PickOrderDetailsTable from './PickOrderDetailsTable'; // Import the new component
import { updateInventoryLotLineStatus, updateInventoryStatus, updateInventoryLotLineQuantities } from "@/app/api/inventory/actions"; import { updateInventoryLotLineStatus, updateInventoryStatus, updateInventoryLotLineQuantities } from "@/app/api/inventory/actions";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig"; import { SessionWithTokens } from "@/config/authConfig";
@@ -147,7 +147,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
const [selectedLotRowId, setSelectedLotRowId] = useState<string | null>(null); const [selectedLotRowId, setSelectedLotRowId] = useState<string | null>(null);
const [selectedLotId, setSelectedLotId] = useState<number | null>(null); const [selectedLotId, setSelectedLotId] = useState<number | null>(null);


// Keep only the main table paging controller
// Keep only the main table paging controller
const [mainTablePagingController, setMainTablePagingController] = useState({ const [mainTablePagingController, setMainTablePagingController] = useState({
pageNum: 0, pageNum: 0,
pageSize: 10, pageSize: 10,
@@ -383,14 +383,14 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
try { try {
// FIXED: 计算累计拣货数量
// FIXED: 计算累计拣货数量
const totalPickedForThisLot = (selectedLot.actualPickQty || 0) + qty; const totalPickedForThisLot = (selectedLot.actualPickQty || 0) + qty;
console.log(" DEBUG - Previous picked:", selectedLot.actualPickQty || 0); console.log(" DEBUG - Previous picked:", selectedLot.actualPickQty || 0);
console.log("🔍 DEBUG - Current submit:", qty); console.log("🔍 DEBUG - Current submit:", qty);
console.log("🔍 DEBUG - Total picked:", totalPickedForThisLot); console.log("🔍 DEBUG - Total picked:", totalPickedForThisLot);
console.log("�� DEBUG - Required qty:", selectedLot.requiredQty); console.log("�� DEBUG - Required qty:", selectedLot.requiredQty);
// FIXED: 状态应该基于累计拣货数量
// FIXED: 状态应该基于累计拣货数量
let newStatus = 'partially_completed'; let newStatus = 'partially_completed';
if (totalPickedForThisLot >= selectedLot.requiredQty) { if (totalPickedForThisLot >= selectedLot.requiredQty) {
newStatus = 'completed'; newStatus = 'completed';
@@ -405,7 +405,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
qty: qty qty: qty
}); });
console.log(" Stock out line updated:", stockOutLineUpdate);
console.log(" Stock out line updated:", stockOutLineUpdate);
} catch (error) { } catch (error) {
console.error("❌ Error updating stock out line:", error); console.error("❌ Error updating stock out line:", error);
@@ -423,11 +423,11 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
console.log("Inventory lot line updated:", inventoryLotLineUpdate); console.log("Inventory lot line updated:", inventoryLotLineUpdate);
} }
// RE-ENABLE: Check if pick order should be completed
// RE-ENABLE: Check if pick order should be completed
if (newStatus === 'completed') { if (newStatus === 'completed') {
console.log(" Stock out line completed, checking if entire pick order is complete...");
console.log(" Stock out line completed, checking if entire pick order is complete...");
// 添加调试日志来查看所有 pick orders 的 consoCode
// 添加调试日志来查看所有 pick orders 的 consoCode
console.log("📋 DEBUG - All pick orders and their consoCodes:"); console.log("📋 DEBUG - All pick orders and their consoCodes:");
if (pickOrderDetails) { if (pickOrderDetails) {
pickOrderDetails.pickOrders.forEach((pickOrder, index) => { pickOrderDetails.pickOrders.forEach((pickOrder, index) => {
@@ -435,7 +435,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
}); });
} }
// FIXED: 直接查找 consoCode,不依赖 selectedRow
// FIXED: 直接查找 consoCode,不依赖 selectedRow
if (pickOrderDetails) { if (pickOrderDetails) {
let currentConsoCode: string | null = null; let currentConsoCode: string | null = null;
@@ -443,7 +443,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
for (const pickOrder of pickOrderDetails.pickOrders) { for (const pickOrder of pickOrderDetails.pickOrders) {
const foundLine = pickOrder.pickOrderLines.find(line => line.id === selectedRowId); const foundLine = pickOrder.pickOrderLines.find(line => line.id === selectedRowId);
if (foundLine) { if (foundLine) {
// 直接使用 pickOrder.code 作为 consoCode
// 直接使用 pickOrder.code 作为 consoCode
currentConsoCode = pickOrder.consoCode; currentConsoCode = pickOrder.consoCode;
console.log(`�� DEBUG - Found consoCode for line ${selectedRowId}: ${currentConsoCode} (from pick order ${pickOrder.id})`); console.log(`�� DEBUG - Found consoCode for line ${selectedRowId}: ${currentConsoCode} (from pick order ${pickOrder.id})`);
break; break;
@@ -545,7 +545,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
setSelectedItemForQc(item); setSelectedItemForQc(item);
}, []); }, []);


// Main table pagination handlers
// Main table pagination handlers
const handleMainTablePageChange = useCallback((event: unknown, newPage: number) => { const handleMainTablePageChange = useCallback((event: unknown, newPage: number) => {
setMainTablePagingController(prev => ({ setMainTablePagingController(prev => ({
...prev, ...prev,
@@ -781,7 +781,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
} }
const stockOutLineData: CreateStockOutLine = { const stockOutLineData: CreateStockOutLine = {
consoCode: correctConsoCode || pickOrderDetails?.consoCode || "", // 使用正确的 consoCode
consoCode: correctConsoCode || pickOrderDetails?.consoCode || "", // 使用正确的 consoCode
pickOrderLineId: selectedRowId, pickOrderLineId: selectedRowId,
inventoryLotLineId: inventoryLotLineId, inventoryLotLineId: inventoryLotLineId,
qty: 0.0 qty: 0.0
@@ -806,7 +806,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
await handleFetchAllPickOrderDetails(); await handleFetchAllPickOrderDetails();
console.log(" Data refresh completed - lot selection maintained!");
console.log(" Data refresh completed - lot selection maintained!");
} catch (refreshError) { } catch (refreshError) {
console.error("❌ Error refreshing data:", refreshError); console.error("❌ Error refreshing data:", refreshError);
} }
@@ -834,13 +834,13 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
setSelectedLotRowId(currentSelectedLotRowId); setSelectedLotRowId(currentSelectedLotRowId);
setSelectedLotId(currentSelectedLotId); setSelectedLotId(currentSelectedLotId);
console.log(" Data refreshed with selection preserved");
console.log(" Data refreshed with selection preserved");
} catch (error) { } catch (error) {
console.error("❌ Error refreshing data:", error); console.error("❌ Error refreshing data:", error);
} }
}, [selectedRowId, selectedLotRowId, selectedLotId, handleRowSelect, handleFetchAllPickOrderDetails]); }, [selectedRowId, selectedLotRowId, selectedLotId, handleRowSelect, handleFetchAllPickOrderDetails]);


// Search criteria
// Search criteria
const searchCriteria: Criterion<any>[] = useMemo( const searchCriteria: Criterion<any>[] = useMemo(
() => [ () => [
{ {
@@ -868,7 +868,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
[t], [t],
); );


// Search handler
// Search handler
const handleSearch = useCallback((query: Record<string, any>) => { const handleSearch = useCallback((query: Record<string, any>) => {
setSearchQuery({ ...query }); setSearchQuery({ ...query });
console.log("Search query:", query); console.log("Search query:", query);
@@ -899,7 +899,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
console.log("Filtered pick orders count:", filtered.length); console.log("Filtered pick orders count:", filtered.length);
}, [originalPickOrderData, t]); }, [originalPickOrderData, t]);


// Reset handler
// Reset handler
const handleReset = useCallback(() => { const handleReset = useCallback(() => {
setSearchQuery({}); setSearchQuery({});
if (originalPickOrderData) { if (originalPickOrderData) {
@@ -907,7 +907,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
} }
}, [originalPickOrderData]); }, [originalPickOrderData]);


// Debug the lot data
// Debug the lot data
useEffect(() => { useEffect(() => {
console.log("Lot data:", lotData); console.log("Lot data:", lotData);
console.log("Pick Qty Data:", pickQtyData); console.log("Pick Qty Data:", pickQtyData);
@@ -925,7 +925,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
/> />
</Box> </Box>


{/* Main table using the new component */}
{/* Main table using the new component */}
<Box> <Box>
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
{t("Pick Order Details")} {t("Pick Order Details")}
@@ -969,7 +969,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
setShowInputBody={setShowInputBody} setShowInputBody={setShowInputBody}
selectedLotForInput={selectedLotForInput} selectedLotForInput={selectedLotForInput}
generateInputBody={generateInputBody} generateInputBody={generateInputBody}
// Add missing props
// Add missing props
totalPickedByAllPickOrders={0} // You can calculate this from lotData if needed totalPickedByAllPickOrders={0} // You can calculate this from lotData if needed
outQty={0} // You can calculate this from lotData if needed outQty={0} // You can calculate this from lotData if needed
holdQty={0} // You can calculate this from lotData if needed holdQty={0} // You can calculate this from lotData if needed


+ 10
- 10
src/components/PickOrderSearch/PickExecutionForm.tsx View File

@@ -53,7 +53,7 @@ interface PickExecutionFormProps {
selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null; selectedPickOrderLine: (GetPickOrderLineInfo & { pickOrderCode: string }) | null;
pickOrderId?: number; pickOrderId?: number;
pickOrderCreateDate: any; pickOrderCreateDate: any;
// Remove these props since we're not handling normal cases
// Remove these props since we're not handling normal cases
// onNormalPickSubmit?: (lineId: number, lotId: number, qty: number) => Promise<void>; // onNormalPickSubmit?: (lineId: number, lotId: number, qty: number) => Promise<void>;
// selectedRowId?: number | null; // selectedRowId?: number | null;
} }
@@ -75,7 +75,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({
selectedPickOrderLine, selectedPickOrderLine,
pickOrderId, pickOrderId,
pickOrderCreateDate, pickOrderCreateDate,
// Remove these props
// Remove these props
// onNormalPickSubmit, // onNormalPickSubmit,
// selectedRowId, // selectedRowId,
}) => { }) => {
@@ -166,7 +166,7 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => {
} }
}, [errors]); }, [errors]);


// Update form validation to require either missQty > 0 OR badItemQty > 0
// Update form validation to require either missQty > 0 OR badItemQty > 0
const validateForm = (): boolean => { const validateForm = (): boolean => {
const newErrors: FormErrors = {}; const newErrors: FormErrors = {};
@@ -174,17 +174,17 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => {
newErrors.actualPickQty = t('Qty is required'); newErrors.actualPickQty = t('Qty is required');
} }
// ADD: Check if actual pick qty exceeds remaining available qty
// ADD: Check if actual pick qty exceeds remaining available qty
if (formData.actualPickQty && formData.actualPickQty > remainingAvailableQty) { if (formData.actualPickQty && formData.actualPickQty > remainingAvailableQty) {
newErrors.actualPickQty = t('Qty is not allowed to be greater than remaining available qty'); newErrors.actualPickQty = t('Qty is not allowed to be greater than remaining available qty');
} }
// ADD: Check if actual pick qty exceeds required qty
// ADD: Check if actual pick qty exceeds required qty
if (formData.actualPickQty && formData.actualPickQty > requiredQty) { if (formData.actualPickQty && formData.actualPickQty > requiredQty) {
newErrors.actualPickQty = t('Qty is not allowed to be greater than required qty'); newErrors.actualPickQty = t('Qty is not allowed to be greater than required qty');
} }
// NEW: Require either missQty > 0 OR badItemQty > 0 (at least one issue must be reported)
// NEW: Require either missQty > 0 OR badItemQty > 0 (at least one issue must be reported)
const hasMissQty = formData.missQty && formData.missQty > 0; const hasMissQty = formData.missQty && formData.missQty > 0;
const hasBadItemQty = formData.badItemQty && formData.badItemQty > 0; const hasBadItemQty = formData.badItemQty && formData.badItemQty > 0;
@@ -229,11 +229,11 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => {
return ( return (
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth> <Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
<DialogTitle> <DialogTitle>
{t('Pick Execution Issue Form')} {/* Always show issue form title */}
{t('Pick Execution Issue Form')} {/* Always show issue form title */}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<Box sx={{ mt: 2 }}> <Box sx={{ mt: 2 }}>
{/* Add instruction text */}
{/* Add instruction text */}
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12}> <Grid item xs={12}>
<Box sx={{ p: 2, backgroundColor: '#fff3cd', borderRadius: 1, mb: 2 }}> <Box sx={{ p: 2, backgroundColor: '#fff3cd', borderRadius: 1, mb: 2 }}>
@@ -243,7 +243,7 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => {
</Box> </Box>
</Grid> </Grid>
{/* Keep the existing form fields */}
{/* Keep the existing form fields */}
<Grid item xs={6}> <Grid item xs={6}>
<TextField <TextField
fullWidth fullWidth
@@ -305,7 +305,7 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => {
/> />
</Grid> </Grid>
{/* Show issue description and handler fields when bad items > 0 */}
{/* Show issue description and handler fields when bad items > 0 */}
{(formData.badItemQty && formData.badItemQty > 0) ? ( {(formData.badItemQty && formData.badItemQty > 0) ? (
<> <>
<Grid item xs={12}> <Grid item xs={12}>


+ 3
- 3
src/components/PickOrderSearch/PickOrderDetailsTable.tsx View File

@@ -49,7 +49,7 @@ const PickOrderDetailsTable: React.FC<PickOrderDetailsTableProps> = ({
const availableQty = line.availableQty ?? 0; const availableQty = line.availableQty ?? 0;
const balanceToPick = availableQty - line.requiredQty; const balanceToPick = availableQty - line.requiredQty;
// Handle both string and array date formats from the optimized API
// Handle both string and array date formats from the optimized API
let formattedTargetDate = 'N/A'; let formattedTargetDate = 'N/A';
if (pickOrder.targetDate) { if (pickOrder.targetDate) {
if (typeof pickOrder.targetDate === 'string') { if (typeof pickOrder.targetDate === 'string') {
@@ -66,14 +66,14 @@ const PickOrderDetailsTable: React.FC<PickOrderDetailsTableProps> = ({
pickOrderCode: pickOrder.code, pickOrderCode: pickOrder.code,
targetDate: formattedTargetDate, targetDate: formattedTargetDate,
balanceToPick: balanceToPick, balanceToPick: balanceToPick,
pickedQty: line.pickedQty, // This now comes from the optimized API
pickedQty: line.pickedQty, // This now comes from the optimized API
availableQty: availableQty, availableQty: availableQty,
}; };
}) })
); );
}, [pickOrderDetails]); }, [pickOrderDetails]);


// Paginated data
// Paginated data
const paginatedMainTableData = useMemo(() => { const paginatedMainTableData = useMemo(() => {
const startIndex = pageNum * pageSize; const startIndex = pageNum * pageSize;
const endIndex = startIndex + pageSize; const endIndex = startIndex + pageSize;


+ 38
- 38
src/components/PickOrderSearch/PickQcStockInModalVer3.tsx View File

@@ -33,7 +33,7 @@ import EscalationComponent from "../PoDetail/EscalationComponent";
import { fetchPickOrderQcResult, savePickOrderQcResult } from "@/app/api/qc/actions"; import { fetchPickOrderQcResult, savePickOrderQcResult } from "@/app/api/qc/actions";
import { import {
updateInventoryLotLineStatus updateInventoryLotLineStatus
} from "@/app/api/inventory/actions"; // 导入新的 API
} from "@/app/api/inventory/actions"; // 导入新的 API
import { dayjsToDateTimeString } from "@/app/utils/formatUtil"; import { dayjsToDateTimeString } from "@/app/utils/formatUtil";
import dayjs from "dayjs"; import dayjs from "dayjs";


@@ -42,8 +42,8 @@ interface ExtendedQcItem extends QcItemWithChecks {
qcPassed?: boolean; qcPassed?: boolean;
failQty?: number; failQty?: number;
remarks?: string; remarks?: string;
order?: number; // Add order property
stableId?: string; // Also add stableId for better row identification
order?: number; // Add order property
stableId?: string; // Also add stableId for better row identification
} }
interface Props extends CommonProps { interface Props extends CommonProps {
itemDetail: GetPickOrderLineInfo & { itemDetail: GetPickOrderLineInfo & {
@@ -55,7 +55,7 @@ interface Props extends CommonProps {
selectedLotId?: number; selectedLotId?: number;
onStockOutLineUpdate?: () => void; onStockOutLineUpdate?: () => void;
lotData: LotPickData[]; lotData: LotPickData[];
// Add missing props
// Add missing props
pickQtyData?: PickQtyData; pickQtyData?: PickQtyData;
selectedRowId?: number; selectedRowId?: number;
} }
@@ -104,7 +104,7 @@ interface Props extends CommonProps {
}; };
qcItems: ExtendedQcItem[]; // Change to ExtendedQcItem qcItems: ExtendedQcItem[]; // Change to ExtendedQcItem
setQcItems: Dispatch<SetStateAction<ExtendedQcItem[]>>; // Change to ExtendedQcItem setQcItems: Dispatch<SetStateAction<ExtendedQcItem[]>>; // Change to ExtendedQcItem
// Add props for stock out line update
// Add props for stock out line update
selectedLotId?: number; selectedLotId?: number;
onStockOutLineUpdate?: () => void; onStockOutLineUpdate?: () => void;
lotData: LotPickData[]; lotData: LotPickData[];
@@ -193,7 +193,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
failQty: item.isPassed ? 0 : (item.failQty || 0), // 0 for passed, actual qty for failed failQty: item.isPassed ? 0 : (item.failQty || 0), // 0 for passed, actual qty for failed
type: "pick_order_qc", type: "pick_order_qc",
remarks: item.remarks || "", remarks: item.remarks || "",
qcPassed: item.isPassed, // This will now be included
qcPassed: item.isPassed, // This will now be included
})); }));


// Store the submitted data for debug display // Store the submitted data for debug display
@@ -217,17 +217,17 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
} }
}; };


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


// Add this helper function at the top of the component
// Add this helper function at the top of the component
const safeClose = useCallback(() => { const safeClose = useCallback(() => {
if (onClose) { if (onClose) {
// Create a mock event object that satisfies the Modal onClose signature // Create a mock event object that satisfies the Modal onClose signature
@@ -259,12 +259,12 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
isPersistent: () => false isPersistent: () => false
} as any; } as any;
// Fixed: Pass both event and reason parameters
// Fixed: Pass both event and reason parameters
onClose(mockEvent, 'escapeKeyDown'); // 'escapeKeyDown' is a valid reason onClose(mockEvent, 'escapeKeyDown'); // 'escapeKeyDown' is a valid reason
} }
}, [onClose]); }, [onClose]);


// 修改:移除 alert 弹窗,改为控制台日志
// 修改:移除 alert 弹窗,改为控制台日志
const onSubmitQc = useCallback<SubmitHandler<any>>( const onSubmitQc = useCallback<SubmitHandler<any>>(
async (data, event) => { async (data, event) => {
setIsSubmitting(true); setIsSubmitting(true);
@@ -276,7 +276,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
const validationErrors : string[] = []; const validationErrors : string[] = [];
const selectedLot = lotData.find(lot => lot.stockOutLineId === selectedLotId); const selectedLot = lotData.find(lot => lot.stockOutLineId === selectedLotId);
// Add safety check for selectedLot
// Add safety check for selectedLot
if (!selectedLot) { if (!selectedLot) {
console.error("Selected lot not found"); console.error("Selected lot not found");
return; return;
@@ -313,23 +313,23 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
return; return;
} }
// Handle different QC decisions
// Handle different QC decisions
if (selectedLotId) { if (selectedLotId) {
try { try {
const allPassed = qcData.qcItems.every(item => item.isPassed); const allPassed = qcData.qcItems.every(item => item.isPassed);
if (qcDecision === "2") { if (qcDecision === "2") {
// QC Decision 2: Report and Re-pick
// QC Decision 2: Report and Re-pick
console.log("QC Decision 2 - Report and Re-pick: Rejecting lot and marking as unavailable"); console.log("QC Decision 2 - Report and Re-pick: Rejecting lot and marking as unavailable");
// Inventory lot line status: unavailable
// Inventory lot line status: unavailable
if (selectedLot) { if (selectedLot) {
try { try {
console.log("=== DEBUG: Updating inventory lot line status ==="); console.log("=== DEBUG: Updating inventory lot line status ===");
console.log("Selected lot:", selectedLot); console.log("Selected lot:", selectedLot);
console.log("Selected lot ID:", selectedLotId); console.log("Selected lot ID:", selectedLotId);
// FIX: Only send the fields that the backend expects
// FIX: Only send the fields that the backend expects
const updateData = { const updateData = {
inventoryLotLineId: selectedLot.lotId, inventoryLotLineId: selectedLot.lotId,
status: 'unavailable' status: 'unavailable'
@@ -339,7 +339,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
console.log("Update data:", updateData); console.log("Update data:", updateData);
const result = await updateInventoryLotLineStatus(updateData); const result = await updateInventoryLotLineStatus(updateData);
console.log(" Inventory lot line status updated successfully:", result);
console.log(" Inventory lot line status updated successfully:", result);
} catch (error) { } catch (error) {
console.error("❌ Error updating inventory lot line status:", error); console.error("❌ Error updating inventory lot line status:", error);
@@ -359,28 +359,28 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
return; return;
} }
// Close modal and refresh data
safeClose(); // Fixed: Use safe close function with both parameters
// Close modal and refresh data
safeClose(); // Fixed: Use safe close function with both parameters
if (onStockOutLineUpdate) { if (onStockOutLineUpdate) {
onStockOutLineUpdate(); onStockOutLineUpdate();
} }
} else if (qcDecision === "1") { } else if (qcDecision === "1") {
// QC Decision 1: Accept
// QC Decision 1: Accept
console.log("QC Decision 1 - Accept: QC passed"); console.log("QC Decision 1 - Accept: QC passed");
// Stock out line status: checked (QC completed)
// Stock out line status: checked (QC completed)
await updateStockOutLineStatus({ await updateStockOutLineStatus({
id: selectedLotId, id: selectedLotId,
status: 'checked', status: 'checked',
qty: acceptQty || 0 qty: acceptQty || 0
}); });
// Inventory lot line status: NO CHANGE needed
// Inventory lot line status: NO CHANGE needed
// Keep the existing status from handleSubmitPickQty // Keep the existing status from handleSubmitPickQty
// Close modal and refresh data
safeClose(); // Fixed: Use safe close function with both parameters
// Close modal and refresh data
safeClose(); // Fixed: Use safe close function with both parameters
if (onStockOutLineUpdate) { if (onStockOutLineUpdate) {
onStockOutLineUpdate(); onStockOutLineUpdate();
} }
@@ -399,7 +399,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
console.log("QC results saved successfully!"); console.log("QC results saved successfully!");
// Show warning dialog for failed QC items when accepting
// Show warning dialog for failed QC items when accepting
if (qcDecision === "1" && !qcData.qcItems.every((q) => q.isPassed)) { if (qcDecision === "1" && !qcData.qcItems.every((q) => q.isPassed)) {
submitDialogWithWarning(() => { submitDialogWithWarning(() => {
closeHandler?.({}, 'escapeKeyDown'); closeHandler?.({}, 'escapeKeyDown');
@@ -448,7 +448,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
value={current.qcPassed === undefined ? "" : (current.qcPassed ? "true" : "false")} value={current.qcPassed === undefined ? "" : (current.qcPassed ? "true" : "false")}
onChange={(e) => { onChange={(e) => {
const value = e.target.value === "true"; const value = e.target.value === "true";
// Simple state update
// Simple state update
setQcItems(prev => setQcItems(prev =>
prev.map(item => prev.map(item =>
item.id === params.id item.id === params.id
@@ -490,10 +490,10 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
<TextField <TextField
type="number" type="number"
size="small" size="small"
// 修改:失败项目自动显示 Lot Required Pick Qty
// 修改:失败项目自动显示 Lot Required Pick Qty
value={!params.row.qcPassed ? (0) : 0} value={!params.row.qcPassed ? (0) : 0}
disabled={params.row.qcPassed} disabled={params.row.qcPassed}
// 移除 onChange,因为数量是固定的
// 移除 onChange,因为数量是固定的
// onChange={(e) => { // onChange={(e) => {
// const v = e.target.value; // const v = e.target.value;
// const next = v === "" ? undefined : Number(v); // const next = v === "" ? undefined : Number(v);
@@ -535,7 +535,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
[t], [t],
); );


// Add stable update function
// Add stable update function
const handleQcResultChange = useCallback((itemId: number, qcPassed: boolean) => { const handleQcResultChange = useCallback((itemId: number, qcPassed: boolean) => {
setQcItems(prevItems => setQcItems(prevItems =>
prevItems.map(item => prevItems.map(item =>
@@ -546,16 +546,16 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
); );
}, []); }, []);


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


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


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


{/* Combirne options 2 & 3 into one */}
{/* Combirne options 2 & 3 into one */}
<FormControlLabel <FormControlLabel
value="2" value="2"
control={<Radio />} control={<Radio />}
@@ -649,7 +649,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
</FormControl> </FormControl>
</Grid> </Grid>


{/* Show escalation component when QC Decision = 2 (Report and Re-pick) */}
{/* Show escalation component when QC Decision = 2 (Report and Re-pick) */}


<Grid item xs={12} sx={{ mt: 2 }}> <Grid item xs={12} sx={{ mt: 2 }}>


+ 2
- 2
src/components/PickOrderSearch/newcreatitem.tsx View File

@@ -568,7 +568,7 @@ const handleQtyBlur = useCallback((itemId: number) => {
return; return;
} }
// 修复:自动填充 type 为 "Consumable",不再强制用户选择
// 修复:自动填充 type 为 "Consumable",不再强制用户选择
// if (!data.type) { // if (!data.type) {
// alert(t("Please select product type")); // alert(t("Please select product type"));
// return; // return;
@@ -625,7 +625,7 @@ const handleQtyBlur = useCallback((itemId: number) => {
} }
} }
// 修复:自动使用 "Consumable" 作为默认 type
// 修复:自动使用 "Consumable" 作为默认 type
const pickOrderData: SavePickOrderRequest = { const pickOrderData: SavePickOrderRequest = {
type: data.type || "Consumable", // 如果用户选择了 type 就用用户的,否则默认 "Consumable" type: data.type || "Consumable", // 如果用户选择了 type 就用用户的,否则默认 "Consumable"
targetDate: formattedTargetDate, targetDate: formattedTargetDate,


+ 6
- 6
src/components/ProductionProcess/MachineScanner.tsx View File

@@ -72,18 +72,18 @@ const MachineScanner: React.FC<MachineScannerProps> = ({
try { try {
let machineCode: string; let machineCode: string;
// 尝试解析 JSON
// 尝试解析 JSON
try { try {
const scannedObj: MachineQrCode = JSON.parse(scannedInput); const scannedObj: MachineQrCode = JSON.parse(scannedInput);
machineCode = scannedObj.code; machineCode = scannedObj.code;
} catch (jsonError) { } catch (jsonError) {
// 如果不是 JSON,尝试从花括号中提取
// 如果不是 JSON,尝试从花括号中提取
const match = scannedInput.match(/\{([^}?]+)\??}?/); const match = scannedInput.match(/\{([^}?]+)\??}?/);
if (match && match[1]) { if (match && match[1]) {
machineCode = match[1].trim(); machineCode = match[1].trim();
console.log("Extracted machine code from braces:", machineCode); console.log("Extracted machine code from braces:", machineCode);
} else { } else {
// 如果没有花括号,直接使用输入值
// 如果没有花括号,直接使用输入值
machineCode = scannedInput.replace(/[{}?]/g, '').trim(); machineCode = scannedInput.replace(/[{}?]/g, '').trim();
console.log("Using plain machine code:", machineCode); console.log("Using plain machine code:", machineCode);
} }
@@ -94,7 +94,7 @@ const MachineScanner: React.FC<MachineScannerProps> = ({
return; return;
} }


// 首先尝试从 API 获取
// 首先尝试从 API 获取
const response = await isCorrectMachineUsed(machineCode); const response = await isCorrectMachineUsed(machineCode);


if (response.message === "Success") { if (response.message === "Success") {
@@ -109,7 +109,7 @@ const MachineScanner: React.FC<MachineScannerProps> = ({
target.value = ""; target.value = "";
setScanError(null); setScanError(null);
} else { } else {
// 如果 API 失败,尝试从本地默认数据查找
// 如果 API 失败,尝试从本地默认数据查找
const localMachine = machineDatabase.find( const localMachine = machineDatabase.find(
(m) => m.code.toLowerCase() === machineCode.toLowerCase() (m) => m.code.toLowerCase() === machineCode.toLowerCase()
); );
@@ -125,7 +125,7 @@ const MachineScanner: React.FC<MachineScannerProps> = ({
target.value = ""; target.value = "";
setScanError(null); setScanError(null);
console.log(" Used local machine data:", localMachine);
console.log(" Used local machine data:", localMachine);
} else { } else {
setScanError( setScanError(
"Machine not found. Please check the code and try again." "Machine not found. Please check the code and try again."


+ 8
- 8
src/components/ProductionProcess/OperatorScanner.tsx View File

@@ -13,7 +13,7 @@ import {
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { isOperatorExist } from "@/app/api/jo/actions"; import { isOperatorExist } from "@/app/api/jo/actions";
import { OperatorQrCode } from "./types"; import { OperatorQrCode } from "./types";
// 新增:导入 user API
// 新增:导入 user API
import { fetchUserDetails } from "@/app/api/user/actions"; import { fetchUserDetails } from "@/app/api/user/actions";
import { fetchNameList } from "@/app/api/user/actions"; import { fetchNameList } from "@/app/api/user/actions";


@@ -71,19 +71,19 @@ const OperatorScanner: React.FC<OperatorScannerProps> = ({
console.log("Raw input:", usernameInput); console.log("Raw input:", usernameInput);
try { try {
// 检查是否是测试快捷格式 {2fitest<id>}
// 检查是否是测试快捷格式 {2fitest<id>}
const testMatch = usernameInput.match(/\{2fitest(\d+)\??}?/i); const testMatch = usernameInput.match(/\{2fitest(\d+)\??}?/i);
if (testMatch && testMatch[1]) { if (testMatch && testMatch[1]) {
const userId = parseInt(testMatch[1]); const userId = parseInt(testMatch[1]);
console.log(`🧪 Test mode: Fetching user with ID ${userId} from API`); console.log(`🧪 Test mode: Fetching user with ID ${userId} from API`);
try { try {
// 方案 1:使用 fetchNameList 获取所有用户,然后找到对应 ID
// 方案 1:使用 fetchNameList 获取所有用户,然后找到对应 ID
const nameList = await fetchNameList(); const nameList = await fetchNameList();
const matchedUser = nameList.find(user => user.id === userId); const matchedUser = nameList.find(user => user.id === userId);
if (matchedUser) { if (matchedUser) {
// 将 NameList 转换为 Operator 格式
// 将 NameList 转换为 Operator 格式
const operator: Operator = { const operator: Operator = {
id: matchedUser.id, id: matchedUser.id,
name: matchedUser.name, name: matchedUser.name,
@@ -100,7 +100,7 @@ const OperatorScanner: React.FC<OperatorScannerProps> = ({
target.value = ""; target.value = "";
setScanError(null); setScanError(null);
console.log(` Added operator from API:`, operator);
console.log(` Added operator from API:`, operator);
return; return;
} else { } else {
setScanError( setScanError(
@@ -121,18 +121,18 @@ const OperatorScanner: React.FC<OperatorScannerProps> = ({
let username: string; let username: string;
// 尝试解析 JSON
// 尝试解析 JSON
try { try {
const usernameObj: OperatorQrCode = JSON.parse(usernameInput); const usernameObj: OperatorQrCode = JSON.parse(usernameInput);
username = usernameObj.username; username = usernameObj.username;
} catch (jsonError) { } catch (jsonError) {
// 如果不是 JSON,尝试从花括号中提取
// 如果不是 JSON,尝试从花括号中提取
const match = usernameInput.match(/\{([^}?]+)\??}?/); const match = usernameInput.match(/\{([^}?]+)\??}?/);
if (match && match[1]) { if (match && match[1]) {
username = match[1].trim(); username = match[1].trim();
console.log("Extracted username from braces:", username); console.log("Extracted username from braces:", username);
} else { } else {
// 如果没有花括号,直接使用输入值
// 如果没有花括号,直接使用输入值
username = usernameInput.replace(/[{}?]/g, '').trim(); username = usernameInput.replace(/[{}?]/g, '').trim();
console.log("Using plain username:", username); console.log("Using plain username:", username);
} }


+ 656
- 0
src/components/ProductionProcess/ProductionProcessDetail.tsx View File

@@ -0,0 +1,656 @@
"use client";
import React, { useCallback, useEffect, useState, useRef } from "react";
import {
Box,
Button,
Paper,
Stack,
Typography,
TextField,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Chip,
Card,
CardContent,
CircularProgress,
} from "@mui/material";
import QrCodeIcon from '@mui/icons-material/QrCode';
import { useTranslation } from "react-i18next";
import { Operator, Machine } from "@/app/api/jo";
import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider';
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import dayjs from "dayjs";
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import {
fetchProductProcessById,
fetchProductProcessLines,
updateProductProcessLineQrscan,
fetchProductProcessLineDetail,
ProductProcessResponse,
ProductProcessLineResponse,
startProductProcessLine
} from "@/app/api/jo/actions";
import { fetchNameList, NameList } from "@/app/api/user/actions";

// 添加设备数据库(从 MachineScanner.tsx)
const machineDatabase: Machine[] = [
{ id: 1, name: "CNC Mill #1", code: "CNC001", qrCode: "QR-CNC001" },
{ id: 2, name: "Lathe #2", code: "LAT002", qrCode: "QR-LAT002" },
{ id: 3, name: "Press #3", code: "PRS003", qrCode: "QR-PRS003" },
{ id: 4, name: "Welder #4", code: "WLD004", qrCode: "QR-WLD004" },
{ id: 5, name: "Drill Press #5", code: "DRL005", qrCode: "QR-DRL005" },
];

interface ProcessLine {
id: number;
seqNo: number;
name: string;
description?: string;
equipmentType?: string;
startTime?: string;
endTime?: string;
outputFromProcessQty?: number;
outputFromProcessUom?: string;
defectQty?: number;
scrapQty?: number;
byproductName?: string;
byproductQty?: number;
handlerId?: number; // 添加 handlerId
}

interface ProductProcessDetailProps {
processId: number;
onBack: () => void;
}

const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
processId,
onBack,
}) => {
const { t } = useTranslation();
const { data: session } = useSession() as { data: SessionWithTokens | null };
const currentUserId = session?.id ? parseInt(session.id) : undefined;
const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext();
// 基本信息
const [processData, setProcessData] = useState<any>(null);
const [lines, setLines] = useState<ProcessLine[]>([]);
const [loading, setLoading] = useState(false);
// 选中的 line 和执行状态
const [selectedLineId, setSelectedLineId] = useState<number | null>(null);
const [isExecutingLine, setIsExecutingLine] = useState(false);
// 扫描器状态
const [isManualScanning, setIsManualScanning] = useState(false);
const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
const [scannedOperators, setScannedOperators] = useState<Operator[]>([]);
const [scannedMachines, setScannedMachines] = useState<Machine[]>([]);
// 产出表单
const [outputData, setOutputData] = useState({
byproductName: "",
byproductQty: "",
byproductUom: "",
scrapQty: "",
scrapUom: "",
defectQty: "",
defectUom: "",
outputFromProcessQty: "",
outputFromProcessUom: "",
});

// 处理 QR 码扫描
// 处理 QR 码扫描
const processQrCode = useCallback((qrValue: string) => {
// 操作员格式:{2fitestu1} - 键盘模拟输入(测试用)
if (qrValue.match(/\{2fitestu(\d+)\}/)) {
const match = qrValue.match(/\{2fitestu(\d+)\}/);
const userId = parseInt(match![1]);
// 调用 API 获取用户信息
fetchNameList().then((users: NameList[]) => {
const user = users.find((u: NameList) => u.id === userId);
if (user) {
setScannedOperators([{
id: user.id,
name: user.name,
username: user.name
}]);
updateProductProcessLineQrscan({
lineId: selectedLineId || 0 as number,
operatorId: user.id,
});
}
});
return;
}
// 设备格式:{2fiteste1} - 键盘模拟输入(测试用)
if (qrValue.match(/\{2fiteste(\d+)\}/)) {
const match = qrValue.match(/\{2fiteste(\d+)\}/);
const equipmentId = parseInt(match![1]);
// 使用本地设备数据库
const machine = machineDatabase.find((m: Machine) => m.id === equipmentId);
if (machine) {
setScannedMachines([machine]);
}
updateProductProcessLineQrscan({
lineId: selectedLineId || 0 as number,
equipmentId: equipmentId,
}).then((res) => {
console.log(res);
});
return;
}
// 正常 QR 扫描器扫描:格式为 "operatorId: 1" 或 "equipmentId: 1"
const trimmedValue = qrValue.trim();
// 检查 operatorId 格式
const operatorMatch = trimmedValue.match(/^operatorId:\s*(\d+)$/i);
if (operatorMatch) {
const operatorId = parseInt(operatorMatch[1]);
fetchNameList().then((users: NameList[]) => {
const user = users.find((u: NameList) => u.id === operatorId);
if (user) {
setScannedOperators([{
id: user.id,
name: user.name,
username: user.name
}]);
updateProductProcessLineQrscan({
lineId: selectedLineId || 0 as number,
operatorId: user.id,
});
} else {
console.warn(`User with ID ${operatorId} not found`);
}
});
return;
}
// 检查 equipmentId 格式
const equipmentMatch = trimmedValue.match(/^equipmentId:\s*(\d+)$/i);
if (equipmentMatch) {
const equipmentId = parseInt(equipmentMatch[1]);
const machine = machineDatabase.find((m: Machine) => m.id === equipmentId);
if (machine) {
setScannedMachines([machine]);
}
updateProductProcessLineQrscan({
lineId: selectedLineId || 0 as number,
equipmentId: equipmentId,
}).then((res) => {
console.log(res);
});
return;
}
// 其他格式处理(JSON、普通文本等)
try {
const qrData = JSON.parse(qrValue);
// TODO: 处理 JSON 格式的 QR 码
} catch {
// 普通文本格式
// TODO: 处理普通文本格式
}
}, [selectedLineId]);

// 处理 QR 码扫描效果
useEffect(() => {
if (isManualScanning && qrValues.length > 0 && isExecutingLine) {
const latestQr = qrValues[qrValues.length - 1];
if (processedQrCodes.has(latestQr)) {
return;
}
setProcessedQrCodes(prev => new Set(prev).add(latestQr));
processQrCode(latestQr);
}
}, [qrValues, isManualScanning, isExecutingLine, processedQrCodes, processQrCode]);

// 开始扫描
const handleStartScan = useCallback(() => {
setIsManualScanning(true);
setProcessedQrCodes(new Set());
startScan();
}, [startScan]);

// 停止扫描
const handleStopScan = useCallback(() => {
setIsManualScanning(false);
stopScan();
resetScan();
}, [stopScan, resetScan]);

// 获取 process 和 lines 数据
const fetchProcessDetail = useCallback(async () => {
setLoading(true);
try {
console.log(`🔍 Loading process detail for ID: ${processId}`);
const data = await fetchProductProcessLineDetail(processId);
setProcessData(data);
const linesData = await fetchProductProcessLineDetail(processId);
setLines([linesData]);
console.log(" Process data loaded:", data);
console.log(" Lines loaded:", linesData);
} catch (error) {
console.error("❌ Error loading process detail:", error);
alert(`无法加载生产流程 ID ${processId}。该记录可能不存在。`);
onBack();
} finally {
setLoading(false);
}
}, [processId, onBack]);

useEffect(() => {
fetchProcessDetail();
}, [fetchProcessDetail]);

// 开始执行某个 line
const handleStartLine = async (lineId: number) => {
if (!currentUserId) {
alert("Please login first!");
return;
}
try {
// 使用 Server Action 而不是直接 fetch
await startProductProcessLine(lineId, currentUserId);
console.log(` Starting line ${lineId} with handlerId: ${currentUserId}`);
setSelectedLineId(lineId);
setIsExecutingLine(true);
setScannedOperators([]);
setScannedMachines([]);
setOutputData({
byproductName: "",
byproductQty: "",
byproductUom: "",
scrapQty: "",
scrapUom: "",
defectQty: "",
defectUom: "",
outputFromProcessQty: "",
outputFromProcessUom: "",
});
// 刷新数据
await fetchProcessDetail();
} catch (error) {
console.error("Error starting line:", error);
alert("Failed to start line. Please try again.");
}
};

// 提交产出数据
const handleSubmitOutput = async () => {
if (!selectedLineId) return;

if (scannedOperators.length === 0 || scannedMachines.length === 0) {
alert("Please scan operator and machine first!");
return;
}

try {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8090'}/product-process/lines/${selectedLineId}/output`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
outputQty: parseFloat(outputData.outputFromProcessQty) || 0,
outputUom: outputData.outputFromProcessUom,
defectQty: parseFloat(outputData.defectQty) || 0,
defectUom: outputData.defectUom,
scrapQty: parseFloat(outputData.scrapQty) || 0,
scrapUom: outputData.scrapUom,
byproductName: outputData.byproductName,
byproductQty: parseFloat(outputData.byproductQty) || 0,
byproductUom: outputData.byproductUom,
}),
});

if (response.ok) {
console.log(" Output data submitted successfully");
setIsExecutingLine(false);
setSelectedLineId(null);
handleStopScan(); // 停止扫描
fetchProcessDetail(); // 刷新数据
}
} catch (error) {
console.error("Error submitting output:", error);
}
};

const selectedLine = lines.find(l => l.id === selectedLineId);

if (loading) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress/>
</Box>
);
}

return (
<Box>
{/* 返回按钮 */}
<Box sx={{ mb: 2 }}>
<Button variant="outlined" onClick={onBack}>
{t("Back to List")}
</Button>
</Box>

{/* ========== 第一部分:基本信息 ========== */}
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom fontWeight="bold">
{t("Production Process Information")}
</Typography>
<Stack spacing={2} direction="row" useFlexGap flexWrap="wrap">
<Typography variant="subtitle1">
<strong>{t("Process Code")}:</strong> {processData?.productProcessCode}
</Typography>
<Typography variant="subtitle1">
<strong>{t("Status")}:</strong>{" "}
<Chip
label={t(processData?.status || 'pending')}
color={processData?.status === 'completed' ? 'success' : 'primary'}
size="small"
/>
</Typography>
<Typography variant="subtitle1">
<strong>{t("Date")}:</strong> {dayjs(processData?.date).format(OUTPUT_DATE_FORMAT)}
</Typography>
<Typography variant="subtitle1">
<strong>{t("Total Steps")}:</strong> {lines.length}
</Typography>
</Stack>
</Paper>

{/* ========== 第二部分:Process Lines ========== */}
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom fontWeight="bold">
{t("Production Process Steps")}
</Typography>

{!isExecutingLine ? (
/* ========== 步骤列表视图 ========== */
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>{t("Seq")}</TableCell>
<TableCell>{t("Step Name")}</TableCell>
<TableCell>{t("Description")}</TableCell>
<TableCell>{t("Equipment Type")}</TableCell>
<TableCell align="center">{t("Status")}</TableCell>
<TableCell align="center">{t("Action")}</TableCell>
</TableRow>
</TableHead>
<TableBody>
{lines.map((line) => (
<TableRow key={line.id}>
<TableCell>{line.seqNo}</TableCell>
<TableCell>
<Typography fontWeight={500}>{line.name}</Typography>
</TableCell>
<TableCell>{line.description}</TableCell>
<TableCell>{line.equipmentType}</TableCell>
<TableCell align="center">
{line.endTime ? (
<Chip label={t("Completed")} color="success" size="small" />
) : line.startTime ? (
<Chip label={t("In Progress")} color="primary" size="small" />
) : (
<Chip label={t("Pending")} color="default" size="small" />
)}
</TableCell>
<TableCell align="center">
<Button
variant="contained"
size="small"
startIcon={<PlayArrowIcon />}
onClick={() => handleStartLine(line.id)}
disabled={!!line.endTime}
>
{line.endTime ? t("Completed") : t("Start")}
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
) : (
/* ========== 步骤执行视图 ========== */
<Box>
{/* 当前步骤信息 */}
<Card sx={{ mb: 3, bgcolor: 'primary.50', border: '2px solid', borderColor: 'primary.main' }}>
<CardContent>
<Typography variant="h6" color="primary.main" gutterBottom>
{t("Executing")}: {selectedLine?.name} (Seq: {selectedLine?.seqNo})
</Typography>
<Typography variant="body2" color="text.secondary">
{selectedLine?.description}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Equipment")}: {selectedLine?.equipmentType}
</Typography>
</CardContent>
</Card>

<Stack spacing={3}>
{/* 合并的扫描器 */}
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
{t("Scan Operator & Equipment")}
</Typography>
<Stack spacing={2}>
{/* 操作员扫描 */}
<Box>
<Typography variant="body2" color="text.secondary">
{scannedOperators.length > 0
? `${t("Operator")}: ${scannedOperators[0].name || scannedOperators[0].username}`
: t("Please scan operator code")
}
</Typography>
</Box>
{/* 设备扫描 */}
<Box>
<Typography variant="body2" color="text.secondary">
{scannedMachines.length > 0
? `${t("Equipment")}: ${scannedMachines[0].name || scannedMachines[0].code}`
: t("Please scan equipment code")
}
</Typography>
</Box>
{/* 单个扫描按钮 */}
<Button
variant={isManualScanning ? "outlined" : "contained"}
startIcon={<QrCodeIcon />}
onClick={isManualScanning ? handleStopScan : handleStartScan}
color={isManualScanning ? "secondary" : "primary"}
>
{isManualScanning ? t("Stop QR Scan") : t("Start QR Scan")}
</Button>
</Stack>
</Paper>

{/* ========== 产出输入表单 ========== */}
{scannedOperators.length > 0 && scannedMachines.length > 0 && (
<Paper sx={{ p: 3, bgcolor: 'grey.50' }}>
<Typography variant="h6" gutterBottom fontWeight={600}>
{t("Production Output Data Entry")}
</Typography>

<Table size="small">
<TableHead>
<TableRow>
<TableCell width="30%">{t("Type")}</TableCell>
<TableCell width="35%">{t("Quantity")}</TableCell>
<TableCell width="35%">{t("Unit of Measure")}</TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* 步骤收成 */}
<TableRow>
<TableCell>
<Typography fontWeight={500}>{t("Output from Process")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.outputFromProcessQty}
onChange={(e) => setOutputData(prev => ({ ...prev, outputFromProcessQty: e.target.value }))}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.outputFromProcessUom}
onChange={(e) => setOutputData(prev => ({ ...prev, outputFromProcessUom: e.target.value }))}
placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>

{/* 副产品 */}
<TableRow>
<TableCell>
<Stack>
<Typography fontWeight={500}>{t("By-product")}</Typography>
<TextField
fullWidth
size="small"
value={outputData.byproductName}
onChange={(e) => setOutputData(prev => ({ ...prev, byproductName: e.target.value }))}
placeholder={t("By-product name")}
sx={{ mt: 1 }}
/>
</Stack>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.byproductQty}
onChange={(e) => setOutputData(prev => ({ ...prev, byproductQty: e.target.value }))}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.byproductUom}
onChange={(e) => setOutputData(prev => ({ ...prev, byproductUom: e.target.value }))}
placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>

{/* 次品 */}
<TableRow sx={{ bgcolor: 'warning.50' }}>
<TableCell>
<Typography fontWeight={500} color="warning.dark">{t("Defect")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.defectQty}
onChange={(e) => setOutputData(prev => ({ ...prev, defectQty: e.target.value }))}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.defectUom}
onChange={(e) => setOutputData(prev => ({ ...prev, defectUom: e.target.value }))}
placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>

{/* 废品 */}
<TableRow sx={{ bgcolor: 'error.50' }}>
<TableCell>
<Typography fontWeight={500} color="error.dark">{t("Scrap")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.scrapQty}
onChange={(e) => setOutputData(prev => ({ ...prev, scrapQty: e.target.value }))}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.scrapUom}
onChange={(e) => setOutputData(prev => ({ ...prev, scrapUom: e.target.value }))}
placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>
</TableBody>
</Table>

{/* 提交按钮 */}
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
<Button
variant="outlined"
onClick={() => {
setIsExecutingLine(false);
setSelectedLineId(null);
handleStopScan();
}}
>
{t("Cancel")}
</Button>
<Button
variant="contained"
startIcon={<CheckCircleIcon />}
onClick={handleSubmitOutput}
>
{t("Complete Step")}
</Button>
</Box>
</Paper>
)}
</Stack>
</Box>
)}
</Paper>
</Box>
);
};

export default ProductionProcessDetail;

+ 185
- 0
src/components/ProductionProcess/ProductionProcessList.tsx View File

@@ -0,0 +1,185 @@
"use client";
import React, { useCallback, useEffect, useState } from "react";
import {
Box,
Button,
Card,
CardContent,
CardActions,
Stack,
Typography,
Chip,
CircularProgress,
TablePagination,
Grid,
} from "@mui/material";
import { useTranslation } from "react-i18next";
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import dayjs from "dayjs";
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import {
fetchAllJoborderProductProcessInfo,
AllJoborderProductProcessInfoResponse,
} from "@/app/api/jo/actions";

interface ProductProcessListProps {
onSelectProcess: (processId: number) => void;
}

const PER_PAGE = 6;

const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess }) => {
const { t } = useTranslation();
const { data: session } = useSession() as { data: SessionWithTokens | null };

const [loading, setLoading] = useState(false);
const [processes, setProcesses] = useState<AllJoborderProductProcessInfoResponse[]>([]);
const [page, setPage] = useState(0);

const fetchProcesses = useCallback(async () => {
setLoading(true);
try {
const data = await fetchAllJoborderProductProcessInfo();
setProcesses(data || []);
setPage(0);
} catch (e) {
console.error(e);
setProcesses([]);
} finally {
setLoading(false);
}
}, []);

useEffect(() => {
fetchProcesses();
}, [fetchProcesses]);

const startIdx = page * PER_PAGE;
const paged = processes.slice(startIdx, startIdx + PER_PAGE);

return (
<Box>
{loading ? (
<Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
<CircularProgress />
</Box>
) : (
<Box>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{t("Total processes")}: {processes.length}
</Typography>

<Grid container spacing={2}>
{paged.map((process) => {
const status = String(process.status || "");
const statusLower = status.toLowerCase();
const statusColor =
statusLower === "completed"
? "success"
: statusLower === "in_progress" || statusLower === "processing"
? "primary"
: "default";

const finishedCount =
(process as any).finishedProductProcessLineCount ??
(process as any).FinishedProductProcessLineCount ??
0;

const totalCount = process.productProcessLineCount ?? process.lines?.length ?? 0;
const linesWithStatus = (process.lines || []).filter(
(l) => String(l.status ?? "").trim() !== ""
);

const dateDisplay = process.date
? dayjs(process.date as any).format(OUTPUT_DATE_FORMAT)
: "-";
const jobOrderCode =
(process as any).jobOrderCode ??
(process.jobOrderId ? `JO-${process.jobOrderId}` : "N/A");
const inProgressLines = (process.lines || [])
.filter(l => String(l.status ?? "").trim() !== "")
.filter(l => String(l.status).toLowerCase() === "in_progress");

return (
<Grid key={process.id} item xs={12} sm={6} md={4}>
<Card
sx={{
minHeight: 160,
maxHeight: 240,
display: "flex",
flexDirection: "column",
}}
>
<CardContent
sx={{
pb: 1,
flexGrow: 1, // let content take remaining height
overflow: "auto", // allow scroll when content exceeds
}}
>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Box sx={{ minWidth: 0 }}>
<Typography variant="subtitle1">
{process.productProcessCode}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Job Order")}: {jobOrderCode}
</Typography>

</Box>
<Chip size="small" label={t(status)} color={statusColor as any} />
</Stack>

{statusLower !== "pending" && linesWithStatus.length > 0 && (
<Box sx={{ mt: 1 }}>
<Typography variant="body2" fontWeight={600}>
{t("Finished lines")}: {finishedCount} / {totalCount}
</Typography>

{inProgressLines.length > 0 && (
<Box sx={{ mt: 1 }}>
{inProgressLines.map(line => (
<Typography key={line.id} variant="caption" color="text.secondary" display="block">
{t("Operator")}: {line.operatorName || "-"} <br />
{t("Equipment")}: {line.equipmentName || "-"}
</Typography>
))}
</Box>
)}
</Box>
)}
</CardContent>

<CardActions sx={{ pt: 0.5 }}>
<Button variant="contained" size="small" onClick={() => onSelectProcess(process.id)}>
{t("View Details")}
</Button>
<Box sx={{ flex: 1 }} />
<Typography variant="caption" color="text.secondary">
{t("Lines")}: {totalCount}
</Typography>
</CardActions>
</Card>
</Grid>
);
})}
</Grid>

{processes.length > 0 && (
<TablePagination
component="div"
count={processes.length}
page={page}
rowsPerPage={PER_PAGE}
onPageChange={(e, p) => setPage(p)}
rowsPerPageOptions={[PER_PAGE]}
/>
)}
</Box>
)}
</Box>
);
};

export default ProductProcessList;

+ 67
- 0
src/components/ProductionProcess/ProductionProcessPage.tsx View File

@@ -0,0 +1,67 @@
"use client";
import React, { useState, useEffect, useCallback } from "react";
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import ProductionProcessList from "@/components/ProductionProcess/ProductionProcessList";
import ProductionProcessDetail from "@/components/ProductionProcess/ProductionProcessDetail";
import {
fetchProductProcesses,
fetchProductProcessLines,
ProductProcessLineResponse
} from "@/app/api/jo/actions";

const ProductionProcessPage = () => {
const [selectedProcessId, setSelectedProcessId] = useState<number | null>(null);
const { data: session } = useSession() as { data: SessionWithTokens | null };
const currentUserId = session?.id ? parseInt(session.id) : undefined;

const checkAndRedirectToDetail = useCallback(async () => {
if (!currentUserId) return;
try {
// 获取所有 processes
const processes = await fetchProductProcesses();
// 获取所有 lines 并检查是否有匹配的
for (const process of processes.content || []) {
const lines = await fetchProductProcessLines(process.id);
const pendingLine = lines.find((line: ProductProcessLineResponse) =>
line.handlerId === currentUserId &&
!line.endTime &&
line.startTime
);
if (pendingLine) {
setSelectedProcessId(process.id);
break;
}
}
} catch (error) {
console.error("Error checking pending lines:", error);
}
}, [currentUserId]);

useEffect(() => {
if (currentUserId && !selectedProcessId) {
// 检查是否有当前用户的 pending line
checkAndRedirectToDetail();
}
}, [currentUserId, selectedProcessId, checkAndRedirectToDetail]);

if (selectedProcessId !== null) {
return (
<ProductionProcessDetail
processId={selectedProcessId}
onBack={() => setSelectedProcessId(null)}
/>
);
}

return (
<ProductionProcessList
onSelectProcess={(id) => setSelectedProcessId(id)}
/>
);
};

export default ProductionProcessPage;

Loading…
Cancel
Save