FPSMS-frontend
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

QcStockInModal.tsx 31 KiB

3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
3日前
3ヶ月前
3日前
3ヶ月前
3ヶ月前
3日前
3日前
3日前
3日前
3ヶ月前
3ヶ月前
3週間前
3ヶ月前
4日前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
1ヶ月前
4日前
1ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. "use client";
  2. import { QcItemWithChecks, QcData } from "@/app/api/qc";
  3. import {
  4. Autocomplete,
  5. Box,
  6. Button,
  7. Divider,
  8. Grid,
  9. Modal,
  10. ModalProps,
  11. Stack,
  12. Tab,
  13. Tabs,
  14. TabsProps,
  15. TextField,
  16. Typography,
  17. } from "@mui/material";
  18. import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
  19. import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form";
  20. import { StockInLineRow } from "../PoDetail/PoInputGrid";
  21. import { useTranslation } from "react-i18next";
  22. import StockInForm from "../StockIn/StockInForm";
  23. import QcComponent from "./QcComponent";
  24. import PutAwayForm from "../PoDetail/PutAwayForm";
  25. import { GridRowModes, GridRowSelectionModel, useGridApiRef } from "@mui/x-data-grid";
  26. import {msg, submitDialogWithWarning} from "../Swal/CustomAlerts";
  27. import { INPUT_DATE_FORMAT, arrayToDateString, dayjsToDateTimeString } from "@/app/utils/formatUtil";
  28. import dayjs from "dayjs";
  29. import { fetchPoQrcode } from "@/app/api/pdf/actions";
  30. import { downloadFile } from "@/app/utils/commonUtil";
  31. import { PrinterCombo } from "@/app/api/settings/printer";
  32. import { EscalationResult } from "@/app/api/escalation";
  33. import { SessionWithTokens } from "@/config/authConfig";
  34. import { GridRowModesModel } from "@mui/x-data-grid";
  35. import { isEmpty } from "lodash";
  36. import { EscalationCombo } from "@/app/api/user";
  37. import { truncateSync } from "fs";
  38. import { ModalFormInput, StockInLineInput, StockInLine, StockInStatus } from "@/app/api/stockIn";
  39. import { StockInLineEntry, updateStockInLine, printQrCodeForSil, PrintQrCodeForSilRequest } from "@/app/api/stockIn/actions";
  40. import { fetchStockInLineInfo } from "@/app/api/stockIn/actions";
  41. import FgStockInForm from "../StockIn/FgStockInForm";
  42. import LoadingComponent from "../General/LoadingComponent";
  43. import { printFGStockInLabel, PrintFGStockInLabelRequest, fetchFGStockInLabel } from "@/app/api/jo/actions";
  44. import { fetchItemForPutAway } from "@/app/api/stockIn/actions";
  45. const style = {
  46. position: "absolute",
  47. top: "50%",
  48. left: "50%",
  49. transform: "translate(-50%, -50%)",
  50. bgcolor: "background.paper",
  51. // pt: 5,
  52. // px: 5,
  53. // pb: 10,
  54. display: "block",
  55. width: { xs: "90%", sm: "90%", md: "90%" },
  56. height: { xs: "90%", sm: "90%", md: "90%" },
  57. };
  58. interface CommonProps extends Omit<ModalProps, "children"> {
  59. // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] } | undefined;
  60. inputDetail: StockInLineInput | undefined;
  61. session: SessionWithTokens | null;
  62. warehouse?: any[];
  63. printerCombo: PrinterCombo[];
  64. onClose: () => void;
  65. skipQc?: Boolean;
  66. printSource?: "stockIn" | "productionProcess";
  67. }
  68. interface Props extends CommonProps {
  69. // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] };
  70. }
  71. const QcStockInModal: React.FC<Props> = ({
  72. open,
  73. onClose,
  74. // itemDetail,
  75. inputDetail,
  76. session,
  77. warehouse,
  78. printerCombo,
  79. skipQc = false,
  80. printSource = "stockIn",
  81. }) => {
  82. const {
  83. t,
  84. i18n: { language },
  85. } = useTranslation("purchaseOrder");
  86. const [stockInLineInfo, setStockInLineInfo] = useState<StockInLine>();
  87. const [isLoading, setIsLoading] = useState<boolean>(false);
  88. const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
  89. // const [skipQc, setSkipQc] = useState<Boolean>(false);
  90. // const [viewOnly, setViewOnly] = useState(false);
  91. const [itemLocationCode, setItemLocationCode] = useState<string | null>(null);
  92. const printerStorageKey = useMemo(
  93. () => `qcStockInModal_selectedPrinterId_${session?.id ?? "guest"}`,
  94. [session?.id],
  95. );
  96. const getDefaultPrinter = useMemo(() => {
  97. if (!printerCombo.length) return undefined;
  98. if (typeof window === "undefined") return printerCombo[0];
  99. const savedId = sessionStorage.getItem(printerStorageKey);
  100. const matched = savedId ? printerCombo.find(p => p.id === Number(savedId)) : undefined;
  101. return matched ?? printerCombo[0];
  102. }, [printerCombo, printerStorageKey]);
  103. const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0]);
  104. const [printQty, setPrintQty] = useState(1);
  105. const [tabIndex, setTabIndex] = useState(0);
  106. const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
  107. (_e, newValue) => {
  108. setTabIndex(newValue);
  109. },
  110. [],
  111. );
  112. const fetchStockInLineData = useCallback(
  113. async (stockInLineId: number) => {
  114. try {
  115. const res = await fetchStockInLineInfo(stockInLineId);
  116. if (res) {
  117. console.log("%c Fetched Stock In Line: ", "color:orange", res);
  118. console.log("%c [QC] itemId in response:", "color:yellow", res.itemId);
  119. console.log("%c [QC] locationCode in response:", "color:yellow", res.locationCode);
  120. // 如果 res 中没有 itemId,检查是否有其他方式获取
  121. if (!res.itemId) {
  122. console.warn("%c [QC] Warning: itemId is missing in response!", "color:red");
  123. }
  124. setStockInLineInfo({...inputDetail, ...res, expiryDate: res.expiryDate});
  125. } else throw("Result is undefined");
  126. } catch (e) {
  127. console.log("%c Error when fetching Stock In Line: ", "color:red", e);
  128. console.log("%c Error details: ", "color:red", {
  129. message: e instanceof Error ? e.message : String(e),
  130. stack: e instanceof Error ? e.stack : undefined
  131. });
  132. alert("Something went wrong, please retry");
  133. closeHandler({}, "backdropClick");
  134. }
  135. },[fetchStockInLineInfo, inputDetail]
  136. );
  137. // Fetch info if id is input
  138. useEffect(() => {
  139. setIsLoading(true);
  140. setIsSubmitting(false);
  141. if (inputDetail && open) {
  142. console.log("%c Opened Modal with input:", "color:yellow", inputDetail);
  143. if (inputDetail.id) {
  144. const id = inputDetail.id;
  145. fetchStockInLineData(id);
  146. }
  147. }
  148. }, [open]);
  149. useEffect(() => {
  150. // 如果后端已经在 StockInLine 中返回了 locationCode,直接使用
  151. if (stockInLineInfo?.locationCode) {
  152. setItemLocationCode(stockInLineInfo.locationCode);
  153. console.log("%c [QC] item LocationCode from API:", "color:cyan", stockInLineInfo.locationCode);
  154. return;
  155. }
  156. // 如果没有 locationCode,尝试从 itemId 获取(向后兼容)
  157. const loadItemLocationCode = async () => {
  158. if (!stockInLineInfo?.itemId) return;
  159. try {
  160. const itemResult = await fetchItemForPutAway(stockInLineInfo.itemId);
  161. const item = itemResult.item;
  162. const locationCode = item.LocationCode || item.locationCode || null;
  163. setItemLocationCode(locationCode);
  164. console.log("%c [QC] item LocationCode from fetchItemForPutAway:", "color:cyan", locationCode);
  165. } catch (error) {
  166. console.error("Error fetching item to get LocationCode in QC:", error);
  167. setItemLocationCode(null);
  168. }
  169. };
  170. if (stockInLineInfo && stockInLineInfo.status !== StockInStatus.REJECTED) {
  171. loadItemLocationCode();
  172. }
  173. }, [stockInLineInfo]);
  174. // Make sure stock in line info is fetched
  175. useEffect(() => {
  176. if (stockInLineInfo) {
  177. if (stockInLineInfo.id) {
  178. if (isLoading) {
  179. formProps.reset({
  180. ...defaultNewValue
  181. });
  182. console.log("%c Modal loaded successfully", "color:lime");
  183. setIsLoading(false);
  184. }
  185. }
  186. }
  187. }, [stockInLineInfo]);
  188. const defaultNewValue = useMemo(() => {
  189. const d = stockInLineInfo;
  190. if (d !== undefined) {
  191. // console.log("%c sil info", "color:yellow", d )
  192. return (
  193. {
  194. ...d,
  195. // status: d.status ?? "pending",
  196. productionDate: d.productionDate ? arrayToDateString(d.productionDate, "input") : dayjs().format(INPUT_DATE_FORMAT),
  197. expiryDate: d.expiryDate ? (Array.isArray(d.expiryDate) ? arrayToDateString(d.expiryDate, "input") : d.expiryDate) : undefined,
  198. receiptDate: d.receiptDate ? arrayToDateString(d.receiptDate, "input")
  199. : dayjs().add(0, "month").format(INPUT_DATE_FORMAT),
  200. acceptQty: d.status != StockInStatus.REJECTED ? (d.acceptedQty ?? d.receivedQty ?? d.demandQty) : 0,
  201. // escResult: (d.escResult && d.escResult?.length > 0) ? d.escResult : [],
  202. // qcResult: (d.qcResult && d.qcResult?.length > 0) ? d.qcResult : [],//[...dummyQCData],
  203. warehouseId: d.defaultWarehouseId ?? 489,
  204. putAwayLines: d.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false, _disableDelete: true})) ?? [],
  205. } as ModalFormInput
  206. )
  207. } return undefined
  208. }, [stockInLineInfo])
  209. // const [qcItems, setQcItems] = useState(dummyQCData)
  210. const formProps = useForm<ModalFormInput>({
  211. defaultValues: {
  212. ...defaultNewValue,
  213. },
  214. });
  215. const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
  216. () => {
  217. setStockInLineInfo(undefined);
  218. formProps.reset({});
  219. onClose?.();
  220. },
  221. [onClose],
  222. );
  223. const isPutaway = () => {
  224. if (stockInLineInfo) {
  225. const status = stockInLineInfo.status;
  226. return status == "received";
  227. } else return false;
  228. };
  229. // Get show putaway
  230. const showPutaway = useMemo(() => {
  231. if (stockInLineInfo) {
  232. const status = stockInLineInfo.status;
  233. return status !== StockInStatus.PENDING && status !== StockInStatus.ESCALATED && status !== StockInStatus.REJECTED;
  234. }
  235. return false;
  236. }, [stockInLineInfo]);
  237. // Get is view only
  238. const viewOnly = useMemo(() => {
  239. if (stockInLineInfo) {
  240. if (stockInLineInfo.status) {
  241. const status = stockInLineInfo.status;
  242. const isViewOnly = status.toLowerCase() == StockInStatus.COMPLETED
  243. || status.toLowerCase() == StockInStatus.PARTIALLY_COMPLETED // TODO update DB
  244. || status.toLowerCase() == StockInStatus.REJECTED
  245. || (status.toLowerCase() == StockInStatus.ESCALATED && session?.id != stockInLineInfo.handlerId)
  246. if (showPutaway) { setTabIndex(1); } else { setTabIndex(0); }
  247. return isViewOnly;
  248. }
  249. }
  250. return true;
  251. }, [stockInLineInfo])
  252. const [openPutaway, setOpenPutaway] = useState(false);
  253. const onOpenPutaway = useCallback(() => {
  254. setOpenPutaway(true);
  255. }, []);
  256. const onClosePutaway = useCallback(() => {
  257. setOpenPutaway(false);
  258. }, []);
  259. // Stock In submission handler
  260. const onSubmitStockIn = useCallback<SubmitHandler<ModalFormInput>>(
  261. async (data, event) => {
  262. console.log("Stock In Submission:", event!.nativeEvent);
  263. // Extract only stock-in related fields
  264. const stockInData = {
  265. // quantity: data.quantity,
  266. // receiptDate: data.receiptDate,
  267. // batchNumber: data.batchNumber,
  268. // expiryDate: data.expiryDate,
  269. // warehouseId: data.warehouseId,
  270. // location: data.location,
  271. // unitCost: data.unitCost,
  272. data: data,
  273. // Add other stock-in specific fields from your form
  274. };
  275. console.log("Stock In Data:", stockInData);
  276. // Handle stock-in submission logic here
  277. // e.g., call API, update state, etc.
  278. },
  279. [],
  280. );
  281. // QC submission handler
  282. const onSubmitErrorQc = useCallback<SubmitErrorHandler<ModalFormInput>>(
  283. async (data, event) => {
  284. console.log("Error", data);
  285. }, []
  286. );
  287. // QC submission handler
  288. const onSubmitQc = useCallback<SubmitHandler<ModalFormInput>>(
  289. async (data, event) => {
  290. console.log("QC Submission:", event!.nativeEvent);
  291. console.log("Validating:", data.qcResult);
  292. // TODO: Move validation into QC page
  293. // if (errors.length > 0) {
  294. // alert(`未完成品檢: ${errors.map((err) => err[1].message)}`);
  295. // return;
  296. // }
  297. // Get QC data from the shared form context
  298. const qcAccept = data.qcDecision == 1;
  299. // const qcAccept = data.qcAccept;
  300. let acceptQty = Number(data.acceptQty);
  301. const qcResults = data.qcResult?.filter((qc) => qc.escalationLogId === undefined) || []; // Remove old QC data
  302. // const qcResults = data.qcResult as PurchaseQcResult[]; // qcItems;
  303. // const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems;
  304. // Validate QC data
  305. const validationErrors : string[] = [];
  306. // Check if failed items have failed quantity
  307. const failedItemsWithoutQty = qcResults.filter(item =>
  308. item.qcPassed === false && (!item.failQty || item.failQty <= 0)
  309. );
  310. if (failedItemsWithoutQty.length > 0) {
  311. validationErrors.push(`${t("Failed items must have failed quantity")}`);
  312. // validationErrors.push(`${t("Failed items must have failed quantity")}: ${failedItemsWithoutQty.map(item => item.code).join(', ')}`);
  313. }
  314. // Check if QC accept decision is made
  315. if (data.qcDecision === undefined) {
  316. // if (qcAccept === undefined) {
  317. validationErrors.push(t("QC decision is required"));
  318. }
  319. // Check if accept quantity is valid
  320. if (data.qcDecision == 2) {
  321. acceptQty = 0;
  322. } else {
  323. if (acceptQty === undefined || acceptQty <= 0) {
  324. validationErrors.push("Accept quantity must be greater than 0");
  325. }
  326. }
  327. // Check if dates are input
  328. // if (data.productionDate === undefined || data.productionDate == null) {
  329. // alert("請輸入生產日期!");
  330. // return;
  331. // }
  332. if (data.expiryDate === undefined || data.expiryDate == null) {
  333. alert("請輸入到期日!");
  334. return;
  335. }
  336. if (!qcResults.every((qc) => qc.qcPassed) && qcAccept && stockInLineInfo?.status != StockInStatus.ESCALATED) { //TODO: fix it please!
  337. validationErrors.push("有不合格檢查項目,無法收貨!");
  338. // submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?",
  339. // confirmButtonText: t("confirm putaway"), html: ""});
  340. // return;
  341. }
  342. // Check if all QC items have results
  343. const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined);
  344. if (itemsWithoutResult.length > 0 && stockInLineInfo?.status != StockInStatus.ESCALATED) { //TODO: fix it please!
  345. validationErrors.push(`${t("QC items without result")}`);
  346. // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`);
  347. }
  348. if (validationErrors.length > 0 && !skipQc) {
  349. console.error("QC Validation failed:", validationErrors);
  350. alert(`未完成品檢: ${validationErrors}`);
  351. return;
  352. }
  353. const qcData = {
  354. dnNo : data.dnNo? data.dnNo : "DN00000",
  355. // dnDate : data.dnDate? arrayToDateString(data.dnDate, "input") : dayjsToInputDateString(dayjs()),
  356. productionDate : data.productionDate ? (Array.isArray(data.productionDate) ? arrayToDateString(data.productionDate, "input") : data.productionDate) : undefined,
  357. expiryDate : data.expiryDate ? (Array.isArray(data.expiryDate) ? arrayToDateString(data.expiryDate, "input") : data.expiryDate) : undefined,
  358. receiptDate : data.receiptDate ? (Array.isArray(data.receiptDate) ? arrayToDateString(data.receiptDate, "input") : data.receiptDate) : undefined,
  359. qcAccept: qcAccept? qcAccept : false,
  360. acceptQty: acceptQty? acceptQty : 0,
  361. // qcResult: itemDetail.status != "escalated" ? qcResults.map(item => ({
  362. qcResult: qcResults.map(item => ({
  363. // id: item.id,
  364. qcItemId: item.qcItemId,
  365. // code: item.code,
  366. // qcDescription: item.qcDescription,
  367. qcPassed: item.qcPassed? item.qcPassed : false,
  368. failQty: (item.failQty && !item.qcPassed) ? item.failQty : 0,
  369. // failedQty: (typeof item.failedQty === "number" && !item.isPassed) ? item.failedQty : 0,
  370. remarks: item.remarks || '',
  371. })),
  372. };
  373. // const qcData = data;
  374. console.log("QC Data for submission:", qcData);
  375. if (data.qcDecision == 3) { // Escalate
  376. if (data.escalationLog?.handlerId == undefined) { alert("請選擇上報負責同事!"); return; }
  377. else if (data.escalationLog?.handlerId < 1) { alert("上報負責同事資料有誤"); return; }
  378. const escalationLog = {
  379. type : "qc",
  380. status : "pending", // TODO: update with supervisor decision
  381. reason : data.escalationLog?.reason,
  382. recordDate : dayjsToDateTimeString(dayjs()),
  383. handlerId : data.escalationLog?.handlerId,
  384. }
  385. console.log("Escalation Data for submission", escalationLog);
  386. setIsSubmitting(true); //TODO improve
  387. await postStockInLine({...qcData, escalationLog});
  388. } else {
  389. setIsSubmitting(true); //TODO improve
  390. await postStockInLine(qcData);
  391. }
  392. if (qcData.qcAccept) {
  393. // submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?",
  394. // confirmButtonText: t("confirm putaway"), html: ""});
  395. // onOpenPutaway();
  396. const isJobOrderBom = (stockInLineInfo?.jobOrderId != null || printSource === "productionProcess")
  397. && stockInLineInfo?.bomDescription === "WIP";
  398. if (isJobOrderBom) {
  399. // Auto putaway to default warehouse
  400. const defaultWarehouseId = stockInLineInfo?.defaultWarehouseId ?? 489;
  401. // Get warehouse name from warehouse prop or use default
  402. let defaultWarehouseName = "2F-W201-#A-01"; // Default warehouse name
  403. if (warehouse && warehouse.length > 0) {
  404. const defaultWarehouse = warehouse.find(w => w.id === defaultWarehouseId);
  405. if (defaultWarehouse) {
  406. defaultWarehouseName = `${defaultWarehouse.code} - ${defaultWarehouse.name}`;
  407. }
  408. }
  409. // Create putaway data
  410. const putawayData = {
  411. id: stockInLineInfo?.id, // Include ID
  412. itemId: stockInLineInfo?.itemId, // Include Item ID
  413. purchaseOrderId: stockInLineInfo?.purchaseOrderId, // Include PO ID if exists
  414. purchaseOrderLineId: stockInLineInfo?.purchaseOrderLineId, // Include POL ID if exists
  415. acceptedQty:acceptQty, // Include acceptedQty
  416. acceptQty: stockInLineInfo?.acceptedQty, // Putaway quantity
  417. warehouseId: defaultWarehouseId,
  418. status: "received", // Use string like PutAwayModal
  419. productionDate: data.productionDate ? (Array.isArray(data.productionDate) ? arrayToDateString(data.productionDate, "input") : data.productionDate) : undefined,
  420. expiryDate: data.expiryDate ? (Array.isArray(data.expiryDate) ? arrayToDateString(data.expiryDate, "input") : data.expiryDate) : undefined,
  421. receiptDate: data.receiptDate ? (Array.isArray(data.receiptDate) ? arrayToDateString(data.receiptDate, "input") : data.receiptDate) : undefined,
  422. inventoryLotLines: [{
  423. warehouseId: defaultWarehouseId,
  424. qty: stockInLineInfo?.acceptedQty, // Simplified like PutAwayModal
  425. }],
  426. } as StockInLineEntry & ModalFormInput;
  427. try {
  428. // Use updateStockInLine directly like PutAwayModal does
  429. const res = await updateStockInLine(putawayData);
  430. if (Boolean(res.id)) {
  431. console.log("Auto putaway completed for job order bom");
  432. }
  433. } catch (error) {
  434. console.error("Error during auto putaway:", error);
  435. alert(t("Auto putaway failed. Please complete putaway manually."));
  436. }
  437. }
  438. closeHandler({}, "backdropClick");
  439. // setTabIndex(1); // Need to go Putaway tab?
  440. } else {
  441. closeHandler({}, "backdropClick");
  442. }
  443. setIsSubmitting(false);
  444. msg("已更新來貨狀態");
  445. return ;
  446. },
  447. [onOpenPutaway, formProps.formState.errors],
  448. );
  449. const postStockInLine = useCallback(async (args: ModalFormInput) => {
  450. const submitData = {
  451. ...stockInLineInfo, ...args
  452. } as StockInLineEntry & ModalFormInput;
  453. console.log("Submitting", submitData);
  454. const res = await updateStockInLine(submitData);
  455. return res;
  456. }, [stockInLineInfo])
  457. // Put away model
  458. const [pafRowModesModel, setPafRowModesModel] = useState<GridRowModesModel>({})
  459. const [pafRowSelectionModel, setPafRowSelectionModel] = useState<GridRowSelectionModel>([])
  460. const pafSubmitDisable = useMemo(() => {
  461. return Object.entries(pafRowModesModel).length > 0 || Object.entries(pafRowModesModel).some(([key, value], index) => value.mode === GridRowModes.Edit)
  462. }, [pafRowModesModel])
  463. // Putaway submission handler
  464. const onSubmitPutaway = useCallback<SubmitHandler<ModalFormInput>>(
  465. async (data, event) => {
  466. // Extract only putaway related fields
  467. const putawayData = {
  468. acceptQty: Number(data.acceptQty?? (stockInLineInfo?.demandQty?? (stockInLineInfo?.acceptedQty))), //TODO improve
  469. warehouseId: data.warehouseId,
  470. status: data.status, //TODO Fix it!
  471. // ...data,
  472. // dnDate : data.dnDate? arrayToDateString(data.dnDate, "input") : dayjsToInputDateString(dayjs()),
  473. productionDate : arrayToDateString(data.productionDate, "input"),
  474. expiryDate : arrayToDateString(data.expiryDate, "input"),
  475. receiptDate : arrayToDateString(data.receiptDate, "input"),
  476. // for putaway data
  477. inventoryLotLines: data.putAwayLines?.filter((line) => line._isNew !== false)
  478. // Add other putaway specific fields
  479. } as ModalFormInput;
  480. console.log("Putaway Data:", putawayData);
  481. console.log("DEBUG",data.putAwayLines);
  482. // if (data.putAwayLines!!.filter((line) => line._isNew !== false).length <= 0) {
  483. // alert("請新增上架資料!");
  484. // return;
  485. // }
  486. if (data.putAwayLines!!.filter((line) => /[^0-9]/.test(String(line.qty))).length > 0) { //TODO Improve
  487. alert("上架數量不正確!");
  488. return;
  489. }
  490. if (data.putAwayLines!!.reduce((acc, cur) => acc + Number(cur.qty), 0) > putawayData.acceptQty!!) {
  491. alert(`上架數量不能大於 ${putawayData.acceptQty}!`);
  492. return;
  493. }
  494. // Handle putaway submission logic here
  495. const res = await postStockInLine(putawayData);
  496. console.log("result ", res);
  497. // Close modal after successful putaway
  498. closeHandler({}, "backdropClick");
  499. },
  500. [closeHandler],
  501. );
  502. // Print handler
  503. useEffect(() => {
  504. if (!printerCombo.length) return;
  505. if (typeof window === "undefined") {
  506. setSelectedPrinter(printerCombo[0]);
  507. return;
  508. }
  509. const savedId = sessionStorage.getItem(printerStorageKey);
  510. const matched = savedId ? printerCombo.find(p => p.id === Number(savedId)) : undefined;
  511. setSelectedPrinter(matched ?? printerCombo[0]);
  512. }, [printerCombo, printerStorageKey]);
  513. const [isPrinting, setIsPrinting] = useState(false)
  514. const handlePrint = useCallback(async () => {
  515. // console.log("Print putaway documents");
  516. console.log("%c data", "background: white; color: red", formProps.watch("putAwayLines"));
  517. // Handle print logic here
  518. // window.print();
  519. // const postData = { stockInLineIds: [itemDetail.id]};
  520. // const response = await fetchPoQrcode(postData);
  521. // if (response) {
  522. // downloadFile(new Uint8Array(response.blobValue), response.filename)
  523. // }
  524. try {
  525. setIsPrinting(() => true)
  526. if ((formProps.watch("putAwayLines") ?? []).filter((line) => /[^0-9]/.test(String(line.printQty))).length > 0) { //TODO Improve
  527. alert("列印數量不正確!");
  528. return;
  529. }
  530. // Conditionally call different APIs based on source
  531. let response;
  532. if (printSource === "productionProcess") {
  533. // Use FG Stock In Label print API for production process
  534. const data: PrintFGStockInLabelRequest = {
  535. stockInLineId: stockInLineInfo?.id ?? 0,
  536. printerId: selectedPrinter.id,
  537. printQty: printQty
  538. }
  539. response = await printFGStockInLabel(data);
  540. } else {
  541. // Use stock-in print API (default)
  542. const data: PrintQrCodeForSilRequest = {
  543. stockInLineId: stockInLineInfo?.id ?? 0,
  544. printerId: selectedPrinter.id,
  545. printQty: printQty
  546. }
  547. response = await printQrCodeForSil(data);
  548. }
  549. if (response) {
  550. console.log(response)
  551. }
  552. if (typeof window !== 'undefined' && selectedPrinter) {
  553. sessionStorage.setItem(printerStorageKey, String(selectedPrinter.id));
  554. }
  555. } finally {
  556. setIsPrinting(() => false)
  557. }
  558. }, [stockInLineInfo?.id, pafRowSelectionModel, printQty, selectedPrinter, printSource]);
  559. // const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => {
  560. // const isPassed = qcItems.every((qc) => qc.qcPassed);
  561. // console.log(isPassed)
  562. // if (isPassed) {
  563. // formProps.setValue("passingQty", acceptQty)
  564. // } else {
  565. // formProps.setValue("passingQty", 0)
  566. // }
  567. // return isPassed
  568. // }, [acceptQty, formProps])
  569. const printQrcode = useCallback(
  570. async () => {
  571. setIsPrinting(true);
  572. try {
  573. let response;
  574. if (printSource === "productionProcess") {
  575. // Use FG Stock In Label download API for production process
  576. if (!stockInLineInfo?.id) {
  577. console.error("Stock In Line ID is required for download");
  578. setIsPrinting(false);
  579. return;
  580. }
  581. const postData = { stockInLineId: stockInLineInfo.id };
  582. response = await fetchFGStockInLabel(postData);
  583. } else {
  584. const postData = { stockInLineIds: [stockInLineInfo?.id] };
  585. response = await fetchPoQrcode(postData);
  586. }
  587. if (response) {
  588. console.log(response);
  589. downloadFile(new Uint8Array(response.blobValue), response.filename!);
  590. }
  591. } catch (e) {
  592. console.log("%c Error downloading QR Code", "color:red", e);
  593. } finally {
  594. setIsPrinting(false);
  595. }
  596. },
  597. [stockInLineInfo, printSource],
  598. );
  599. return (
  600. <>
  601. <FormProvider {...formProps}>
  602. <Modal open={open} onClose={closeHandler}>
  603. <Box
  604. sx={{
  605. ...style,
  606. // padding: 2,
  607. maxHeight: "90vh",
  608. overflowY: "auto",
  609. marginLeft: 3,
  610. marginRight: 3,
  611. // overflow: "hidden",
  612. display: 'flex',
  613. flexDirection: 'column',
  614. }}
  615. >
  616. {(!isLoading && stockInLineInfo) ? (<>
  617. <Box sx={{ position: 'sticky', top: 0, bgcolor: 'background.paper',
  618. zIndex: 5, borderBottom: 2, borderColor: 'divider', width: "100%"}}>
  619. <Tabs
  620. value={tabIndex}
  621. onChange={handleTabChange}
  622. variant="scrollable"
  623. sx={{pl: 2, pr: 2, pt: 2}}
  624. >
  625. <Tab label={
  626. showPutaway ? t("dn and qc info") : t("qc processing")
  627. } iconPosition="end" />
  628. {showPutaway && <Tab label={t("putaway processing")} iconPosition="end" />}
  629. </Tabs>
  630. </Box>
  631. <Grid
  632. container
  633. justifyContent="flex-start"
  634. alignItems="flex-start"
  635. sx={{padding: 2}}
  636. >
  637. <Grid item xs={12}>
  638. {tabIndex === 0 &&
  639. <Box>
  640. <Grid item xs={12}>
  641. <Typography variant="h6" display="block" marginBlockEnd={1}>
  642. {t("Delivery Detail")}
  643. </Typography>
  644. </Grid>
  645. {stockInLineInfo.jobOrderId ? (
  646. <FgStockInForm itemDetail={stockInLineInfo} disabled={viewOnly || showPutaway} />
  647. ) : (
  648. <StockInForm itemDetail={stockInLineInfo} disabled={viewOnly || showPutaway} />
  649. )
  650. }
  651. {skipQc === false && (
  652. <QcComponent
  653. itemDetail={stockInLineInfo}
  654. disabled={viewOnly || showPutaway}
  655. />)
  656. }
  657. <Stack direction="row" justifyContent="flex-end" gap={1} sx={{pt:2}}>
  658. {(!viewOnly && !showPutaway) && (<Button
  659. id="Submit"
  660. type="button"
  661. variant="contained"
  662. color="primary"
  663. sx={{ mt: 1 }}
  664. onClick={formProps.handleSubmit(onSubmitQc, onSubmitErrorQc)}
  665. disabled={isSubmitting || isLoading}
  666. >
  667. {isSubmitting ? (t("submitting")) : (skipQc ? t("confirm") : t("confirm qc result"))}
  668. </Button>)}
  669. </Stack>
  670. </Box>
  671. }
  672. {tabIndex === 1 &&
  673. <Box>
  674. <PutAwayForm
  675. itemDetail={stockInLineInfo}
  676. warehouse={warehouse!}
  677. disabled={viewOnly}
  678. setRowModesModel={setPafRowModesModel}
  679. setRowSelectionModel={setPafRowSelectionModel}
  680. suggestedLocationCode={itemLocationCode || undefined}
  681. />
  682. </Box>
  683. }
  684. </Grid>
  685. </Grid>
  686. {tabIndex == 1 && (
  687. <Stack direction="row" justifyContent="flex-end" gap={1} sx={{m:3, mt:"auto"}}>
  688. <Autocomplete
  689. disableClearable
  690. options={printerCombo}
  691. defaultValue={selectedPrinter}
  692. onChange={(event, value) => {
  693. setSelectedPrinter(value)
  694. }}
  695. renderInput={(params) => (
  696. <TextField
  697. {...params}
  698. variant="outlined"
  699. label={t("Printer")}
  700. sx={{ width: 300}}
  701. />
  702. )}
  703. />
  704. <TextField
  705. variant="outlined"
  706. label={t("Print Qty")}
  707. defaultValue={printQty}
  708. onChange={(event) => {
  709. event.target.value = event.target.value.replace(/[^0-9]/g, '')
  710. setPrintQty(Number(event.target.value))
  711. }}
  712. sx={{ width: 300}}
  713. />
  714. <Button
  715. id="printButton"
  716. type="button"
  717. variant="contained"
  718. color="primary"
  719. sx={{ mt: 1 }}
  720. onClick={handlePrint}
  721. disabled={isPrinting || printerCombo.length <= 0 || pafSubmitDisable}
  722. >
  723. {isPrinting ? t("Printing") : t("print")}
  724. </Button>
  725. <Button
  726. id="demoPrint"
  727. type="button"
  728. variant="contained"
  729. color="primary"
  730. sx={{ mt: 1 }}
  731. onClick={printQrcode}
  732. disabled={isPrinting}
  733. >
  734. {isPrinting ? t("downloading") : t("download Qr Code")}
  735. </Button>
  736. </Stack>
  737. )}
  738. </>) : <LoadingComponent/>}
  739. </Box>
  740. </Modal>
  741. </FormProvider>
  742. </>
  743. );
  744. };
  745. export default QcStockInModal;