FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

1085 lines
35 KiB

  1. "use client";
  2. import {
  3. Autocomplete,
  4. Box,
  5. Button,
  6. CircularProgress,
  7. FormControl,
  8. Grid,
  9. Paper,
  10. Stack,
  11. Table,
  12. TableBody,
  13. TableCell,
  14. TableContainer,
  15. TableHead,
  16. TableRow,
  17. TextField,
  18. Typography,
  19. Checkbox,
  20. FormControlLabel,
  21. Select,
  22. MenuItem,
  23. InputLabel,
  24. TablePagination,
  25. } from "@mui/material";
  26. import { useCallback, useEffect, useMemo, useState } from "react";
  27. import { useTranslation } from "react-i18next";
  28. import {
  29. ByItemsSummary,
  30. ConsoPickOrderResult,
  31. PickOrderLine,
  32. PickOrderResult,
  33. } from "@/app/api/pickOrder";
  34. import { useRouter } from "next/navigation";
  35. import { GridInputRowSelectionModel } from "@mui/x-data-grid";
  36. import {
  37. fetchConsoDetail,
  38. fetchConsoPickOrderClient,
  39. releasePickOrder,
  40. ReleasePickOrderInputs,
  41. fetchPickOrderDetails,
  42. fetchAllPickOrderDetails,
  43. GetPickOrderInfoResponse,
  44. GetPickOrderLineInfo,
  45. createStockOutLine,
  46. updateStockOutLineStatus,
  47. resuggestPickOrder,
  48. checkAndCompletePickOrderByConsoCode,
  49. } from "@/app/api/pickOrder/actions";
  50. import { EditNote } from "@mui/icons-material";
  51. import { fetchNameList, NameList } from "@/app/api/user/actions";
  52. import {
  53. FormProvider,
  54. SubmitErrorHandler,
  55. SubmitHandler,
  56. useForm,
  57. } from "react-hook-form";
  58. import { OUTPUT_DATE_FORMAT, pickOrderStatusMap } from "@/app/utils/formatUtil";
  59. import { QcItemWithChecks } from "@/app/api/qc";
  60. import { fetchQcItemCheck, fetchPickOrderQcResult } from "@/app/api/qc/actions";
  61. import { PurchaseQcResult } from "@/app/api/po/actions";
  62. //import PickQcStockInModalVer2 from "./PickQcStockInModalVer3";
  63. import { fetchPickOrderLineLotDetails, PickOrderLotDetailResponse } from "@/app/api/pickOrder/actions";
  64. import SearchResults, { Column } from "../SearchResults/SearchResults";
  65. import { defaultPagingController } from "../SearchResults/SearchResults";
  66. import SearchBox, { Criterion } from "../SearchBox";
  67. import dayjs from "dayjs";
  68. import { CreateStockOutLine , NoLotLineDto} from "@/app/api/pickOrder/actions";
  69. import LotTable from './LotTable';
  70. import PickOrderDetailsTable from './PickOrderDetailsTable'; // Import the new component
  71. import { updateInventoryLotLineStatus, updateInventoryStatus, updateInventoryLotLineQuantities } from "@/app/api/inventory/actions";
  72. import { useSession } from "next-auth/react";
  73. import { SessionWithTokens } from "@/config/authConfig";
  74. interface Props {
  75. filterArgs: Record<string, any>;
  76. }
  77. interface LotPickData {
  78. id: number;
  79. lotId: number | null;
  80. lotNo: string | null;
  81. expiryDate: string;
  82. location: string | null;
  83. stockUnit: string;
  84. inQty: number;
  85. outQty: number;
  86. holdQty: number;
  87. totalPickedByAllPickOrders: number;
  88. availableQty: number;
  89. requiredQty: number;
  90. actualPickQty: number;
  91. lotStatus: string;
  92. noLot?: boolean;
  93. lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected';
  94. stockOutLineId?: number;
  95. stockOutLineStatus?: string;
  96. stockOutLineQty?: number;
  97. }
  98. interface PickQtyData {
  99. [lineId: number]: {
  100. [lotId: number]: number;
  101. };
  102. }
  103. const PickExecution: React.FC<Props> = ({ filterArgs }) => {
  104. const { t } = useTranslation("pickOrder");
  105. const router = useRouter();
  106. const { data: session } = useSession() as { data: SessionWithTokens | null };
  107. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  108. const [filteredPickOrders, setFilteredPickOrders] = useState(
  109. [] as ConsoPickOrderResult[],
  110. );
  111. const [isLoading, setIsLoading] = useState(false);
  112. const [selectedConsoCode, setSelectedConsoCode] = useState<string | undefined>();
  113. const [revertIds, setRevertIds] = useState<GridInputRowSelectionModel>([]);
  114. const [totalCount, setTotalCount] = useState<number>();
  115. const [usernameList, setUsernameList] = useState<NameList[]>([]);
  116. const [byPickOrderRows, setByPickOrderRows] = useState<
  117. Omit<PickOrderResult, "items">[] | undefined
  118. >(undefined);
  119. const [byItemsRows, setByItemsRows] = useState<ByItemsSummary[] | undefined>(
  120. undefined,
  121. );
  122. const [disableRelease, setDisableRelease] = useState<boolean>(true);
  123. const [selectedRowId, setSelectedRowId] = useState<number | null>(null);
  124. const [pickOrderDetails, setPickOrderDetails] = useState<GetPickOrderInfoResponse | null>(null);
  125. const [detailLoading, setDetailLoading] = useState(false);
  126. const [pickQtyData, setPickQtyData] = useState<PickQtyData>({});
  127. const [lotData, setLotData] = useState<LotPickData[]>([]);
  128. const [qcItems, setQcItems] = useState<QcItemWithChecks[]>([]);
  129. const [qcModalOpen, setQcModalOpen] = useState(false);
  130. const [selectedItemForQc, setSelectedItemForQc] = useState<GetPickOrderLineInfo & {
  131. pickOrderCode: string;
  132. qcResult?: PurchaseQcResult[];
  133. } | null>(null);
  134. const [selectedLotForQc, setSelectedLotForQc] = useState<LotPickData | null>(null);
  135. const [selectedLotRowId, setSelectedLotRowId] = useState<string | null>(null);
  136. const [selectedLotId, setSelectedLotId] = useState<number | null>(null);
  137. // Keep only the main table paging controller
  138. const [mainTablePagingController, setMainTablePagingController] = useState({
  139. pageNum: 0,
  140. pageSize: 10,
  141. });
  142. const [lotTablePagingController, setLotTablePagingController] = useState({
  143. pageNum: 0,
  144. pageSize: 10,
  145. });
  146. const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
  147. const [originalPickOrderData, setOriginalPickOrderData] = useState<GetPickOrderInfoResponse | null>(null);
  148. const formProps = useForm<ReleasePickOrderInputs>();
  149. const errors = formProps.formState.errors;
  150. const onDetailClick = useCallback(
  151. (pickOrder: any) => {
  152. console.log(pickOrder);
  153. const status = pickOrder.status;
  154. if (pickOrderStatusMap[status] >= 3) {
  155. router.push(`/pickOrder/detail?consoCode=${pickOrder.consoCode}`);
  156. } else {
  157. setSelectedConsoCode(pickOrder.consoCode);
  158. }
  159. },
  160. [router],
  161. );
  162. const fetchNewPageConsoPickOrder = useCallback(
  163. async (
  164. pagingController: Record<string, number>,
  165. filterArgs: Record<string, number>,
  166. ) => {
  167. setIsLoading(true);
  168. const params = {
  169. ...pagingController,
  170. ...filterArgs,
  171. };
  172. const res = await fetchConsoPickOrderClient(params);
  173. if (res) {
  174. console.log(res);
  175. setFilteredPickOrders(res.records);
  176. setTotalCount(res.total);
  177. }
  178. setIsLoading(false);
  179. },
  180. [],
  181. );
  182. useEffect(() => {
  183. fetchNewPageConsoPickOrder({ limit: 10, offset: 0 }, filterArgs);
  184. }, [fetchNewPageConsoPickOrder, filterArgs]);
  185. const isReleasable = useCallback((itemList: ByItemsSummary[]): boolean => {
  186. let isReleasable = true;
  187. for (const item of itemList) {
  188. isReleasable = item.requiredQty >= item.availableQty;
  189. if (!isReleasable) return isReleasable;
  190. }
  191. return isReleasable;
  192. }, []);
  193. const fetchConso = useCallback(
  194. async (consoCode: string) => {
  195. const res = await fetchConsoDetail(consoCode);
  196. const nameListRes = await fetchNameList();
  197. if (res) {
  198. console.log(res);
  199. setByPickOrderRows(res.pickOrders);
  200. setByItemsRows(res.items);
  201. setDisableRelease(isReleasable(res.items));
  202. } else {
  203. console.log("error");
  204. console.log(res);
  205. }
  206. if (nameListRes) {
  207. console.log(nameListRes);
  208. setUsernameList(nameListRes);
  209. }
  210. },
  211. [isReleasable],
  212. );
  213. const handleFetchAllPickOrderDetails = useCallback(async () => {
  214. setDetailLoading(true);
  215. try {
  216. const data = await fetchAllPickOrderDetails(currentUserId);
  217. console.log("All Pick Order Details for user:", currentUserId, data);
  218. if (data && data.pickOrders) {
  219. setPickOrderDetails(data);
  220. setOriginalPickOrderData(data);
  221. const initialPickQtyData: PickQtyData = {};
  222. data.pickOrders.forEach((pickOrder: any) => {
  223. pickOrder.pickOrderLines.forEach((line: any) => {
  224. initialPickQtyData[line.id] = {};
  225. });
  226. });
  227. setPickQtyData(initialPickQtyData);
  228. } else {
  229. console.log("No pick order data returned");
  230. setPickOrderDetails({
  231. consoCode: null,
  232. pickOrders: [],
  233. items: []
  234. });
  235. setOriginalPickOrderData({
  236. consoCode: null,
  237. pickOrders: [],
  238. items: []
  239. });
  240. setPickQtyData({});
  241. }
  242. } catch (error) {
  243. console.error("Error fetching all pick order details:", error);
  244. setPickOrderDetails({
  245. consoCode: null,
  246. pickOrders: [],
  247. items: []
  248. });
  249. setOriginalPickOrderData({
  250. consoCode: null,
  251. pickOrders: [],
  252. items: []
  253. });
  254. setPickQtyData({});
  255. } finally {
  256. setDetailLoading(false);
  257. }
  258. }, [currentUserId]);
  259. useEffect(() => {
  260. handleFetchAllPickOrderDetails();
  261. }, [handleFetchAllPickOrderDetails]);
  262. const onChange = useCallback(
  263. (event: React.SyntheticEvent, newValue: NameList) => {
  264. console.log(newValue);
  265. formProps.setValue("assignTo", newValue.id);
  266. },
  267. [formProps],
  268. );
  269. const onSubmit = useCallback<SubmitHandler<ReleasePickOrderInputs>>(
  270. async (data, event) => {
  271. console.log(data);
  272. try {
  273. const res = await releasePickOrder(data);
  274. console.log(res);
  275. if (res.consoCode.length > 0) {
  276. console.log(res);
  277. router.push(`/pickOrder/detail?consoCode=${res.consoCode}`);
  278. } else {
  279. console.log(res);
  280. }
  281. } catch (error) {
  282. console.log(error);
  283. }
  284. },
  285. [router],
  286. );
  287. const onSubmitError = useCallback<SubmitErrorHandler<ReleasePickOrderInputs>>(
  288. (errors) => {},
  289. [],
  290. );
  291. const handleConsolidate_revert = useCallback(() => {
  292. console.log(revertIds);
  293. }, [revertIds]);
  294. useEffect(() => {
  295. if (selectedConsoCode) {
  296. fetchConso(selectedConsoCode);
  297. formProps.setValue("consoCode", selectedConsoCode);
  298. }
  299. }, [selectedConsoCode, fetchConso, formProps]);
  300. const handlePickQtyChange = useCallback((lineId: number, lotId: number, value: number | string) => {
  301. console.log("Changing pick qty:", { lineId, lotId, value });
  302. const numericValue = typeof value === 'string' ? (value === '' ? 0 : parseInt(value, 10)) : value;
  303. setPickQtyData(prev => {
  304. const newData = {
  305. ...prev,
  306. [lineId]: {
  307. ...prev[lineId],
  308. [lotId]: numericValue
  309. }
  310. };
  311. console.log("New pick qty data:", newData);
  312. return newData;
  313. });
  314. }, []);
  315. const handleSubmitPickQty = useCallback(async (lineId: number, lotId: number) => {
  316. const qty = pickQtyData[lineId]?.[lotId] || 0;
  317. console.log(`提交拣货数量: Line ${lineId}, Lot ${lotId}, Qty ${qty}`);
  318. const selectedLot = lotData.find(lot => lot.lotId === lotId);
  319. if (!selectedLot?.stockOutLineId) {
  320. return;
  321. }
  322. try {
  323. // FIXED: 计算累计拣货数量
  324. const totalPickedForThisLot = (selectedLot.actualPickQty || 0) + qty;
  325. console.log(" DEBUG - Previous picked:", selectedLot.actualPickQty || 0);
  326. console.log("🔍 DEBUG - Current submit:", qty);
  327. console.log("🔍 DEBUG - Total picked:", totalPickedForThisLot);
  328. console.log("�� DEBUG - Required qty:", selectedLot.requiredQty);
  329. // FIXED: 状态应该基于累计拣货数量
  330. let newStatus = 'partially_completed';
  331. if (totalPickedForThisLot >= selectedLot.requiredQty) {
  332. newStatus = 'completed';
  333. }
  334. console.log("�� DEBUG - Calculated status:", newStatus);
  335. try {
  336. const stockOutLineUpdate = await updateStockOutLineStatus({
  337. id: selectedLot.stockOutLineId,
  338. status: newStatus,
  339. qty: qty
  340. });
  341. console.log(" Stock out line updated:", stockOutLineUpdate);
  342. } catch (error) {
  343. console.error("❌ Error updating stock out line:", error);
  344. return;
  345. }
  346. if (qty > 0) {
  347. const inventoryLotLineUpdate = await updateInventoryLotLineQuantities({
  348. inventoryLotLineId: lotId,
  349. qty: qty,
  350. status: 'available',
  351. operation: 'pick'
  352. });
  353. console.log("Inventory lot line updated:", inventoryLotLineUpdate);
  354. }
  355. // RE-ENABLE: Check if pick order should be completed
  356. if (newStatus === 'completed') {
  357. console.log(" Stock out line completed, checking if entire pick order is complete...");
  358. // 添加调试日志来查看所有 pick orders 的 consoCode
  359. console.log("📋 DEBUG - All pick orders and their consoCodes:");
  360. if (pickOrderDetails) {
  361. pickOrderDetails.pickOrders.forEach((pickOrder, index) => {
  362. console.log(` Pick Order ${index + 1}: ID=${pickOrder.id}, Code=${pickOrder.code}, ConsoCode=${pickOrder.consoCode}`);
  363. });
  364. }
  365. // FIXED: 直接查找 consoCode,不依赖 selectedRow
  366. if (pickOrderDetails) {
  367. let currentConsoCode: string | null = null;
  368. // 找到当前选中行所属的 pick order
  369. for (const pickOrder of pickOrderDetails.pickOrders) {
  370. const foundLine = pickOrder.pickOrderLines.find(line => line.id === selectedRowId);
  371. if (foundLine) {
  372. // 直接使用 pickOrder.code 作为 consoCode
  373. currentConsoCode = pickOrder.consoCode;
  374. console.log(`�� DEBUG - Found consoCode for line ${selectedRowId}: ${currentConsoCode} (from pick order ${pickOrder.id})`);
  375. break;
  376. }
  377. }
  378. if (currentConsoCode) {
  379. try {
  380. console.log(`🔍 Checking completion for consoCode: ${currentConsoCode}`);
  381. const completionResponse = await checkAndCompletePickOrderByConsoCode(currentConsoCode);
  382. console.log("�� Completion response:", completionResponse);
  383. if (completionResponse.message === "completed") {
  384. console.log("🎉 Pick order completed successfully!");
  385. await handleFetchAllPickOrderDetails();
  386. // 刷新当前选中的行数据
  387. if (selectedRowId) {
  388. await handleRowSelect(selectedRowId, true);
  389. }
  390. } else if (completionResponse.message === "not completed") {
  391. console.log("⏳ Pick order not completed yet, more lines remaining");
  392. } else {
  393. console.error("❌ Error checking completion:", completionResponse.message);
  394. }
  395. } catch (error) {
  396. console.error("❌ Error checking pick order completion:", error);
  397. }
  398. } else {
  399. console.warn("⚠️ No consoCode found for current pick order, cannot check completion");
  400. }
  401. }
  402. }
  403. console.log("All updates completed successfully");
  404. if (selectedRowId) {
  405. await handleRowSelect(selectedRowId, true);
  406. console.log("Data refresh needed but handleRowSelect not available yet");
  407. }
  408. await handleFetchAllPickOrderDetails();
  409. } catch (error) {
  410. console.error("Error updating pick quantity:", error);
  411. }
  412. }, [pickQtyData, lotData, selectedRowId, pickOrderDetails, handleFetchAllPickOrderDetails]);
  413. const getTotalPickedQty = useCallback((lineId: number) => {
  414. const lineData = pickQtyData[lineId];
  415. if (!lineData) return 0;
  416. return Object.values(lineData).reduce((sum, qty) => sum + qty, 0);
  417. }, [pickQtyData]);
  418. const handleQcCheck = useCallback(async (line: GetPickOrderLineInfo, pickOrderCode: string) => {
  419. if (!selectedLotId) {
  420. return;
  421. }
  422. const selectedLot = lotData.find(lot => lot.lotId === selectedLotId);
  423. if (!selectedLot) {
  424. return;
  425. }
  426. if (!selectedLot.stockOutLineId) {
  427. return;
  428. }
  429. setSelectedLotForQc(selectedLot);
  430. let qcResult: any[] = [];
  431. try {
  432. const rawQcResult = await fetchPickOrderQcResult(line.id);
  433. qcResult = rawQcResult.map((result: any) => ({
  434. ...result,
  435. isPassed: result.isPassed || false
  436. }));
  437. } catch (error) {
  438. // No existing QC result found - this is normal
  439. }
  440. setSelectedItemForQc({
  441. ...line,
  442. pickOrderCode,
  443. qcResult
  444. });
  445. setQcModalOpen(true);
  446. }, [lotData, selectedLotId, setQcItems]);
  447. const handleCloseQcModal = useCallback(() => {
  448. console.log("Closing QC modal");
  449. setQcModalOpen(false);
  450. setSelectedItemForQc(null);
  451. }, []);
  452. const handleSetItemDetail = useCallback((item: any) => {
  453. setSelectedItemForQc(item);
  454. }, []);
  455. // Main table pagination handlers
  456. const handleMainTablePageChange = useCallback((event: unknown, newPage: number) => {
  457. setMainTablePagingController(prev => ({
  458. ...prev,
  459. pageNum: newPage,
  460. }));
  461. }, []);
  462. const handleMainTablePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  463. const newPageSize = parseInt(event.target.value, 10);
  464. setMainTablePagingController({
  465. pageNum: 0,
  466. pageSize: newPageSize,
  467. });
  468. }, []);
  469. const handleLotTablePageChange = useCallback((event: unknown, newPage: number) => {
  470. setLotTablePagingController(prev => ({
  471. ...prev,
  472. pageNum: newPage,
  473. }));
  474. }, []);
  475. const handleLotTablePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  476. const newPageSize = parseInt(event.target.value, 10);
  477. setLotTablePagingController({
  478. pageNum: 0,
  479. pageSize: newPageSize,
  480. });
  481. }, []);
  482. const handleLotSelection = useCallback((uniqueLotId: string, lotId: number | null) => {
  483. console.log("=== DEBUG: Lot Selection ===");
  484. console.log("uniqueLotId:", uniqueLotId);
  485. console.log("lotId (inventory lot line ID):", lotId);
  486. const selectedLot = lotData.find(lot => lot.lotId === lotId);
  487. console.log("Selected lot data:", selectedLot);
  488. if (selectedLotRowId === uniqueLotId) {
  489. setSelectedLotRowId(null);
  490. setSelectedLotId(null);
  491. } else {
  492. setSelectedLotRowId(uniqueLotId);
  493. setSelectedLotId(lotId ?? null);
  494. }
  495. }, [selectedLotRowId, lotData]);
  496. const handleRowSelect = useCallback(async (lineId: number, preserveLotSelection: boolean = false) => {
  497. setSelectedRowId(lineId);
  498. if (!preserveLotSelection) {
  499. setSelectedLotRowId(null);
  500. setSelectedLotId(null);
  501. }
  502. try {
  503. const lotDetails = await fetchPickOrderLineLotDetails(lineId);
  504. console.log("lineId:", lineId);
  505. console.log("Lot details from API:", lotDetails);
  506. // ✅ 直接使用 API 返回的数据,包括 noLot 字段
  507. const allLotData: LotPickData[] = lotDetails.map((lot: PickOrderLotDetailResponse) => ({
  508. id: lot.lotId ?? -(lineId * 1000 + Math.random()), // 如果 lotId 为 null,生成一个临时 ID
  509. lotId: lot.lotId,
  510. lotNo: lot.lotNo,
  511. expiryDate: lot.expiryDate ? dayjs(lot.expiryDate).format(OUTPUT_DATE_FORMAT) : t("N/A"),
  512. location: lot.location?? t("N/A"),
  513. stockUnit: lot.stockUnit || "",
  514. inQty: lot.inQty ?? 0,
  515. outQty: lot.outQty ?? 0,
  516. holdQty: lot.holdQty ?? 0,
  517. totalPickedByAllPickOrders: lot.totalPickedByAllPickOrders ?? 0,
  518. availableQty: lot.availableQty ?? 0,
  519. requiredQty: lot.requiredQty ?? 0,
  520. actualPickQty: lot.actualPickQty || 0,
  521. lotStatus: lot.lotStatus || "unavailable",
  522. lotAvailability: lot.lotAvailability,
  523. stockOutLineId: lot.stockOutLineId ?? undefined,
  524. stockOutLineStatus: lot.stockOutLineStatus ?? undefined,
  525. stockOutLineQty: lot.stockOutLineQty ?? undefined,
  526. noLot: lot.noLot, // ✅ 关键:使用 API 返回的 noLot 字段
  527. }));
  528. console.log("✅ Combined lot data:", allLotData);
  529. console.log(" - Total rows:", allLotData.length);
  530. console.log(" - No-lot rows:", allLotData.filter(l => l.noLot).length);
  531. setLotData(allLotData);
  532. } catch (error) {
  533. console.error("Error fetching lot details:", error);
  534. setLotData([]);
  535. }
  536. }, []);
  537. const prepareLotTableData = useMemo(() => {
  538. return lotData.map((lot) => ({
  539. ...lot,
  540. id: lot.lotId,
  541. }));
  542. }, [lotData]);
  543. const paginatedLotTableData = useMemo(() => {
  544. const startIndex = lotTablePagingController.pageNum * lotTablePagingController.pageSize;
  545. const endIndex = startIndex + lotTablePagingController.pageSize;
  546. return prepareLotTableData.slice(startIndex, endIndex);
  547. }, [prepareLotTableData, lotTablePagingController]);
  548. const selectedRow = useMemo(() => {
  549. if (!selectedRowId || !pickOrderDetails) return null;
  550. for (const pickOrder of pickOrderDetails.pickOrders) {
  551. const foundLine = pickOrder.pickOrderLines.find(line => line.id === selectedRowId);
  552. if (foundLine) {
  553. return { ...foundLine, pickOrderCode: pickOrder.code,
  554. pickOrderId: pickOrder.id };
  555. }
  556. }
  557. return null;
  558. }, [selectedRowId, pickOrderDetails]);
  559. const handleInventoryUpdate = useCallback(async (itemId: number, lotId: number, qty: number) => {
  560. try {
  561. const inventoryUpdate = await updateInventoryStatus({
  562. itemId: itemId,
  563. lotId: lotId,
  564. status: 'reserved',
  565. qty: qty
  566. });
  567. console.log("Inventory status updated:", inventoryUpdate);
  568. } catch (error) {
  569. console.error("Error updating inventory status:", error);
  570. }
  571. }, []);
  572. const handleLotDataRefresh = useCallback(async () => {
  573. if (selectedRowId) {
  574. try {
  575. await handleRowSelect(selectedRowId, true);
  576. } catch (error) {
  577. console.error("Error refreshing lot data:", error);
  578. }
  579. }
  580. }, [selectedRowId, handleRowSelect]);
  581. const handleDataRefresh = useCallback(async () => {
  582. if (selectedRowId) {
  583. try {
  584. await handleRowSelect(selectedRowId, true);
  585. } catch (error) {
  586. console.error("Error refreshing data:", error);
  587. }
  588. }
  589. }, [selectedRowId, handleRowSelect]);
  590. const handleInsufficientStock = useCallback(async () => {
  591. console.log("Insufficient stock - testing resuggest API");
  592. if (!selectedRowId || !pickOrderDetails) {
  593. return;
  594. }
  595. let pickOrderId: number | null = null;
  596. for (const pickOrder of pickOrderDetails.pickOrders) {
  597. const foundLine = pickOrder.pickOrderLines.find(line => line.id === selectedRowId);
  598. if (foundLine) {
  599. pickOrderId = pickOrder.id;
  600. break;
  601. }
  602. }
  603. if (!pickOrderId) {
  604. return;
  605. }
  606. try {
  607. console.log(`Calling resuggest API for pick order ID: ${pickOrderId}`);
  608. const result = await resuggestPickOrder(pickOrderId);
  609. console.log("Resuggest API result:", result);
  610. if (result.code === "SUCCESS") {
  611. if (selectedRowId) {
  612. await handleRowSelect(selectedRowId);
  613. }
  614. await handleFetchAllPickOrderDetails();
  615. }
  616. } catch (error) {
  617. console.error("Error calling resuggest API:", error);
  618. }
  619. }, [selectedRowId, pickOrderDetails, handleRowSelect, handleFetchAllPickOrderDetails]);
  620. const hasSelectedLots = useCallback((lineId: number) => {
  621. return selectedLotRowId !== null;
  622. }, [selectedLotRowId]);
  623. const [showInputBody, setShowInputBody] = useState(false);
  624. const [selectedLotForInput, setSelectedLotForInput] = useState<LotPickData | null>(null);
  625. const handleLotSelectForInput = useCallback((lot: LotPickData) => {
  626. setSelectedLotForInput(lot);
  627. setShowInputBody(true);
  628. }, []);
  629. const generateInputBody = useCallback(() => {
  630. if (!selectedLotForInput || !selectedRowId || !pickOrderDetails?.consoCode) {
  631. return null;
  632. }
  633. // ✅ 處理 lotId 可能為 null 的情況
  634. if (selectedLotForInput.lotId === null) {
  635. return null; // no-lot 行不能創建 stock out line
  636. }
  637. return {
  638. consoCode: pickOrderDetails.consoCode,
  639. pickOrderLineId: selectedRowId,
  640. inventoryLotLineId: selectedLotForInput.lotId, // ✅ 現在確定不是 null
  641. qty: 0.0
  642. };
  643. }, [selectedLotForInput, selectedRowId, selectedRow, pickOrderDetails?.consoCode]);
  644. // 在 handleSubmitPickQty 函数附近添加新函数
  645. const handleIssueNoLotStockOutLine = useCallback(async (stockOutLineId: number) => {
  646. if (!stockOutLineId) {
  647. console.error("Cannot issue: stockOutLineId is missing");
  648. return;
  649. }
  650. try {
  651. console.log(`提交 no-lot stock out line: ${stockOutLineId}`);
  652. // ✅ 直接完成 no-lot 的 stock out line(设置状态为 completed,qty 为 0)
  653. const result = await updateStockOutLineStatus({
  654. id: stockOutLineId,
  655. status: 'completed',
  656. qty: 0 // no-lot 行没有实际数量
  657. });
  658. console.log("✅ No-lot stock out line completed:", result);
  659. // 刷新数据
  660. if (selectedRowId) {
  661. handleRowSelect(selectedRowId);
  662. }
  663. } catch (error) {
  664. console.error("❌ Error completing no-lot stock out line:", error);
  665. }
  666. }, [selectedRowId, handleRowSelect]);
  667. const handleCreateStockOutLine = useCallback(async (inventoryLotLineId: number) => {
  668. if (!selectedRowId || !pickOrderDetails?.consoCode) {
  669. console.error("Missing required data for creating stock out line.");
  670. return;
  671. }
  672. try {
  673. const currentSelectedLotRowId = selectedLotRowId;
  674. const currentSelectedLotId = selectedLotId;
  675. let correctConsoCode: string | null = null;
  676. if (pickOrderDetails && selectedRowId) {
  677. for (const pickOrder of pickOrderDetails.pickOrders) {
  678. const foundLine = pickOrder.pickOrderLines.find(line => line.id === selectedRowId);
  679. if (foundLine) {
  680. correctConsoCode = pickOrder.consoCode;
  681. console.log(`🔍 Found consoCode for line ${selectedRowId}: ${correctConsoCode} (from pick order ${pickOrder.id})`);
  682. break;
  683. }
  684. }
  685. }
  686. const stockOutLineData: CreateStockOutLine = {
  687. consoCode: correctConsoCode || pickOrderDetails?.consoCode || "", // 使用正确的 consoCode
  688. pickOrderLineId: selectedRowId,
  689. inventoryLotLineId: inventoryLotLineId,
  690. qty: 0.0
  691. };
  692. console.log("=== STOCK OUT LINE CREATION DEBUG ===");
  693. console.log("Input Body:", JSON.stringify(stockOutLineData, null, 2));
  694. const result = await createStockOutLine(stockOutLineData);
  695. console.log("Stock Out Line created:", result);
  696. if (result) {
  697. console.log("Stock out line created successfully:", result);
  698. console.log("🔄 Refreshing data after stock out line creation...");
  699. try {
  700. if (selectedRowId) {
  701. await handleRowSelect(selectedRowId, true);
  702. }
  703. await handleFetchAllPickOrderDetails();
  704. console.log(" Data refresh completed - lot selection maintained!");
  705. } catch (refreshError) {
  706. console.error("❌ Error refreshing data:", refreshError);
  707. }
  708. setShowInputBody(false);
  709. } else {
  710. console.error("Failed to create stock out line: No response");
  711. }
  712. } catch (error) {
  713. console.error("Error creating stock out line:", error);
  714. }
  715. }, [selectedRowId, pickOrderDetails?.consoCode, handleRowSelect, handleFetchAllPickOrderDetails, selectedLotRowId, selectedLotId]);
  716. const handleRefreshDataPreserveSelection = useCallback(async () => {
  717. if (!selectedRowId) return;
  718. const currentSelectedLotRowId = selectedLotRowId;
  719. const currentSelectedLotId = selectedLotId;
  720. try {
  721. await handleRowSelect(selectedRowId, true);
  722. await handleFetchAllPickOrderDetails();
  723. setSelectedLotRowId(currentSelectedLotRowId);
  724. setSelectedLotId(currentSelectedLotId);
  725. console.log(" Data refreshed with selection preserved");
  726. } catch (error) {
  727. console.error("❌ Error refreshing data:", error);
  728. }
  729. }, [selectedRowId, selectedLotRowId, selectedLotId, handleRowSelect, handleFetchAllPickOrderDetails]);
  730. // Search criteria
  731. const searchCriteria: Criterion<any>[] = useMemo(
  732. () => [
  733. {
  734. label: t("Item Code"),
  735. paramName: "itemCode",
  736. type: "text",
  737. },
  738. {
  739. label: t("Pick Order Code"),
  740. paramName: "pickOrderCode",
  741. type: "text",
  742. },
  743. {
  744. label: t("Item Name"),
  745. paramName: "itemName",
  746. type: "text",
  747. },
  748. {
  749. label: t("Target Date From"),
  750. label2: t("Target Date To"),
  751. paramName: "targetDate",
  752. type: "dateRange",
  753. },
  754. ],
  755. [t],
  756. );
  757. // Search handler
  758. const handleSearch = useCallback((query: Record<string, any>) => {
  759. setSearchQuery({ ...query });
  760. console.log("Search query:", query);
  761. if (!originalPickOrderData) return;
  762. const filtered = originalPickOrderData.pickOrders.filter((pickOrder) => {
  763. return pickOrder.pickOrderLines.some((line) => {
  764. const itemCodeMatch = !query.itemCode ||
  765. line.itemCode?.toLowerCase().includes((query.itemCode || "").toLowerCase());
  766. const itemNameMatch = !query.itemName ||
  767. line.itemName?.toLowerCase().includes((query.itemName || "").toLowerCase());
  768. const pickOrderCodeMatch = !query.pickOrderCode ||
  769. pickOrder.code?.toLowerCase().includes((query.pickOrderCode || "").toLowerCase());
  770. return itemCodeMatch && itemNameMatch && pickOrderCodeMatch;
  771. });
  772. });
  773. const filteredData: GetPickOrderInfoResponse = {
  774. ...originalPickOrderData,
  775. pickOrders: filtered
  776. };
  777. setPickOrderDetails(filteredData);
  778. console.log("Filtered pick orders count:", filtered.length);
  779. }, [originalPickOrderData, t]);
  780. // Reset handler
  781. const handleReset = useCallback(() => {
  782. setSearchQuery({});
  783. if (originalPickOrderData) {
  784. setPickOrderDetails(originalPickOrderData);
  785. }
  786. }, [originalPickOrderData]);
  787. // Debug the lot data
  788. useEffect(() => {
  789. console.log("Lot data:", lotData);
  790. console.log("Pick Qty Data:", pickQtyData);
  791. }, [lotData, pickQtyData]);
  792. return (
  793. <FormProvider {...formProps}>
  794. <Stack spacing={2}>
  795. {/* Search Box */}
  796. <Box>
  797. <SearchBox
  798. criteria={searchCriteria}
  799. onSearch={handleSearch}
  800. onReset={handleReset}
  801. />
  802. </Box>
  803. {/* Main table using the new component */}
  804. <Box>
  805. <Typography variant="h6" gutterBottom>
  806. {t("Pick Order Details")}
  807. </Typography>
  808. <PickOrderDetailsTable
  809. pickOrderDetails={pickOrderDetails}
  810. detailLoading={detailLoading}
  811. selectedRowId={selectedRowId}
  812. onRowSelect={handleRowSelect}
  813. onPageChange={handleMainTablePageChange}
  814. onPageSizeChange={handleMainTablePageSizeChange}
  815. pageNum={mainTablePagingController.pageNum}
  816. pageSize={mainTablePagingController.pageSize}
  817. />
  818. </Box>
  819. {/* Lot table - below main table */}
  820. {selectedRow && (
  821. <Box>
  822. <Typography variant="h6" gutterBottom>
  823. {t("Item lot to be Pick:")} {selectedRow.pickOrderCode} - {selectedRow.itemName}
  824. </Typography>
  825. {lotData.length > 0 ? (
  826. <LotTable
  827. lotData={lotData}
  828. selectedRowId={selectedRowId}
  829. selectedRow={selectedRow}
  830. pickQtyData={pickQtyData}
  831. selectedLotRowId={selectedLotRowId}
  832. selectedLotId={selectedLotId}
  833. onLotSelection={handleLotSelection}
  834. onPickQtyChange={handlePickQtyChange}
  835. onSubmitPickQty={handleSubmitPickQty}
  836. onCreateStockOutLine={handleCreateStockOutLine}
  837. onQcCheck={handleQcCheck}
  838. onDataRefresh={handleFetchAllPickOrderDetails}
  839. onLotDataRefresh={handleLotDataRefresh}
  840. onLotSelectForInput={handleLotSelectForInput}
  841. showInputBody={showInputBody}
  842. onIssueNoLotStockOutLine={handleIssueNoLotStockOutLine}
  843. setShowInputBody={setShowInputBody}
  844. //selectedLotForInput={selectedLotForInput}
  845. generateInputBody={generateInputBody}
  846. // Add missing props
  847. totalPickedByAllPickOrders={0} // You can calculate this from lotData if needed
  848. outQty={0} // You can calculate this from lotData if needed
  849. holdQty={0} // You can calculate this from lotData if needed
  850. />
  851. ) : (
  852. <Box
  853. sx={{
  854. p: 3,
  855. textAlign: 'center',
  856. border: '1px solid',
  857. borderColor: 'divider',
  858. borderRadius: 1,
  859. backgroundColor: 'background.paper'
  860. }}
  861. >
  862. <Typography variant="body1" color="text.secondary" gutterBottom>
  863. {selectedRow.availableQty === null || selectedRow.availableQty === 0
  864. ? t("No available stock for this item")
  865. : t("No lot details available for this item")
  866. }
  867. </Typography>
  868. <Typography variant="body2" color="text.secondary">
  869. {selectedRow.availableQty === null || selectedRow.availableQty === 0
  870. ? t("Current stock is insufficient or unavailable")
  871. : t("Please check inventory status")
  872. }
  873. </Typography>
  874. </Box>
  875. )}
  876. {/* Action buttons below the lot table */}
  877. {/*
  878. <Box sx={{ mt: 2 }}>
  879. <Stack direction="row" spacing={1}>
  880. <Button
  881. variant="contained"
  882. onClick={() => handleInsufficientStock()}
  883. sx={{ whiteSpace: 'nowrap' }}
  884. >
  885. {t("Pick Another Lot")}
  886. </Button>
  887. </Stack>
  888. </Box>
  889. */}
  890. </Box>
  891. )}
  892. {/* Action Buttons */}
  893. {selectedRow && (
  894. <Grid container>
  895. <Grid item xs={12} display="flex" justifyContent="end" alignItems="end">
  896. <Button
  897. disabled={(revertIds as number[]).length < 1}
  898. variant="outlined"
  899. onClick={handleConsolidate_revert}
  900. sx={{ mr: 1 }}
  901. >
  902. {t("remove")}
  903. </Button>
  904. <Button
  905. disabled={disableRelease}
  906. variant="contained"
  907. onClick={formProps.handleSubmit(onSubmit, onSubmitError)}
  908. >
  909. {t("release")}
  910. </Button>
  911. </Grid>
  912. </Grid>
  913. )}
  914. {/* QC Modal */}
  915. { /*
  916. {selectedItemForQc && qcModalOpen && (
  917. <PickQcStockInModalVer2
  918. open={qcModalOpen}
  919. onClose={handleCloseQcModal}
  920. itemDetail={selectedItemForQc}
  921. setItemDetail={handleSetItemDetail}
  922. qc={qcItems}
  923. warehouse={[]}
  924. qcItems={qcItems}
  925. setQcItems={setQcItems}
  926. selectedLotId={selectedLotForQc?.stockOutLineId}
  927. onStockOutLineUpdate={() => {
  928. if (selectedRowId) {
  929. handleRowSelect(selectedRowId);
  930. }
  931. }}
  932. lotData={lotData}
  933. />
  934. )}
  935. */}
  936. </Stack>
  937. </FormProvider>
  938. );
  939. };
  940. export default PickExecution;