| @@ -38,15 +38,17 @@ export interface SearchJoResult { | |||
| status: JoStatus; | |||
| } | |||
| export interface ReleaseJoRequest { | |||
| // For Jo Button Actions | |||
| export interface CommonActionJoRequest { | |||
| id: number; | |||
| } | |||
| export interface ReleaseJoResponse { | |||
| export interface CommonActionJoResponse { | |||
| id: number; | |||
| entity: { status: JoStatus } | |||
| } | |||
| // For Jo Process | |||
| export interface IsOperatorExistResponse<T> { | |||
| id: number | null; | |||
| name: string; | |||
| @@ -251,8 +253,17 @@ export const fetchJos = cache(async (data?: SearchJoResultRequest) => { | |||
| return response | |||
| }) | |||
| export const releaseJo = cache(async (data: ReleaseJoRequest) => { | |||
| return serverFetchJson<ReleaseJoResponse>(`${BASE_API_URL}/jo/release`, | |||
| export const releaseJo = cache(async (data: CommonActionJoRequest) => { | |||
| return serverFetchJson<CommonActionJoResponse>(`${BASE_API_URL}/jo/release`, | |||
| { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }) | |||
| }) | |||
| export const startJo = cache(async (data: CommonActionJoRequest) => { | |||
| return serverFetchJson<CommonActionJoResponse>(`${BASE_API_URL}/jo/start`, | |||
| { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| @@ -261,7 +272,6 @@ export const releaseJo = cache(async (data: ReleaseJoRequest) => { | |||
| }) | |||
| export const manualCreateJo = cache(async (data: SaveJo) => { | |||
| console.log(data) | |||
| return serverFetchJson<SaveJoResponse>(`${BASE_API_URL}/jo/manualCreate`, { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| @@ -14,6 +14,7 @@ type Props = { | |||
| interface ErrorEntry { | |||
| qtyErr: boolean; | |||
| scanErr: boolean; | |||
| pickErr: boolean; | |||
| } | |||
| const ActionButtons: React.FC<Props> = ({ | |||
| @@ -36,24 +37,31 @@ const ActionButtons: React.FC<Props> = ({ | |||
| const errors: ErrorEntry = useMemo(() => { | |||
| let qtyErr = false; | |||
| let scanErr = false; | |||
| let pickErr = false | |||
| pickLines.forEach((line) => { | |||
| if (!qtyErr) { | |||
| const pickedQty = line.pickedLotNo?.reduce((acc, cur) => acc + cur.qty, 0) ?? 0 | |||
| qtyErr = pickedQty > 0 && pickedQty >= line.reqQty | |||
| qtyErr = pickedQty <= 0 || pickedQty < line.reqQty | |||
| } | |||
| if (!scanErr) { | |||
| scanErr = line.pickedLotNo?.some((lotNo) => Boolean(lotNo.isScanned) === false) ?? false // default false | |||
| } | |||
| if (!pickErr) { | |||
| pickErr = line.pickedLotNo === null | |||
| } | |||
| }) | |||
| return { | |||
| qtyErr: qtyErr, | |||
| scanErr: scanErr | |||
| scanErr: scanErr, | |||
| pickErr: pickErr | |||
| } | |||
| }, [pickLines]) | |||
| }, [pickLines, status]) | |||
| console.log(pickLines) | |||
| return ( | |||
| <Stack direction="row" justifyContent="flex-start" gap={1}> | |||
| {status === "planning" && ( | |||
| @@ -71,12 +79,13 @@ const ActionButtons: React.FC<Props> = ({ | |||
| variant="outlined" | |||
| startIcon={<PlayCircleFilledWhiteIcon />} | |||
| onClick={handleStart} | |||
| disabled={errors.qtyErr || errors.scanErr} | |||
| disabled={errors.qtyErr || errors.scanErr || errors.pickErr} | |||
| > | |||
| {t("Start Job Order")} | |||
| </Button> | |||
| {errors.scanErr && (<Typography variant="h3" color="error">{t("Please scan the item qr code.")}</Typography>)} | |||
| {errors.qtyErr && (<Typography variant="h3" color="error">{t("Please make sure the qty is enough.")}</Typography>)} | |||
| {errors.pickErr && (<Typography variant="h3" color="error">{t("Please make sure all required items are picked")}</Typography>)} | |||
| {errors.scanErr && (<Typography variant="h3" color="error">{t("Please scan the item qr code")}</Typography>)} | |||
| {errors.qtyErr && (<Typography variant="h3" color="error">{t("Please make sure the qty is enough")}</Typography>)} | |||
| </Box> | |||
| )} | |||
| </Stack> | |||
| @@ -8,12 +8,13 @@ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useState } fro | |||
| import { Button, Stack, Typography } from "@mui/material"; | |||
| import StartIcon from "@mui/icons-material/Start"; | |||
| import ArrowBackIcon from '@mui/icons-material/ArrowBack'; | |||
| import { releaseJo } from "@/app/api/jo/actions"; | |||
| import { releaseJo, startJo } from "@/app/api/jo/actions"; | |||
| import InfoCard from "./InfoCard"; | |||
| import PickTable from "./PickTable"; | |||
| import ActionButtons from "./ActionButtons"; | |||
| import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; | |||
| import { fetchStockInLineInfo } from "@/app/api/po/actions"; | |||
| import { submitDialog } from "../Swal/CustomAlerts"; | |||
| type Props = { | |||
| id?: number; | |||
| @@ -93,12 +94,18 @@ const JoSave: React.FC<Props> = ({ | |||
| shouldValidate: true, | |||
| shouldDirty: true, | |||
| }); | |||
| // Ask user and confirm to start JO | |||
| await submitDialog(() => handleStart(), t, { | |||
| title: t("Do you want to start job order"), | |||
| confirmButtonText: t("Start Job Order") | |||
| }) | |||
| } | |||
| } | |||
| } | |||
| } finally { | |||
| scanner.resetScan() | |||
| setIsUploading(false) | |||
| scanner.resetScan() | |||
| setIsUploading(false) | |||
| } | |||
| }, []) | |||
| @@ -126,7 +133,6 @@ const JoSave: React.FC<Props> = ({ | |||
| formProps.setValue("status", response.entity.status) | |||
| } | |||
| } | |||
| } catch (e) { | |||
| // backend error | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| @@ -137,7 +143,25 @@ const JoSave: React.FC<Props> = ({ | |||
| }, []) | |||
| const handleStart = useCallback(async () => { | |||
| console.log("first") | |||
| try { | |||
| setIsUploading(true) | |||
| if (id) { | |||
| const response = await startJo({ id: id }) | |||
| if (response) { | |||
| formProps.setValue("status", response.entity.status) | |||
| pickLines.map((line) => ({...line, status: "completed"})) | |||
| handleBack() | |||
| } | |||
| } | |||
| } catch (e) { | |||
| // backend error | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| console.log(e); | |||
| } finally { | |||
| setIsUploading(false) | |||
| } | |||
| }, []) | |||
| // --------------------------------------------- Form Submit --------------------------------------------- // | |||
| @@ -53,8 +53,8 @@ const PickTable: React.FC<Props> = ({ | |||
| if (params.row.pickedLotNo === null || params.row.pickedLotNo === undefined) { | |||
| return notPickedStatusColumn | |||
| } | |||
| const scanStatus = params.row.pickedLotNo.map((pln) => Boolean(pln.isScanned)) | |||
| return isEmpty(scanStatus) ? notPickedStatusColumn : <Stack direction={"column"}>{scanStatus.map((status) => scanStatusColumn(status))}</Stack> | |||
| const scanStatus = params.row.pickedLotNo.map((pln) => params.row.status === "completed" ? true : Boolean(pln.isScanned)) | |||
| return isEmpty(scanStatus) ? notPickedStatusColumn : <Stack key={`${params.id}-scan`} direction={"column"}>{scanStatus.map((status) => scanStatusColumn(status))}</Stack> | |||
| }, | |||
| }, | |||
| { | |||
| @@ -107,10 +107,11 @@ const PickTable: React.FC<Props> = ({ | |||
| align: "right", | |||
| headerAlign: "right", | |||
| renderCell: (params: GridRenderCellParams<JoDetailPickLine>) => { | |||
| const status = Boolean(params.row.pickedLotNo?.every((lotNo) => Boolean(lotNo.isScanned))) || params.row.status === "completed" | |||
| return ( | |||
| <> | |||
| {params.row.pickedLotNo?.every((lotNo) => Boolean(lotNo.isScanned)) ? t("Scanned") : t(upperFirst(params.value))} | |||
| {scanStatusColumn(Boolean(params.row.pickedLotNo?.every((lotNo) => Boolean(lotNo.isScanned))))} | |||
| {scanStatusColumn(status)} | |||
| </> | |||
| ) | |||
| }, | |||
| @@ -16,6 +16,11 @@ | |||
| "Pending for pick": "待提料", | |||
| "Planning": "計劃中", | |||
| "Scanned": "已掃碼", | |||
| "Processing": "已開始工序", | |||
| "Storing": "入倉中", | |||
| "completed": "已完成", | |||
| "Completed": "已完成", | |||
| "Cancel": "取消", | |||
| "Scan Status": "掃碼狀態", | |||
| "Start Job Order": "開始工單", | |||
| "Job Order Pickexcution": "工單提料", | |||
| @@ -30,7 +35,6 @@ | |||
| "Location": "位置", | |||
| "Scan Result": "掃碼結果", | |||
| "Expiry Date": "有效期", | |||
| "Pick Order Code": "提料單編號", | |||
| "Target Date": "需求日期", | |||
| "Lot Required Pick Qty": "批號需求數量", | |||
| "Job Order Match": "工單對料", | |||
| @@ -64,5 +68,9 @@ | |||
| "Second Scan Status": "對料狀態", | |||
| "Job Order Pick Order Details": "工單提料單詳情", | |||
| "Scanning...": "掃碼中", | |||
| "Unassigned Job Orders": "未分配工單" | |||
| "Unassigned Job Orders": "未分配工單", | |||
| "Please scan the item qr code": "請掃描物料二維碼", | |||
| "Please make sure the qty is enough": "請確保物料數量是足夠", | |||
| "Please make sure all required items are picked": "請確保所有物料已被提取", | |||
| "Do you want to start job order": "是否開始工單" | |||
| } | |||