FPSMS-frontend
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 

686 строки
26 KiB

  1. "use client";
  2. import {
  3. Box,
  4. Button,
  5. Stack,
  6. Typography,
  7. Chip,
  8. CircularProgress,
  9. Table,
  10. TableBody,
  11. TableCell,
  12. TableContainer,
  13. TableHead,
  14. TableRow,
  15. Paper,
  16. Checkbox,
  17. TextField,
  18. FormControlLabel,
  19. Radio,
  20. TablePagination,
  21. ToggleButton
  22. } from "@mui/material";
  23. import { useState, useCallback, useEffect, useRef, useMemo } from "react";
  24. import { useTranslation } from "react-i18next";
  25. import {
  26. AllPickedStockTakeListReponse,
  27. getInventoryLotDetailsBySection,
  28. InventoryLotDetailResponse,
  29. saveApproverStockTakeRecord,
  30. SaveApproverStockTakeRecordRequest,
  31. BatchSaveApproverStockTakeRecordRequest,
  32. batchSaveApproverStockTakeRecords,
  33. updateStockTakeRecordStatusToNotMatch,
  34. } from "@/app/api/stockTake/actions";
  35. import { useSession } from "next-auth/react";
  36. import { SessionWithTokens } from "@/config/authConfig";
  37. import dayjs from "dayjs";
  38. import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
  39. interface ApproverStockTakeProps {
  40. selectedSession: AllPickedStockTakeListReponse;
  41. onBack: () => void;
  42. onSnackbar: (message: string, severity: "success" | "error" | "warning") => void;
  43. }
  44. type QtySelectionType = "first" | "second" | "approver";
  45. const ApproverStockTake: React.FC<ApproverStockTakeProps> = ({
  46. selectedSession,
  47. onBack,
  48. onSnackbar,
  49. }) => {
  50. const { t } = useTranslation(["inventory", "common"]);
  51. const { data: session } = useSession() as { data: SessionWithTokens | null };
  52. const [inventoryLotDetails, setInventoryLotDetails] = useState<InventoryLotDetailResponse[]>([]);
  53. const [loadingDetails, setLoadingDetails] = useState(false);
  54. const [showOnlyWithDifference, setShowOnlyWithDifference] = useState(false);
  55. // 每个记录的选择状态,key 为 detail.id
  56. const [qtySelection, setQtySelection] = useState<Record<number, QtySelectionType>>({});
  57. const [approverQty, setApproverQty] = useState<Record<number, string>>({});
  58. const [approverBadQty, setApproverBadQty] = useState<Record<number, string>>({});
  59. const [saving, setSaving] = useState(false);
  60. const [batchSaving, setBatchSaving] = useState(false);
  61. const [updatingStatus, setUpdatingStatus] = useState(false);
  62. const [page, setPage] = useState(0);
  63. const [pageSize, setPageSize] = useState<number | string>("all");
  64. const [total, setTotal] = useState(0);
  65. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  66. const handleBatchSubmitAllRef = useRef<() => Promise<void>>();
  67. const handleChangePage = useCallback((event: unknown, newPage: number) => {
  68. setPage(newPage);
  69. }, []);
  70. const handleChangeRowsPerPage = useCallback((event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
  71. const newSize = parseInt(event.target.value, 10);
  72. if (newSize === -1) {
  73. setPageSize("all");
  74. } else if (!isNaN(newSize)) {
  75. setPageSize(newSize);
  76. }
  77. setPage(0);
  78. }, []);
  79. const loadDetails = useCallback(async (pageNum: number, size: number | string) => {
  80. setLoadingDetails(true);
  81. try {
  82. let actualSize: number;
  83. if (size === "all") {
  84. if (selectedSession.totalInventoryLotNumber > 0) {
  85. actualSize = selectedSession.totalInventoryLotNumber;
  86. } else if (total > 0) {
  87. actualSize = total;
  88. } else {
  89. actualSize = 10000;
  90. }
  91. } else {
  92. actualSize = typeof size === 'string' ? parseInt(size, 10) : size;
  93. }
  94. const response = await getInventoryLotDetailsBySection(
  95. selectedSession.stockTakeSession,
  96. selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null,
  97. pageNum,
  98. actualSize
  99. );
  100. setInventoryLotDetails(Array.isArray(response.records) ? response.records : []);
  101. setTotal(response.total || 0);
  102. } catch (e) {
  103. console.error(e);
  104. setInventoryLotDetails([]);
  105. setTotal(0);
  106. } finally {
  107. setLoadingDetails(false);
  108. }
  109. }, [selectedSession, total]);
  110. useEffect(() => {
  111. loadDetails(page, pageSize);
  112. }, [page, pageSize, loadDetails]);
  113. const calculateDifference = useCallback((detail: InventoryLotDetailResponse, selection: QtySelectionType): number => {
  114. let selectedQty = 0;
  115. if (selection === "first") {
  116. selectedQty = detail.firstStockTakeQty || 0;
  117. } else if (selection === "second") {
  118. selectedQty = detail.secondStockTakeQty || 0;
  119. } else if (selection === "approver") {
  120. selectedQty = (parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0")) || 0;
  121. }
  122. const bookQty = detail.availableQty || 0;
  123. return selectedQty - bookQty;
  124. }, [approverQty, approverBadQty]);
  125. // 3. 修改默认选择逻辑(在 loadDetails 的 useEffect 中,或创建一个新的 useEffect)
  126. useEffect(() => {
  127. // 初始化默认选择:如果 second 存在则选择 second,否则选择 first
  128. const newSelections: Record<number, QtySelectionType> = {};
  129. inventoryLotDetails.forEach(detail => {
  130. if (!qtySelection[detail.id]) {
  131. // 如果 second 不为 null 且大于 0,默认选择 second,否则选择 first
  132. if (detail.secondStockTakeQty != null && detail.secondStockTakeQty > 0) {
  133. newSelections[detail.id] = "second";
  134. } else {
  135. newSelections[detail.id] = "first";
  136. }
  137. }
  138. });
  139. if (Object.keys(newSelections).length > 0) {
  140. setQtySelection(prev => ({ ...prev, ...newSelections }));
  141. }
  142. }, [inventoryLotDetails]);
  143. // 4. 添加过滤逻辑(在渲染表格之前)
  144. const filteredDetails = useMemo(() => {
  145. if (!showOnlyWithDifference) {
  146. return inventoryLotDetails;
  147. }
  148. return inventoryLotDetails.filter(detail => {
  149. const selection = qtySelection[detail.id] || (detail.secondStockTakeQty != null && detail.secondStockTakeQty > 0 ? "second" : "first");
  150. const difference = calculateDifference(detail, selection);
  151. return difference !== 0;
  152. });
  153. }, [inventoryLotDetails, showOnlyWithDifference, qtySelection, calculateDifference]);
  154. const handleSaveApproverStockTake = useCallback(async (detail: InventoryLotDetailResponse) => {
  155. if (!selectedSession || !currentUserId) {
  156. return;
  157. }
  158. const selection = qtySelection[detail.id] || "first";
  159. let finalQty: number;
  160. let finalBadQty: number;
  161. if (selection === "first") {
  162. if (detail.firstStockTakeQty == null) {
  163. onSnackbar(t("First QTY is not available"), "error");
  164. return;
  165. }
  166. finalQty = detail.firstStockTakeQty;
  167. finalBadQty = detail.firstBadQty || 0;
  168. } else if (selection === "second") {
  169. if (detail.secondStockTakeQty == null) {
  170. onSnackbar(t("Second QTY is not available"), "error");
  171. return;
  172. }
  173. finalQty = detail.secondStockTakeQty;
  174. finalBadQty = detail.secondBadQty || 0;
  175. } else {
  176. // Approver input
  177. const approverQtyValue = approverQty[detail.id];
  178. const approverBadQtyValue = approverBadQty[detail.id];
  179. if (approverQtyValue === undefined || approverQtyValue === null || approverQtyValue === "") {
  180. onSnackbar(t("Please enter Approver QTY"), "error");
  181. return;
  182. }
  183. if (approverBadQtyValue === undefined || approverBadQtyValue === null || approverBadQtyValue === "") {
  184. onSnackbar(t("Please enter Approver Bad QTY"), "error");
  185. return;
  186. }
  187. finalQty = parseFloat(approverQtyValue) || 0;
  188. finalBadQty = parseFloat(approverBadQtyValue) || 0;
  189. }
  190. setSaving(true);
  191. try {
  192. const request: SaveApproverStockTakeRecordRequest = {
  193. stockTakeRecordId: detail.stockTakeRecordId || null,
  194. qty: finalQty,
  195. badQty: finalBadQty,
  196. approverId: currentUserId,
  197. approverQty: selection === "approver" ? finalQty : null,
  198. approverBadQty: selection === "approver" ? finalBadQty : null,
  199. };
  200. await saveApproverStockTakeRecord(
  201. request,
  202. selectedSession.stockTakeId
  203. );
  204. onSnackbar(t("Approver stock take record saved successfully"), "success");
  205. await loadDetails(page, pageSize);
  206. } catch (e: any) {
  207. console.error("Save approver stock take record error:", e);
  208. let errorMessage = t("Failed to save approver stock take record");
  209. if (e?.message) {
  210. errorMessage = e.message;
  211. } else if (e?.response) {
  212. try {
  213. const errorData = await e.response.json();
  214. errorMessage = errorData.message || errorData.error || errorMessage;
  215. } catch {
  216. // ignore
  217. }
  218. }
  219. onSnackbar(errorMessage, "error");
  220. } finally {
  221. setSaving(false);
  222. }
  223. }, [selectedSession, qtySelection, approverQty, approverBadQty, t, currentUserId, onSnackbar, page, pageSize, loadDetails]);
  224. const handleUpdateStatusToNotMatch = useCallback(async (detail: InventoryLotDetailResponse) => {
  225. if (!detail.stockTakeRecordId) {
  226. onSnackbar(t("Stock take record ID is required"), "error");
  227. return;
  228. }
  229. setUpdatingStatus(true);
  230. try {
  231. await updateStockTakeRecordStatusToNotMatch(detail.stockTakeRecordId);
  232. onSnackbar(t("Stock take record status updated to not match"), "success");
  233. } catch (e: any) {
  234. console.error("Update stock take record status error:", e);
  235. let errorMessage = t("Failed to update stock take record status");
  236. if (e?.message) {
  237. errorMessage = e.message;
  238. } else if (e?.response) {
  239. try {
  240. const errorData = await e.response.json();
  241. errorMessage = errorData.message || errorData.error || errorMessage;
  242. } catch {
  243. // ignore
  244. }
  245. }
  246. onSnackbar(errorMessage, "error");
  247. } finally {
  248. setUpdatingStatus(false);
  249. // Reload after status update - the useEffect will handle it with current page/pageSize
  250. // Or explicitly reload:
  251. setPage((currentPage) => {
  252. setPageSize((currentPageSize) => {
  253. setTimeout(() => {
  254. loadDetails(currentPage, currentPageSize);
  255. }, 0);
  256. return currentPageSize;
  257. });
  258. return currentPage;
  259. });
  260. }
  261. }, [selectedSession, t, onSnackbar, loadDetails]);
  262. const handleBatchSubmitAll = useCallback(async () => {
  263. if (!selectedSession || !currentUserId) {
  264. console.log('handleBatchSubmitAll: Missing selectedSession or currentUserId');
  265. return;
  266. }
  267. console.log('handleBatchSubmitAll: Starting batch approver save...');
  268. setBatchSaving(true);
  269. try {
  270. const request: BatchSaveApproverStockTakeRecordRequest = {
  271. stockTakeId: selectedSession.stockTakeId,
  272. stockTakeSection: selectedSession.stockTakeSession,
  273. approverId: currentUserId,
  274. };
  275. const result = await batchSaveApproverStockTakeRecords(request);
  276. console.log('handleBatchSubmitAll: Result:', result);
  277. onSnackbar(
  278. t("Batch approver save completed: {{success}} success, {{errors}} errors", {
  279. success: result.successCount,
  280. errors: result.errorCount,
  281. }),
  282. result.errorCount > 0 ? "warning" : "success"
  283. );
  284. await loadDetails(page, pageSize);
  285. } catch (e: any) {
  286. console.error("handleBatchSubmitAll: Error:", e);
  287. let errorMessage = t("Failed to batch save approver stock take records");
  288. if (e?.message) {
  289. errorMessage = e.message;
  290. } else if (e?.response) {
  291. try {
  292. const errorData = await e.response.json();
  293. errorMessage = errorData.message || errorData.error || errorMessage;
  294. } catch {
  295. // ignore
  296. }
  297. }
  298. onSnackbar(errorMessage, "error");
  299. } finally {
  300. setBatchSaving(false);
  301. }
  302. }, [selectedSession, t, currentUserId, onSnackbar, page, pageSize, loadDetails]);
  303. useEffect(() => {
  304. handleBatchSubmitAllRef.current = handleBatchSubmitAll;
  305. }, [handleBatchSubmitAll]);
  306. const formatNumber = (num: number | null | undefined): string => {
  307. if (num == null) return "0.00";
  308. return num.toLocaleString('en-US', {
  309. minimumFractionDigits: 2,
  310. maximumFractionDigits: 2
  311. });
  312. };
  313. const uniqueWarehouses = Array.from(
  314. new Set(
  315. inventoryLotDetails
  316. .map(detail => detail.warehouse)
  317. .filter(warehouse => warehouse && warehouse.trim() !== "")
  318. )
  319. ).join(", ");
  320. const isSubmitDisabled = useCallback((detail: InventoryLotDetailResponse): boolean => {
  321. // 如果已经有 finalQty(已完成审批),不允许再次编辑
  322. if (detail.finalQty != null) {
  323. return true;
  324. }
  325. // 获取当前选择模式
  326. const hasFirst = detail.firstStockTakeQty != null && detail.firstStockTakeQty >= 0;
  327. const hasSecond = detail.secondStockTakeQty != null && detail.secondStockTakeQty >= 0;
  328. const selection = qtySelection[detail.id] || (hasSecond ? "second" : "first");
  329. // 如果选择了 "approver" 模式,检查用户是否已经输入了值
  330. if (selection === "approver") {
  331. const approverQtyValue = approverQty[detail.id];
  332. const approverBadQtyValue = approverBadQty[detail.id];
  333. // 如果用户已经输入了值(包括0),允许保存
  334. if (approverQtyValue !== undefined && approverQtyValue !== null && approverQtyValue !== "" &&
  335. approverBadQtyValue !== undefined && approverBadQtyValue !== null && approverBadQtyValue !== "") {
  336. return false; // 允许保存
  337. }
  338. // 如果用户还没有输入值,禁用按钮
  339. return true;
  340. }
  341. // 对于 first 或 second 模式,需要检查是否有有效的数量(允许0)
  342. // 只要 firstStockTakeQty 不为 null,就允许保存(即使为0)
  343. if (detail.firstStockTakeQty == null) {
  344. return true; // 如果 firstStockTakeQty 为 null,禁用
  345. }
  346. return false; // 允许保存
  347. }, [qtySelection, approverQty, approverBadQty]);
  348. return (
  349. <Box>
  350. <Button onClick={onBack} sx={{ mb: 2, border: "1px solid", borderColor: "primary.main" }}>
  351. {t("Back to List")}
  352. </Button>
  353. <Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 2 }}>
  354. <Typography variant="h6" sx={{ mb: 2 }}>
  355. {t("Stock Take Section")}: {selectedSession.stockTakeSession}
  356. {uniqueWarehouses && (
  357. <> {t("Warehouse")}: {uniqueWarehouses}</>
  358. )}
  359. </Typography>
  360. <Stack direction="row" spacing={2} alignItems="center">
  361. <Button
  362. variant={showOnlyWithDifference ? "contained" : "outlined"}
  363. color="primary"
  364. onClick={() => setShowOnlyWithDifference(!showOnlyWithDifference)}
  365. startIcon={
  366. <Checkbox
  367. checked={showOnlyWithDifference}
  368. onChange={(e) => setShowOnlyWithDifference(e.target.checked)}
  369. sx={{ p: 0, pointerEvents: 'none' }}
  370. />
  371. }
  372. sx={{ textTransform: 'none' }}
  373. >
  374. {t("Only Variance")}
  375. </Button>
  376. <Button variant="contained" color="primary" onClick={handleBatchSubmitAll} disabled={batchSaving}>
  377. {t("Batch Save All")}
  378. </Button>
  379. </Stack>
  380. </Stack>
  381. {loadingDetails ? (
  382. <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
  383. <CircularProgress />
  384. </Box>
  385. ) : (
  386. <>
  387. <TablePagination
  388. component="div"
  389. count={total}
  390. page={page}
  391. onPageChange={handleChangePage}
  392. rowsPerPage={pageSize === "all" ? total : (pageSize as number)}
  393. onRowsPerPageChange={handleChangeRowsPerPage}
  394. rowsPerPageOptions={[10, 25, 50, 100, { value: -1, label: t("All") }]}
  395. labelRowsPerPage={t("Rows per page")}
  396. />
  397. <TableContainer component={Paper}>
  398. <Table>
  399. <TableHead>
  400. <TableRow>
  401. <TableCell>{t("Warehouse Location")}</TableCell>
  402. <TableCell>{t("Item-lotNo-ExpiryDate")}</TableCell>
  403. <TableCell>{t("Stock Take Qty(include Bad Qty)= Available Qty")}</TableCell>
  404. <TableCell>{t("Remark")}</TableCell>
  405. <TableCell>{t("UOM")}</TableCell>
  406. <TableCell>{t("Record Status")}</TableCell>
  407. <TableCell>{t("Action")}</TableCell>
  408. </TableRow>
  409. </TableHead>
  410. <TableBody>
  411. {filteredDetails.length === 0 ? (
  412. <TableRow>
  413. <TableCell colSpan={7} align="center">
  414. <Typography variant="body2" color="text.secondary">
  415. {t("No data")}
  416. </Typography>
  417. </TableCell>
  418. </TableRow>
  419. ) : (
  420. filteredDetails.map((detail) => {
  421. // const submitDisabled = isSubmitDisabled(detail);
  422. const hasFirst = detail.firstStockTakeQty != null && detail.firstStockTakeQty >= 0;
  423. const hasSecond = detail.secondStockTakeQty != null && detail.secondStockTakeQty >= 0; // 改为 >= 0,允许0值
  424. const selection = qtySelection[detail.id] || (hasSecond ? "second" : "first");
  425. return (
  426. <TableRow key={detail.id}>
  427. <TableCell>{detail.warehouseArea || "-"}{detail.warehouseSlot || "-"}</TableCell>
  428. <TableCell sx={{
  429. maxWidth: 150,
  430. wordBreak: 'break-word',
  431. whiteSpace: 'normal',
  432. lineHeight: 1.5
  433. }}>
  434. <Stack spacing={0.5}>
  435. <Box>{detail.itemCode || "-"} {detail.itemName || "-"}</Box>
  436. <Box>{detail.lotNo || "-"}</Box>
  437. <Box>{detail.expiryDate ? dayjs(detail.expiryDate).format(OUTPUT_DATE_FORMAT) : "-"}</Box>
  438. </Stack>
  439. </TableCell>
  440. <TableCell sx={{ minWidth: 300 }}>
  441. {detail.finalQty != null ? (
  442. <Stack spacing={0.5}>
  443. {(() => {
  444. const finalDifference = (detail.finalQty || 0) - (detail.availableQty || 0);
  445. const differenceColor = finalDifference > 0
  446. ? 'error.main'
  447. : finalDifference < 0
  448. ? 'error.main'
  449. : 'success.main';
  450. return (
  451. <Typography variant="body2" sx={{ fontWeight: 'bold', color: differenceColor }}>
  452. {t("Difference")}: {formatNumber(detail.finalQty)} - {formatNumber(detail.availableQty)} = {formatNumber(finalDifference)}
  453. </Typography>
  454. );
  455. })()}
  456. </Stack>
  457. ) : (
  458. <Stack spacing={1}>
  459. {hasFirst && (
  460. <Stack direction="row" spacing={1} alignItems="center">
  461. <Radio
  462. size="small"
  463. checked={selection === "first"}
  464. onChange={() => setQtySelection({ ...qtySelection, [detail.id]: "first" })}
  465. />
  466. <Typography variant="body2">
  467. {t("First")}: {formatNumber((detail.firstStockTakeQty??0)+(detail.firstBadQty??0))} ({detail.firstBadQty??0}) = {formatNumber(detail.firstStockTakeQty??0)}
  468. </Typography>
  469. </Stack>
  470. )}
  471. {hasSecond && (
  472. <Stack direction="row" spacing={1} alignItems="center">
  473. <Radio
  474. size="small"
  475. checked={selection === "second"}
  476. onChange={() => setQtySelection({ ...qtySelection, [detail.id]: "second" })}
  477. />
  478. <Typography variant="body2">
  479. {t("Second")}: {formatNumber((detail.secondStockTakeQty??0)+(detail.secondBadQty??0))} ({detail.secondBadQty??0}) = {formatNumber(detail.secondStockTakeQty??0)}
  480. </Typography>
  481. </Stack>
  482. )}
  483. {hasSecond && (
  484. <Stack direction="row" spacing={1} alignItems="center">
  485. <Radio
  486. size="small"
  487. checked={selection === "approver"}
  488. onChange={() => setQtySelection({ ...qtySelection, [detail.id]: "approver" })}
  489. />
  490. <Typography variant="body2">{t("Approver Input")}:</Typography>
  491. <TextField
  492. size="small"
  493. type="number"
  494. value={approverQty[detail.id] || ""}
  495. onChange={(e) => setApproverQty({ ...approverQty, [detail.id]: e.target.value })}
  496. sx={{
  497. width: 130,
  498. minWidth: 130,
  499. '& .MuiInputBase-input': {
  500. height: '1.4375em',
  501. padding: '4px 8px'
  502. }
  503. }}
  504. placeholder={t("Stock Take Qty") }
  505. disabled={selection !== "approver"}
  506. />
  507. <TextField
  508. size="small"
  509. type="number"
  510. value={approverBadQty[detail.id] || ""}
  511. onChange={(e) => setApproverBadQty({ ...approverBadQty, [detail.id]: e.target.value })}
  512. sx={{
  513. width: 130,
  514. minWidth: 130,
  515. '& .MuiInputBase-input': {
  516. height: '1.4375em',
  517. padding: '4px 8px'
  518. }
  519. }}
  520. placeholder={t("Bad Qty")}
  521. disabled={selection !== "approver"}
  522. />
  523. <Typography variant="body2">
  524. ={(parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0"))}
  525. </Typography>
  526. </Stack>
  527. )}
  528. {(() => {
  529. let selectedQty = 0;
  530. if (selection === "first") {
  531. selectedQty = detail.firstStockTakeQty || 0;
  532. } else if (selection === "second") {
  533. selectedQty = detail.secondStockTakeQty || 0;
  534. } else if (selection === "approver") {
  535. selectedQty = (parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0"))|| 0;
  536. }
  537. const bookQty = detail.availableQty || 0;
  538. const difference = selectedQty - bookQty;
  539. const differenceColor = difference > 0
  540. ? 'error.main'
  541. : difference < 0
  542. ? 'error.main'
  543. : 'success.main';
  544. return (
  545. <Typography variant="body2" sx={{ fontWeight: 'bold', color: differenceColor }}>
  546. {t("Difference")}: {t("selected stock take qty")}({formatNumber(selectedQty)}) - {t("book qty")}({formatNumber(bookQty)}) = {formatNumber(difference)}
  547. </Typography>
  548. );
  549. })()}
  550. </Stack>
  551. )}
  552. </TableCell>
  553. <TableCell>
  554. <Typography variant="body2">
  555. {detail.remarks || "-"}
  556. </Typography>
  557. </TableCell>
  558. <TableCell>{detail.uom || "-"}</TableCell>
  559. <TableCell>
  560. {detail.stockTakeRecordStatus === "pass" ? (
  561. <Chip size="small" label={t(detail.stockTakeRecordStatus)} color="success" />
  562. ) : detail.stockTakeRecordStatus === "notMatch" ? (
  563. <Chip size="small" label={t(detail.stockTakeRecordStatus)} color="warning" />
  564. ) : (
  565. <Chip size="small" label={t(detail.stockTakeRecordStatus || "")} color="default" />
  566. )}
  567. </TableCell>
  568. <TableCell>
  569. {detail.stockTakeRecordId && detail.stockTakeRecordStatus !== "notMatch" && (
  570. <Box>
  571. <Button
  572. size="small"
  573. variant="outlined"
  574. color="warning"
  575. onClick={() => handleUpdateStatusToNotMatch(detail)}
  576. disabled={updatingStatus || detail.stockTakeRecordStatus === "completed"}
  577. >
  578. {t("ReStockTake")}
  579. </Button>
  580. </Box>
  581. )}
  582. <br/>
  583. {detail.finalQty == null && (
  584. <Box>
  585. <Button
  586. size="small"
  587. variant="contained"
  588. onClick={() => handleSaveApproverStockTake(detail)}
  589. //disabled={saving || submitDisabled || detail.stockTakeRecordStatus === "completed"}
  590. >
  591. {t("Save")}
  592. </Button>
  593. </Box>
  594. )}
  595. </TableCell>
  596. </TableRow>
  597. );
  598. })
  599. )}
  600. </TableBody>
  601. </Table>
  602. </TableContainer>
  603. <TablePagination
  604. component="div"
  605. count={total}
  606. page={page}
  607. onPageChange={handleChangePage}
  608. rowsPerPage={pageSize === "all" ? total : (pageSize as number)}
  609. onRowsPerPageChange={handleChangeRowsPerPage}
  610. rowsPerPageOptions={[10, 25, 50, 100, { value: -1, label: t("All") }]}
  611. labelRowsPerPage={t("Rows per page")}
  612. />
  613. </>
  614. )}
  615. </Box>
  616. );
  617. };
  618. export default ApproverStockTake;