| @@ -26,7 +26,7 @@ const jo: React.FC = async () => { | |||||
| {t("Job Order")} | {t("Job Order")} | ||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| <I18nProvider namespaces={["jo", "common"]}> | |||||
| <I18nProvider namespaces={["jo", "common", "purchaseOrder", "dashboard"]}> {/* TODO: Improve */} | |||||
| <Suspense fallback={<JoSearch.Loading />}> | <Suspense fallback={<JoSearch.Loading />}> | ||||
| <JoSearch /> | <JoSearch /> | ||||
| </Suspense> | </Suspense> | ||||
| @@ -2,7 +2,7 @@ | |||||
| import { cache } from 'react'; | import { cache } from 'react'; | ||||
| import { Pageable, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | import { Pageable, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | ||||
| import { JoStatus, Machine, Operator } from "."; | |||||
| import { JobOrder, JoStatus, Machine, Operator } from "."; | |||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| import { revalidateTag } from "next/cache"; | import { revalidateTag } from "next/cache"; | ||||
| import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | ||||
| @@ -21,14 +21,15 @@ export interface SaveJoResponse { | |||||
| export interface SearchJoResultRequest extends Pageable { | export interface SearchJoResultRequest extends Pageable { | ||||
| code: string; | code: string; | ||||
| name: string; | |||||
| itemName?: string; | |||||
| } | } | ||||
| export interface SearchJoResultResponse { | export interface SearchJoResultResponse { | ||||
| records: SearchJoResult[]; | |||||
| records: JobOrder[]; | |||||
| total: number; | total: number; | ||||
| } | } | ||||
| // DEPRECIATED | |||||
| export interface SearchJoResult { | export interface SearchJoResult { | ||||
| id: number; | id: number; | ||||
| code: string; | code: string; | ||||
| @@ -39,6 +40,11 @@ export interface SearchJoResult { | |||||
| status: JoStatus; | status: JoStatus; | ||||
| } | } | ||||
| export interface UpdateJoRequest { | |||||
| id: number; | |||||
| status: string; | |||||
| } | |||||
| // For Jo Button Actions | // For Jo Button Actions | ||||
| export interface CommonActionJoRequest { | export interface CommonActionJoRequest { | ||||
| id: number; | id: number; | ||||
| @@ -79,6 +85,7 @@ export interface JobOrderDetail { | |||||
| pickLines: any[]; | pickLines: any[]; | ||||
| status: string; | status: string; | ||||
| } | } | ||||
| export interface UnassignedJobOrderPickOrder { | export interface UnassignedJobOrderPickOrder { | ||||
| pickOrderId: number; | pickOrderId: number; | ||||
| pickOrderCode: string; | pickOrderCode: string; | ||||
| @@ -223,6 +230,7 @@ export const fetchCompletedJobOrderPickOrderRecords = cache(async (userId: numbe | |||||
| }, | }, | ||||
| ); | ); | ||||
| }); | }); | ||||
| export const fetchJobOrderDetailByCode = cache(async (code: string) => { | export const fetchJobOrderDetailByCode = cache(async (code: string) => { | ||||
| return serverFetchJson<JobOrderDetail>( | return serverFetchJson<JobOrderDetail>( | ||||
| `${BASE_API_URL}/jo/detailByCode/${code}`, | `${BASE_API_URL}/jo/detailByCode/${code}`, | ||||
| @@ -275,6 +283,15 @@ export const fetchJos = cache(async (data?: SearchJoResultRequest) => { | |||||
| return response | return response | ||||
| }) | }) | ||||
| export const updateJo = cache(async (data: UpdateJoRequest) => { | |||||
| return serverFetchJson<SaveJoResponse>(`${BASE_API_URL}/jo/update`, | |||||
| { | |||||
| method: "POST", | |||||
| body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }) | |||||
| }) | |||||
| export const releaseJo = cache(async (data: CommonActionJoRequest) => { | export const releaseJo = cache(async (data: CommonActionJoRequest) => { | ||||
| const response = serverFetchJson<CommonActionJoResponse>(`${BASE_API_URL}/jo/release`, | const response = serverFetchJson<CommonActionJoResponse>(`${BASE_API_URL}/jo/release`, | ||||
| { | { | ||||
| @@ -3,6 +3,8 @@ | |||||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | import { serverFetchJson } from "@/app/utils/fetchUtil"; | ||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| import { cache } from "react"; | import { cache } from "react"; | ||||
| import { Item } from "../settings/item"; | |||||
| import { Uom } from "../settings/uom"; | |||||
| export type JoStatus = "planning" | "pending" | "processing" | "packaging" | "storing" | "completed" | export type JoStatus = "planning" | "pending" | "processing" | "packaging" | "storing" | "completed" | ||||
| export type JoBomMaterialStatus = "pending" | "completed" | export type JoBomMaterialStatus = "pending" | "completed" | ||||
| @@ -13,6 +15,24 @@ export interface Operator { | |||||
| username: string; | username: string; | ||||
| } | } | ||||
| export interface JobOrder { | |||||
| id: number; | |||||
| code: string; | |||||
| reqQty: number; | |||||
| item: Item; | |||||
| itemName: string; | |||||
| // uom: Uom; | |||||
| pickLines?: JoDetailPickLine[]; | |||||
| status: JoStatus; | |||||
| planStart?: number[]; | |||||
| planEnd?: number[]; | |||||
| type: string; | |||||
| // TODO pack below into StockInLineInfo | |||||
| stockInLineId?: number; | |||||
| stockInLineStatus?: string; | |||||
| silHandlerId?: number; | |||||
| } | |||||
| export interface Machine { | export interface Machine { | ||||
| id: number; | id: number; | ||||
| name: string; | name: string; | ||||
| @@ -24,14 +44,17 @@ export interface JoDetail { | |||||
| id: number; | id: number; | ||||
| code: string; | code: string; | ||||
| itemCode: string; | itemCode: string; | ||||
| itemName?: string; | |||||
| name: string; | name: string; | ||||
| reqQty: number; | reqQty: number; | ||||
| // itemId: number; | |||||
| uom: string; | uom: string; | ||||
| pickLines: JoDetailPickLine[]; | pickLines: JoDetailPickLine[]; | ||||
| status: JoStatus; | status: JoStatus; | ||||
| planStart: number[]; | planStart: number[]; | ||||
| planEnd: number[]; | planEnd: number[]; | ||||
| type: string; | type: string; | ||||
| // item?: Item; | |||||
| } | } | ||||
| export interface JoDetailPickLine { | export interface JoDetailPickLine { | ||||
| @@ -52,7 +75,7 @@ export interface JoDetailPickedLotNo { | |||||
| } | } | ||||
| export const fetchJoDetail = cache(async (id: number) => { | export const fetchJoDetail = cache(async (id: number) => { | ||||
| return serverFetchJson<JoDetail>(`${BASE_API_URL}/jo/detail/${id}`, | |||||
| return serverFetchJson<JobOrder>(`${BASE_API_URL}/jo/detail/${id}`, | |||||
| { | { | ||||
| method: "GET", | method: "GET", | ||||
| headers: { "Content-Type": "application/json"}, | headers: { "Content-Type": "application/json"}, | ||||
| @@ -5,6 +5,7 @@ import "server-only"; | |||||
| import { serverFetchJson } from "../../../utils/fetchUtil"; | import { serverFetchJson } from "../../../utils/fetchUtil"; | ||||
| import { BASE_API_URL } from "../../../../config/api"; | import { BASE_API_URL } from "../../../../config/api"; | ||||
| import { QcCategoryResult } from "../qcCategory"; | import { QcCategoryResult } from "../qcCategory"; | ||||
| import { Uom } from "../uom"; | |||||
| // import { TypeInputs, UomInputs, WeightUnitInputs } from "./actions"; | // import { TypeInputs, UomInputs, WeightUnitInputs } from "./actions"; | ||||
| @@ -24,6 +25,19 @@ export type ItemsResultResponse = { | |||||
| total: number; | total: number; | ||||
| }; | }; | ||||
| export type Item = { | |||||
| id: number; | |||||
| code: string; | |||||
| name: string; | |||||
| description?: string; | |||||
| remarks?: string; | |||||
| type?: string; | |||||
| shelfLife?: number; | |||||
| countryOfOrigin?: string; | |||||
| qcCategory?: QcCategoryResult; | |||||
| uom: Uom; | |||||
| } | |||||
| export type ItemsResult = { | export type ItemsResult = { | ||||
| id: string | number; | id: string | number; | ||||
| code: string; | code: string; | ||||
| @@ -30,6 +30,7 @@ export interface StockInLineEntry { | |||||
| acceptedQty: number; | acceptedQty: number; | ||||
| purchaseOrderId?: number; | purchaseOrderId?: number; | ||||
| purchaseOrderLineId?: number; | purchaseOrderLineId?: number; | ||||
| jobOrderId?: number; | |||||
| status?: string; | status?: string; | ||||
| expiryDate?: string; | expiryDate?: string; | ||||
| productLotNo?: string; | productLotNo?: string; | ||||
| @@ -101,6 +101,7 @@ export interface StockInLine { | |||||
| stockInId?: number; | stockInId?: number; | ||||
| purchaseOrderId?: number; | purchaseOrderId?: number; | ||||
| purchaseOrderLineId: number; | purchaseOrderLineId: number; | ||||
| jobOrderId: number; | |||||
| itemId: number; | itemId: number; | ||||
| itemNo: string; | itemNo: string; | ||||
| itemName: string; | itemName: string; | ||||
| @@ -40,7 +40,11 @@ const EscalationLogTable: React.FC<Props> = ({ | |||||
| const [escalationLogs, setEscalationLogs] = useState<EscalationResult[]>([]); | const [escalationLogs, setEscalationLogs] = useState<EscalationResult[]>([]); | ||||
| const useCardFilter = useContext(CardFilterContext); | const useCardFilter = useContext(CardFilterContext); | ||||
| const showCompleted = useCardFilter.filter; | |||||
| const showCompleted = useMemo(() => { | |||||
| if (type === "dashboard") { | |||||
| return useCardFilter.filter; | |||||
| } else { return true} | |||||
| }, [useCardFilter.filter]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (showCompleted) { | if (showCompleted) { | ||||
| @@ -11,6 +11,7 @@ import { isFinite } from "lodash"; | |||||
| import React, { SetStateAction, SyntheticEvent, useCallback, useEffect } from "react"; | import React, { SetStateAction, SyntheticEvent, useCallback, useEffect } from "react"; | ||||
| import { Controller, FormProvider, SubmitErrorHandler, SubmitHandler, useForm, useFormContext } from "react-hook-form"; | import { Controller, FormProvider, SubmitErrorHandler, SubmitHandler, useForm, useFormContext } from "react-hook-form"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { msg } from "../Swal/CustomAlerts"; | |||||
| interface Props { | interface Props { | ||||
| open: boolean; | open: boolean; | ||||
| @@ -54,6 +55,7 @@ const JoCreateFormModal: React.FC<Props> = ({ | |||||
| const response = await manualCreateJo(data) | const response = await manualCreateJo(data) | ||||
| if (response) { | if (response) { | ||||
| onSearch(); | onSearch(); | ||||
| msg(t("update success")); | |||||
| onModalClose(); | onModalClose(); | ||||
| } | } | ||||
| }, []) | }, []) | ||||
| @@ -1,34 +1,41 @@ | |||||
| "use client" | "use client" | ||||
| import { SearchJoResult, SearchJoResultRequest, fetchJos } from "@/app/api/jo/actions"; | |||||
| import { SearchJoResultRequest, fetchJos, updateJo } from "@/app/api/jo/actions"; | |||||
| import React, { useCallback, useEffect, useMemo, useState } from "react"; | import React, { useCallback, useEffect, useMemo, useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { Criterion } from "../SearchBox"; | import { Criterion } from "../SearchBox"; | ||||
| import SearchResults, { Column, defaultPagingController } from "../SearchResults/SearchResults"; | import SearchResults, { Column, defaultPagingController } from "../SearchResults/SearchResults"; | ||||
| import { EditNote } from "@mui/icons-material"; | import { EditNote } from "@mui/icons-material"; | ||||
| import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; | |||||
| import { arrayToDateString, integerFormatter } from "@/app/utils/formatUtil"; | |||||
| import { orderBy, uniqBy, upperFirst } from "lodash"; | import { orderBy, uniqBy, upperFirst } from "lodash"; | ||||
| import SearchBox from "../SearchBox/SearchBox"; | import SearchBox from "../SearchBox/SearchBox"; | ||||
| import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||
| import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form"; | import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form"; | ||||
| import { JoDetail } from "@/app/api/jo"; | |||||
| import { StockInLineInput } from "@/app/api/stockIn"; | |||||
| import { JobOrder, JoStatus } from "@/app/api/jo"; | |||||
| import { Button, Stack } from "@mui/material"; | import { Button, Stack } from "@mui/material"; | ||||
| import { BomCombo } from "@/app/api/bom"; | import { BomCombo } from "@/app/api/bom"; | ||||
| import JoCreateFormModal from "./JoCreateFormModal"; | import JoCreateFormModal from "./JoCreateFormModal"; | ||||
| import AddIcon from '@mui/icons-material/Add'; | import AddIcon from '@mui/icons-material/Add'; | ||||
| import QcStockInModal from "../PoDetail/QcStockInModal"; | |||||
| import { useSession } from "next-auth/react"; | |||||
| import { SessionWithTokens } from "@/config/authConfig"; | |||||
| import { createStockInLine } from "@/app/api/stockIn/actions"; | |||||
| import { msg } from "../Swal/CustomAlerts"; | |||||
| import dayjs from "dayjs"; | |||||
| interface Props { | interface Props { | ||||
| defaultInputs: SearchJoResultRequest, | defaultInputs: SearchJoResultRequest, | ||||
| bomCombo: BomCombo[] | bomCombo: BomCombo[] | ||||
| } | } | ||||
| type SearchQuery = Partial<Omit<SearchJoResult, "id">>; | |||||
| type SearchQuery = Partial<Omit<JobOrder, "id">>; | |||||
| type SearchParamNames = keyof SearchQuery; | type SearchParamNames = keyof SearchQuery; | ||||
| const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | ||||
| const { t } = useTranslation("jo"); | const { t } = useTranslation("jo"); | ||||
| const router = useRouter() | const router = useRouter() | ||||
| const [filteredJos, setFilteredJos] = useState<SearchJoResult[]>([]); | |||||
| const [filteredJos, setFilteredJos] = useState<JobOrder[]>([]); | |||||
| const [inputs, setInputs] = useState(defaultInputs); | const [inputs, setInputs] = useState(defaultInputs); | ||||
| const [pagingController, setPagingController] = useState( | const [pagingController, setPagingController] = useState( | ||||
| defaultPagingController | defaultPagingController | ||||
| @@ -38,28 +45,28 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [ | const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [ | ||||
| { label: t("Code"), paramName: "code", type: "text" }, | { label: t("Code"), paramName: "code", type: "text" }, | ||||
| { label: t("Item Name"), paramName: "name", type: "text" }, | |||||
| { label: t("Item Name"), paramName: "itemName", type: "text" }, | |||||
| ], [t]) | ], [t]) | ||||
| const columns = useMemo<Column<SearchJoResult>[]>( | |||||
| const columns = useMemo<Column<JobOrder>[]>( | |||||
| () => [ | () => [ | ||||
| { | |||||
| name: "id", | |||||
| label: t("Details"), | |||||
| onClick: (record) => onDetailClick(record), | |||||
| buttonIcon: <EditNote />, | |||||
| }, | |||||
| { | { | ||||
| name: "code", | name: "code", | ||||
| label: t("Code") | label: t("Code") | ||||
| }, | }, | ||||
| { | { | ||||
| name: "itemCode", | |||||
| label: t("Item Code") | |||||
| name: "item", | |||||
| label: t("Item Code"), | |||||
| renderCell: (row) => { | |||||
| return t(row.item.code) | |||||
| } | |||||
| }, | }, | ||||
| { | { | ||||
| name: "name", | |||||
| name: "itemName", | |||||
| label: t("Item Name"), | label: t("Item Name"), | ||||
| renderCell: (row) => { | |||||
| return t(row.item.name) | |||||
| } | |||||
| }, | }, | ||||
| { | { | ||||
| name: "reqQty", | name: "reqQty", | ||||
| @@ -71,12 +78,12 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| } | } | ||||
| }, | }, | ||||
| { | { | ||||
| name: "uom", | |||||
| name: "item", | |||||
| label: t("UoM"), | label: t("UoM"), | ||||
| align: "left", | align: "left", | ||||
| headerAlign: "left", | headerAlign: "left", | ||||
| renderCell: (row) => { | renderCell: (row) => { | ||||
| return t(row.uom) | |||||
| return t(row.item.uom.udfudesc) | |||||
| } | } | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -85,17 +92,69 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| renderCell: (row) => { | renderCell: (row) => { | ||||
| return t(upperFirst(row.status)) | return t(upperFirst(row.status)) | ||||
| } | } | ||||
| } | |||||
| }, | |||||
| { | |||||
| // TODO put it inside Action Buttons | |||||
| name: "id", | |||||
| label: t("Actions"), | |||||
| // onClick: (record) => onDetailClick(record), | |||||
| // buttonIcon: <EditNote />, | |||||
| renderCell: (row) => { | |||||
| const btnSx = getButtonSx(row); | |||||
| return ( | |||||
| <Button | |||||
| id="emailSupplier" | |||||
| type="button" | |||||
| variant="contained" | |||||
| color="primary" | |||||
| sx={{ width: "150px", backgroundColor: btnSx.color }} | |||||
| // disabled={params.row.status != "rejected" && params.row.status != "partially_completed"} | |||||
| onClick={() => onDetailClick(row)} | |||||
| >{btnSx.label}</Button> | |||||
| ) | |||||
| } | |||||
| }, | |||||
| ], [] | ], [] | ||||
| ) | ) | ||||
| const handleUpdate = useCallback(async (jo: JobOrder) => { | |||||
| console.log(jo); | |||||
| try { | |||||
| // setIsUploading(true) | |||||
| if (jo.id) { | |||||
| const response = await updateJo({ id: jo.id, status: "storing" }); | |||||
| console.log(`%c Updated JO:`, "color:lime", response); | |||||
| const postData = { | |||||
| itemId: jo?.item?.id!!, | |||||
| acceptedQty: jo?.reqQty ?? 1, | |||||
| productLotNo: jo?.code, | |||||
| productionDate: arrayToDateString(dayjs(), "input"), | |||||
| jobOrderId: jo?.id, | |||||
| // acceptedQty: secondReceiveQty || 0, | |||||
| // acceptedQty: row.acceptedQty, | |||||
| }; | |||||
| const res = await createStockInLine(postData); | |||||
| console.log(`%c Created Stock In Line`, "color:lime", res); | |||||
| msg(t("update success")); | |||||
| refetchData(defaultInputs, "search"); | |||||
| } | |||||
| } catch (e) { | |||||
| // backend error | |||||
| // setServerError(t("An error has occurred. Please try again later.")); | |||||
| console.log(e); | |||||
| } finally { | |||||
| // setIsUploading(false) | |||||
| } | |||||
| }, []) | |||||
| const refetchData = useCallback(async ( | const refetchData = useCallback(async ( | ||||
| query: Record<SearchParamNames, string> | SearchJoResultRequest, | query: Record<SearchParamNames, string> | SearchJoResultRequest, | ||||
| actionType: "reset" | "search" | "paging", | actionType: "reset" | "search" | "paging", | ||||
| ) => { | ) => { | ||||
| const params: SearchJoResultRequest = { | const params: SearchJoResultRequest = { | ||||
| code: query.code, | code: query.code, | ||||
| name: query.name, | |||||
| itemName: query.itemName, | |||||
| pageNum: pagingController.pageNum - 1, | pageNum: pagingController.pageNum - 1, | ||||
| pageSize: pagingController.pageSize, | pageSize: pagingController.pageSize, | ||||
| } | } | ||||
| @@ -124,14 +183,69 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| searchDataByPage(); | searchDataByPage(); | ||||
| }, [pagingController]); | }, [pagingController]); | ||||
| const onDetailClick = useCallback((record: SearchJoResult) => { | |||||
| router.push(`/jo/edit?id=${record.id}`) | |||||
| const getButtonSx = (jo : JobOrder) => { // TODO put it in ActionButtons.ts | |||||
| const joStatus = jo.status?.toLowerCase(); | |||||
| const silStatus = jo.stockInLineStatus?.toLowerCase(); | |||||
| let btnSx = {label:"", color:""}; | |||||
| switch (joStatus) { | |||||
| case "planning": btnSx = {label: t("release jo"), color:"primary.main"}; break; | |||||
| case "pending": btnSx = {label: t("scan picked material"), color:"error.main"}; break; | |||||
| case "processing": btnSx = {label: t("complete jo"), color:"warning.main"}; break; | |||||
| // case "packaging": | |||||
| // case "storing": btnSx = {label: t("view putaway"), color:"secondary.main"}; break; | |||||
| case "storing": | |||||
| switch (silStatus) { | |||||
| case "pending": btnSx = {label: t("process epqc"), color:"primary.main"}; break; | |||||
| case "received": btnSx = {label: t("view putaway"), color:"secondary.main"}; break; | |||||
| case "escalated": | |||||
| if (sessionToken?.id == jo.silHandlerId) { | |||||
| btnSx = {label: t("escalation processing"), color:"warning.main"}; | |||||
| break; | |||||
| } | |||||
| default: btnSx = {label: t("view stockin"), color:"info.main"}; | |||||
| } | |||||
| break; | |||||
| case "completed": btnSx = {label: t("view stockin"), color:"info.main"}; break; | |||||
| default: btnSx = {label: t("scan picked material"), color:"success.main"}; | |||||
| } | |||||
| return btnSx | |||||
| }; | |||||
| const { data: session } = useSession(); | |||||
| const sessionToken = session as SessionWithTokens | null; | |||||
| const [openModal, setOpenModal] = useState<boolean>(false); | |||||
| const [modalInfo, setModalInfo] = useState<StockInLineInput>(); | |||||
| const onDetailClick = useCallback((record: JobOrder) => { | |||||
| if (record.status == "processing") { | |||||
| handleUpdate(record) | |||||
| } else if (record.status == "storing" || record.status == "completed") { | |||||
| if (record.stockInLineId != null) { | |||||
| const data = { | |||||
| id: record.stockInLineId, | |||||
| } | |||||
| setModalInfo(data); | |||||
| setOpenModal(true); | |||||
| } else { alert('Invalid Stock In Line Id'); } | |||||
| } else { | |||||
| router.push(`/jo/edit?id=${record.id}`) | |||||
| } | |||||
| }, []) | }, []) | ||||
| const closeNewModal = useCallback(() => { | |||||
| // const response = updateJo({ id: 1, status: "storing" }); | |||||
| setOpenModal(false); // Close the modal first | |||||
| // setTimeout(() => { | |||||
| // }, 300); // Add a delay to avoid immediate re-trigger of useEffect | |||||
| refetchData(defaultInputs, "search"); | |||||
| }, []); | |||||
| const onSearch = useCallback((query: Record<SearchParamNames, string>) => { | const onSearch = useCallback((query: Record<SearchParamNames, string>) => { | ||||
| setInputs(() => ({ | setInputs(() => ({ | ||||
| code: query.code, | code: query.code, | ||||
| name: query.name | |||||
| itemName: query.itemName | |||||
| })) | })) | ||||
| refetchData(query, "search"); | refetchData(query, "search"); | ||||
| }, []) | }, []) | ||||
| @@ -170,7 +284,7 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| onSearch={onSearch} | onSearch={onSearch} | ||||
| onReset={onReset} | onReset={onReset} | ||||
| /> | /> | ||||
| <SearchResults<SearchJoResult> | |||||
| <SearchResults<JobOrder> | |||||
| items={filteredJos} | items={filteredJos} | ||||
| columns={columns} | columns={columns} | ||||
| setPagingController={setPagingController} | setPagingController={setPagingController} | ||||
| @@ -184,6 +298,15 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| onClose={onCloseCreateJoModal} | onClose={onCloseCreateJoModal} | ||||
| onSearch={searchDataByPage} | onSearch={searchDataByPage} | ||||
| /> | /> | ||||
| <QcStockInModal | |||||
| session={sessionToken} | |||||
| open={openModal} | |||||
| onClose={closeNewModal} | |||||
| inputDetail={modalInfo} | |||||
| printerCombo={[]} | |||||
| // skipQc={true} | |||||
| /> | |||||
| </> | </> | ||||
| } | } | ||||
| @@ -392,9 +392,9 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse=[], disabled, setR | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={3}> | <Grid item xs={3}> | ||||
| <TextField | <TextField | ||||
| label={t("acceptedQty")} | |||||
| label={t("acceptedPutawayQty")} // TODO: fix it back to acceptedQty after db is fixed | |||||
| fullWidth | fullWidth | ||||
| value={itemDetail.acceptedQty} | |||||
| value={itemDetail.demandQty ?? itemDetail.acceptedQty} | |||||
| disabled | disabled | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| @@ -104,6 +104,11 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| const [qcHistory, setQcHistory] = useState<PurchaseQcResult[]>([]); | const [qcHistory, setQcHistory] = useState<PurchaseQcResult[]>([]); | ||||
| const [qcResult, setQcResult] = useState<PurchaseQcResult[]>([]); | const [qcResult, setQcResult] = useState<PurchaseQcResult[]>([]); | ||||
| const detailMode = useMemo(() => { | |||||
| const isDetailMode = itemDetail.status == "escalated" || isNaN(itemDetail.jobOrderId); | |||||
| return isDetailMode; | |||||
| }, [itemDetail]); | |||||
| // const [qcAccept, setQcAccept] = useState(true); | // const [qcAccept, setQcAccept] = useState(true); | ||||
| // const [qcItems, setQcItems] = useState(dummyQCData) | // const [qcItems, setQcItems] = useState(dummyQCData) | ||||
| @@ -610,7 +615,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| <FormControlLabel disabled={disabled} | <FormControlLabel disabled={disabled} | ||||
| value="1" control={<Radio />} label="接受來貨" /> | value="1" control={<Radio />} label="接受來貨" /> | ||||
| {(itemDetail.status == "escalated"|| (disabled && accQty != itemDetail.acceptedQty && qcDecision == 1)) && ( //TODO Improve | |||||
| {(detailMode || (disabled && accQty != itemDetail.acceptedQty && qcDecision == 1)) && ( //TODO Improve | |||||
| <Box sx={{mr:2}}> | <Box sx={{mr:2}}> | ||||
| <TextField | <TextField | ||||
| // type="number" | // type="number" | ||||
| @@ -661,7 +666,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| label={t("rejectQty")} | label={t("rejectQty")} | ||||
| sx={{ width: '150px' }} | sx={{ width: '150px' }} | ||||
| value={ | value={ | ||||
| (!Boolean(errors.acceptQty)) ? | |||||
| (!Boolean(errors.acceptQty) && qcDecision !== undefined) ? | |||||
| (qcDecision == 1 ? itemDetail.acceptedQty - accQty : itemDetail.acceptedQty) | (qcDecision == 1 ? itemDetail.acceptedQty - accQty : itemDetail.acceptedQty) | ||||
| : "" | : "" | ||||
| } | } | ||||
| @@ -673,7 +678,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| <FormControlLabel disabled={disabled} | <FormControlLabel disabled={disabled} | ||||
| value="2" control={<Radio />} | value="2" control={<Radio />} | ||||
| sx={{"& .Mui-checked": {color: "red"}}} | sx={{"& .Mui-checked": {color: "red"}}} | ||||
| label= {itemDetail.status == "escalated" ? "全部拒絕並退貨" : "不接受並退貨"} /> | |||||
| label= {detailMode ? "全部拒絕並退貨" : "不接受並退貨"} /> | |||||
| {(itemDetail.status == "pending" || disabled) && (<> | {(itemDetail.status == "pending" || disabled) && (<> | ||||
| <FormControlLabel disabled={disabled} | <FormControlLabel disabled={disabled} | ||||
| @@ -42,6 +42,7 @@ import { fetchStockInLineInfo } from "@/app/api/stockIn/actions"; | |||||
| import { fetchQcResult } from "@/app/api/qc/actions"; | import { fetchQcResult } from "@/app/api/qc/actions"; | ||||
| import { fetchEscalationLogsByStockInLines } from "@/app/api/escalation/actions"; | import { fetchEscalationLogsByStockInLines } from "@/app/api/escalation/actions"; | ||||
| import LoadingComponent from "../General/LoadingComponent"; | import LoadingComponent from "../General/LoadingComponent"; | ||||
| import FgStockInForm from "../StockIn/FgStockInForm"; | |||||
| const style = { | const style = { | ||||
| @@ -64,6 +65,7 @@ interface CommonProps extends Omit<ModalProps, "children"> { | |||||
| warehouse?: any[]; | warehouse?: any[]; | ||||
| printerCombo: PrinterCombo[]; | printerCombo: PrinterCombo[]; | ||||
| onClose: () => void; | onClose: () => void; | ||||
| skipQc?: Boolean; | |||||
| } | } | ||||
| interface Props extends CommonProps { | interface Props extends CommonProps { | ||||
| // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | ||||
| @@ -76,6 +78,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| session, | session, | ||||
| warehouse, | warehouse, | ||||
| printerCombo, | printerCombo, | ||||
| skipQc = false, | |||||
| }) => { | }) => { | ||||
| const { | const { | ||||
| t, | t, | ||||
| @@ -84,6 +87,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| const [stockInLineInfo, setStockInLineInfo] = useState<StockInLine>(); | const [stockInLineInfo, setStockInLineInfo] = useState<StockInLine>(); | ||||
| const [isLoading, setIsLoading] = useState<Boolean>(false); | const [isLoading, setIsLoading] = useState<Boolean>(false); | ||||
| // const [skipQc, setSkipQc] = useState<Boolean>(false); | |||||
| // const [viewOnly, setViewOnly] = useState(false); | // const [viewOnly, setViewOnly] = useState(false); | ||||
| // Select Printer | // Select Printer | ||||
| @@ -110,6 +114,8 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| } else throw("Result is undefined"); | } else throw("Result is undefined"); | ||||
| } catch (e) { | } catch (e) { | ||||
| console.log("%c Error when fetching Stock In Line: ", "color:red", e); | console.log("%c Error when fetching Stock In Line: ", "color:red", e); | ||||
| alert("Something went wrong, please retry"); | |||||
| closeHandler({}, "backdropClick"); | |||||
| } | } | ||||
| },[fetchStockInLineInfo, inputDetail] | },[fetchStockInLineInfo, inputDetail] | ||||
| ); | ); | ||||
| @@ -129,6 +135,8 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| // } else throw("Result is undefined"); | // } else throw("Result is undefined"); | ||||
| } catch (e) { | } catch (e) { | ||||
| console.log("%c Error when fetching Qc Result: ", "color:red", e); | console.log("%c Error when fetching Qc Result: ", "color:red", e); | ||||
| // alert("Something went wrong, please retry"); | |||||
| // closeHandler({}, "backdropClick"); | |||||
| } | } | ||||
| },[fetchQcResult] | },[fetchQcResult] | ||||
| ); | ); | ||||
| @@ -145,6 +153,8 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| } else throw("Result is undefined"); | } else throw("Result is undefined"); | ||||
| } catch (e) { | } catch (e) { | ||||
| console.log("%c Error when fetching EscalationLog: ", "color:red", e); | console.log("%c Error when fetching EscalationLog: ", "color:red", e); | ||||
| // alert("Something went wrong, please retry"); | |||||
| // closeHandler({}, "backdropClick"); | |||||
| } | } | ||||
| },[fetchEscalationLogsByStockInLines] | },[fetchEscalationLogsByStockInLines] | ||||
| ); | ); | ||||
| @@ -334,10 +344,10 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| } | } | ||||
| } | } | ||||
| // Check if dates are input | // Check if dates are input | ||||
| if (data.productionDate === undefined || data.productionDate == null) { | |||||
| alert("請輸入生產日期!"); | |||||
| return; | |||||
| } | |||||
| // if (data.productionDate === undefined || data.productionDate == null) { | |||||
| // alert("請輸入生產日期!"); | |||||
| // return; | |||||
| // } | |||||
| if (data.expiryDate === undefined || data.expiryDate == null) { | if (data.expiryDate === undefined || data.expiryDate == null) { | ||||
| alert("請輸入到期日!"); | alert("請輸入到期日!"); | ||||
| return; | return; | ||||
| @@ -357,7 +367,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`); | // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`); | ||||
| } | } | ||||
| if (validationErrors.length > 0) { | |||||
| if (validationErrors.length > 0 && !skipQc) { | |||||
| console.error("QC Validation failed:", validationErrors); | console.error("QC Validation failed:", validationErrors); | ||||
| alert(`未完成品檢: ${validationErrors}`); | alert(`未完成品檢: ${validationErrors}`); | ||||
| return; | return; | ||||
| @@ -531,6 +541,25 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| // return isPassed | // return isPassed | ||||
| // }, [acceptQty, formProps]) | // }, [acceptQty, formProps]) | ||||
| const printQrcode = useCallback( | |||||
| async () => { | |||||
| setIsPrinting(true); | |||||
| try { | |||||
| const postData = { stockInLineIds: [stockInLineInfo?.id] }; | |||||
| const response = await fetchPoQrcode(postData); | |||||
| if (response) { | |||||
| console.log(response); | |||||
| downloadFile(new Uint8Array(response.blobValue), response.filename!); | |||||
| } | |||||
| } catch (e) { | |||||
| console.log("%c Error downloading QR Code", "color:red", e); | |||||
| } finally { | |||||
| setIsPrinting(false); | |||||
| } | |||||
| }, | |||||
| [stockInLineInfo], | |||||
| ); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <FormProvider {...formProps}> | <FormProvider {...formProps}> | ||||
| @@ -577,12 +606,17 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| {t("Delivery Detail")} | {t("Delivery Detail")} | ||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| <StockInForm itemDetail={stockInLineInfo} disabled={viewOnly || showPutaway} /> | |||||
| {stockInLineInfo.qcResult ? | |||||
| {stockInLineInfo.jobOrderId ? ( | |||||
| <FgStockInForm itemDetail={stockInLineInfo} disabled={viewOnly || showPutaway} /> | |||||
| ) : ( | |||||
| <StockInForm itemDetail={stockInLineInfo} disabled={viewOnly || showPutaway} /> | |||||
| ) | |||||
| } | |||||
| {skipQc === false && (stockInLineInfo.qcResult ? | |||||
| <QcComponent | <QcComponent | ||||
| itemDetail={stockInLineInfo} | itemDetail={stockInLineInfo} | ||||
| disabled={viewOnly || showPutaway} | disabled={viewOnly || showPutaway} | ||||
| /> : <LoadingComponent/> | |||||
| /> : <LoadingComponent/>) | |||||
| } | } | ||||
| <Stack direction="row" justifyContent="flex-end" gap={1} sx={{pt:2}}> | <Stack direction="row" justifyContent="flex-end" gap={1} sx={{pt:2}}> | ||||
| {(!viewOnly && !showPutaway) && (<Button | {(!viewOnly && !showPutaway) && (<Button | ||||
| @@ -593,7 +627,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| sx={{ mt: 1 }} | sx={{ mt: 1 }} | ||||
| onClick={formProps.handleSubmit(onSubmitQc, onSubmitErrorQc)} | onClick={formProps.handleSubmit(onSubmitQc, onSubmitErrorQc)} | ||||
| > | > | ||||
| {t("confirm qc result")} | |||||
| {skipQc ? t("confirm") : t("confirm qc result")} | |||||
| </Button>)} | </Button>)} | ||||
| </Stack> | </Stack> | ||||
| </Box> | </Box> | ||||
| @@ -653,6 +687,17 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| > | > | ||||
| {isPrinting ? t("Printing") : t("print")} | {isPrinting ? t("Printing") : t("print")} | ||||
| </Button> | </Button> | ||||
| <Button | |||||
| id="demoPrint" | |||||
| type="button" | |||||
| variant="contained" | |||||
| color="primary" | |||||
| sx={{ mt: 1 }} | |||||
| onClick={printQrcode} | |||||
| disabled={isPrinting} | |||||
| > | |||||
| {isPrinting ? t("downloading") : t("download Qr Code")} | |||||
| </Button> | |||||
| </Stack> | </Stack> | ||||
| )} | )} | ||||
| </>) : <LoadingComponent/>} | </>) : <LoadingComponent/>} | ||||
| @@ -35,6 +35,7 @@ import { arrayToDateString, INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
| import { QrCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider"; | import { QrCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider"; | ||||
| import { msg } from "../Swal/CustomAlerts"; | import { msg } from "../Swal/CustomAlerts"; | ||||
| import { PutAwayRecord } from "."; | import { PutAwayRecord } from "."; | ||||
| import FgStockInForm from "../StockIn/FgStockInForm"; | |||||
| interface Props extends Omit<ModalProps, "children"> { | interface Props extends Omit<ModalProps, "children"> { | ||||
| @@ -337,7 +338,11 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||||
| 處理上架 | 處理上架 | ||||
| </Typography> | </Typography> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <StockInForm itemDetail={itemDetail} disabled={true} putawayMode={true}/> | |||||
| {itemDetail.jobOrderId ? ( | |||||
| <FgStockInForm itemDetail={itemDetail} disabled={true} putawayMode={true}/> | |||||
| ) : ( | |||||
| <StockInForm itemDetail={itemDetail} disabled={true} putawayMode={true}/> | |||||
| )} | |||||
| </Grid> | </Grid> | ||||
| <Paper sx={{ mt: 2, padding: 2, width: "100%", backgroundColor: verified ? '#bceb19' : '#FCD34D' }}> | <Paper sx={{ mt: 2, padding: 2, width: "100%", backgroundColor: verified ? '#bceb19' : '#FCD34D' }}> | ||||
| <Grid | <Grid | ||||
| @@ -0,0 +1,442 @@ | |||||
| "use client"; | |||||
| import { | |||||
| PurchaseQcResult, | |||||
| PurchaseQCInput, | |||||
| StockInInput, | |||||
| } from "@/app/api/stockIn/actions"; | |||||
| import { | |||||
| Box, | |||||
| Card, | |||||
| CardContent, | |||||
| Grid, | |||||
| Stack, | |||||
| TextField, | |||||
| Tooltip, | |||||
| Typography, | |||||
| } from "@mui/material"; | |||||
| import { Controller, useFormContext } from "react-hook-form"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import StyledDataGrid from "../StyledDataGrid"; | |||||
| import { useCallback, useEffect, useMemo } from "react"; | |||||
| import { | |||||
| GridColDef, | |||||
| GridRowIdGetter, | |||||
| GridRowModel, | |||||
| useGridApiContext, | |||||
| GridRenderCellParams, | |||||
| GridRenderEditCellParams, | |||||
| useGridApiRef, | |||||
| } from "@mui/x-data-grid"; | |||||
| import InputDataGrid from "../InputDataGrid"; | |||||
| import { TableRow } from "../InputDataGrid/InputDataGrid"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||||
| import { StockInLine } from "@/app/api/stockIn"; | |||||
| import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; | |||||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||||
| import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
| import dayjs from "dayjs"; | |||||
| // change PurchaseQcResult to stock in entry props | |||||
| interface Props { | |||||
| itemDetail: StockInLine; | |||||
| // qc: QcItemWithChecks[]; | |||||
| disabled: boolean; | |||||
| putawayMode?: boolean; | |||||
| } | |||||
| type EntryError = | |||||
| | { | |||||
| [field in keyof StockInInput]?: string; | |||||
| } | |||||
| | undefined; | |||||
| // type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | |||||
| const textfieldSx = { | |||||
| width: "100%", | |||||
| "& .MuiInputBase-root": { | |||||
| // height: "120", // Scales with root font size | |||||
| height: "5rem", // Scales with root font size | |||||
| }, | |||||
| "& .MuiInputBase-input": { | |||||
| height: "100%", | |||||
| boxSizing: "border-box", | |||||
| padding: "0.75rem", | |||||
| fontSize: 24, | |||||
| }, | |||||
| "& .MuiInputLabel-root": { | |||||
| fontSize: 24, | |||||
| transform: "translate(14px, 1.5rem) scale(1)", | |||||
| "&.MuiInputLabel-shrink": { | |||||
| fontSize: 18, | |||||
| transform: "translate(14px, -9px) scale(1)", | |||||
| }, | |||||
| // [theme.breakpoints.down("sm")]: { | |||||
| // fontSize: "1rem", | |||||
| // transform: "translate(14px, 1.5rem) scale(1)", | |||||
| // "&.MuiInputLabel-shrink": { | |||||
| // fontSize: "0.875rem", | |||||
| // }, | |||||
| // }, | |||||
| }, | |||||
| }; | |||||
| const FgStockInForm: React.FC<Props> = ({ | |||||
| // qc, | |||||
| itemDetail, | |||||
| disabled, | |||||
| putawayMode = false, | |||||
| }) => { | |||||
| const { | |||||
| t, | |||||
| i18n: { language }, | |||||
| } = useTranslation("purchaseOrder"); | |||||
| const apiRef = useGridApiRef(); | |||||
| const { | |||||
| register, | |||||
| formState: { errors, defaultValues, touchedFields }, | |||||
| watch, | |||||
| control, | |||||
| setValue, | |||||
| getValues, | |||||
| reset, | |||||
| resetField, | |||||
| setError, | |||||
| clearErrors, | |||||
| } = useFormContext<StockInInput>(); | |||||
| // console.log(itemDetail); | |||||
| useEffect(() => { | |||||
| // console.log("triggered"); | |||||
| // // receiptDate default tdy | |||||
| // setValue("receiptDate", dayjs().add(0, "month").format(INPUT_DATE_FORMAT)); | |||||
| // setValue("status", "received"); | |||||
| }, [setValue]); | |||||
| useEffect(() => { | |||||
| console.log(errors); | |||||
| }, [errors]); | |||||
| const productionDate = watch("productionDate"); | |||||
| const expiryDate = watch("expiryDate"); | |||||
| const uom = watch("uom"); | |||||
| //// TODO : Add Checking //// | |||||
| // Check if dates are input | |||||
| // if (data.productionDate === undefined || data.productionDate == null) { | |||||
| // validationErrors.push("請輸入生產日期!"); | |||||
| // } | |||||
| // if (data.expiryDate === undefined || data.expiryDate == null) { | |||||
| // validationErrors.push("請輸入到期日!"); | |||||
| // } | |||||
| useEffect(() => { | |||||
| // console.log(uom); | |||||
| // console.log(productionDate); | |||||
| // console.log(expiryDate); | |||||
| if (expiryDate) clearErrors(); | |||||
| if (productionDate) clearErrors(); | |||||
| }, [productionDate, expiryDate, clearErrors]); | |||||
| useEffect(() => { | |||||
| console.log("%c StockInForm itemDetail update: ", "color: brown", itemDetail); | |||||
| }, [itemDetail]); | |||||
| return ( | |||||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | |||||
| {/* <Grid item xs={12}> | |||||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||||
| {t("Stock In Detail")} | |||||
| </Typography> | |||||
| </Grid> */} | |||||
| <Grid | |||||
| container | |||||
| justifyContent="flex-start" | |||||
| alignItems="flex-start" | |||||
| spacing={2} | |||||
| sx={{ mt: 0.5 }} | |||||
| > | |||||
| {putawayMode && ( | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("joCode")} | |||||
| fullWidth | |||||
| {...register("productLotNo", { | |||||
| // required: "productLotNo required!", | |||||
| })} | |||||
| sx={textfieldSx} | |||||
| disabled={true} | |||||
| /> | |||||
| </Grid> | |||||
| ) | |||||
| } | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("itemName")} | |||||
| fullWidth | |||||
| {...register("itemName", { | |||||
| // required: "productLotNo required!", | |||||
| })} | |||||
| sx={textfieldSx} | |||||
| disabled={true} | |||||
| // error={Boolean(errors.productLotNo)} | |||||
| // helperText={errors.productLotNo?.message} | |||||
| /> | |||||
| </Grid> | |||||
| {putawayMode || ( | |||||
| <> | |||||
| <Grid item xs={6}> | |||||
| <Controller | |||||
| control={control} | |||||
| name="receiptDate" | |||||
| rules={{ required: true }} | |||||
| render={({ field }) => { | |||||
| return ( | |||||
| <LocalizationProvider | |||||
| dateAdapter={AdapterDayjs} | |||||
| adapterLocale={`${language}-hk`} | |||||
| > | |||||
| <DatePicker | |||||
| {...field} | |||||
| sx={textfieldSx} | |||||
| label={t("receiptDate")} | |||||
| value={dayjs(watch("receiptDate"))} | |||||
| disabled={true} | |||||
| onChange={(date) => { | |||||
| if (!date) return; | |||||
| // setValue("receiptDate", date.format(INPUT_DATE_FORMAT)); | |||||
| field.onChange(date); | |||||
| }} | |||||
| inputRef={field.ref} | |||||
| slotProps={{ | |||||
| textField: { | |||||
| // required: true, | |||||
| error: Boolean(errors.receiptDate?.message), | |||||
| helperText: errors.receiptDate?.message, | |||||
| }, | |||||
| }} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| ); | |||||
| }} | |||||
| /> | |||||
| </Grid> | |||||
| </> | |||||
| )} | |||||
| <Grid item xs={6}> | |||||
| {putawayMode ? ( | |||||
| <TextField | |||||
| label={t("stockLotNo")} | |||||
| fullWidth | |||||
| {...register("lotNo", { | |||||
| // required: "productLotNo required!", | |||||
| })} | |||||
| sx={textfieldSx} | |||||
| disabled={true} | |||||
| error={Boolean(errors.productLotNo)} | |||||
| helperText={errors.productLotNo?.message} | |||||
| />) : ( | |||||
| <TextField | |||||
| label={t("stockLotNo")} | |||||
| fullWidth | |||||
| {...register("productLotNo", { | |||||
| // required: "productLotNo required!", | |||||
| })} | |||||
| sx={textfieldSx} | |||||
| disabled={true} | |||||
| error={Boolean(errors.productLotNo)} | |||||
| helperText={errors.productLotNo?.message} | |||||
| />) | |||||
| } | |||||
| </Grid> | |||||
| {/* {putawayMode || (<> | |||||
| <Grid item xs={6}> | |||||
| <Controller | |||||
| control={control} | |||||
| name="productionDate" | |||||
| // rules={{ required: !Boolean(expiryDate) }} | |||||
| render={({ field }) => { | |||||
| return ( | |||||
| <LocalizationProvider | |||||
| dateAdapter={AdapterDayjs} | |||||
| adapterLocale={`${language}-hk`} | |||||
| > | |||||
| <DatePicker | |||||
| {...field} | |||||
| sx={textfieldSx} | |||||
| label={t("productionDate")} | |||||
| value={productionDate ? dayjs(productionDate) : undefined} | |||||
| disabled={disabled} | |||||
| onChange={(date) => { | |||||
| if (!date) return; | |||||
| setValue( | |||||
| "productionDate", | |||||
| date.format(INPUT_DATE_FORMAT), | |||||
| ); | |||||
| // field.onChange(date); | |||||
| }} | |||||
| inputRef={field.ref} | |||||
| slotProps={{ | |||||
| textField: { | |||||
| // required: true, | |||||
| error: Boolean(errors.productionDate?.message), | |||||
| helperText: errors.productionDate?.message, | |||||
| }, | |||||
| }} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| ); | |||||
| }} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("qty")} | |||||
| fullWidth | |||||
| {...register("qty", { | |||||
| required: "qty required!", | |||||
| })} | |||||
| sx={textfieldSx} | |||||
| disabled={true} | |||||
| /> | |||||
| </Grid></> | |||||
| )} */} | |||||
| <Grid item xs={6}> | |||||
| <Controller | |||||
| control={control} | |||||
| name="expiryDate" | |||||
| // rules={{ required: !Boolean(productionDate) }} | |||||
| render={({ field }) => { | |||||
| return ( | |||||
| <LocalizationProvider | |||||
| dateAdapter={AdapterDayjs} | |||||
| adapterLocale={`${language}-hk`} | |||||
| > | |||||
| <DatePicker | |||||
| {...field} | |||||
| sx={textfieldSx} | |||||
| label={t("expiryDate")} | |||||
| value={expiryDate ? dayjs(expiryDate) : undefined} | |||||
| disabled={disabled} | |||||
| onChange={(date) => { | |||||
| if (!date) return; | |||||
| console.log(date.format(INPUT_DATE_FORMAT)); | |||||
| setValue("expiryDate", date.format(INPUT_DATE_FORMAT)); | |||||
| // field.onChange(date); | |||||
| }} | |||||
| inputRef={field.ref} | |||||
| slotProps={{ | |||||
| textField: { | |||||
| // required: true, | |||||
| error: Boolean(errors.expiryDate?.message), | |||||
| helperText: errors.expiryDate?.message, | |||||
| }, | |||||
| }} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| ); | |||||
| }} | |||||
| /> | |||||
| </Grid> | |||||
| {/* <Grid item xs={6}> | |||||
| {putawayMode ? ( | |||||
| <TextField | |||||
| label={t("acceptedQty")} | |||||
| fullWidth | |||||
| sx={textfieldSx} | |||||
| disabled={true} | |||||
| value={itemDetail.acceptedQty} | |||||
| // disabled={true} | |||||
| // disabled={disabled} | |||||
| // error={Boolean(errors.acceptedQty)} | |||||
| // helperText={errors.acceptedQty?.message} | |||||
| /> | |||||
| ) : ( | |||||
| <TextField | |||||
| label={t("receivedQty")} | |||||
| fullWidth | |||||
| {...register("receivedQty", { | |||||
| required: "receivedQty required!", | |||||
| })} | |||||
| sx={textfieldSx} | |||||
| disabled={true} | |||||
| /> | |||||
| )} | |||||
| </Grid> */} | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("salesUnit")} | |||||
| fullWidth | |||||
| {...register("uom.udfudesc", { | |||||
| required: "uom required!", | |||||
| })} | |||||
| // value={uom?.code} | |||||
| sx={textfieldSx} | |||||
| disabled={true} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| {putawayMode ? ( | |||||
| <TextField | |||||
| label={t("processedQty")} | |||||
| fullWidth | |||||
| sx={textfieldSx} | |||||
| disabled={true} | |||||
| value={itemDetail.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0} | |||||
| // disabled={true} | |||||
| // disabled={disabled} | |||||
| // error={Boolean(errors.acceptedQty)} | |||||
| // helperText={errors.acceptedQty?.message} | |||||
| /> | |||||
| ) : ( | |||||
| <TextField | |||||
| label={t("acceptedQty")} | |||||
| fullWidth | |||||
| sx={textfieldSx} | |||||
| disabled={true} | |||||
| {...register("acceptedQty", { | |||||
| required: "acceptedQty required!", | |||||
| })} | |||||
| // disabled={true} | |||||
| // disabled={disabled} | |||||
| // error={Boolean(errors.acceptedQty)} | |||||
| // helperText={errors.acceptedQty?.message} | |||||
| /> | |||||
| )} | |||||
| </Grid> | |||||
| {/* <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("acceptedWeight")} | |||||
| fullWidth | |||||
| // {...register("acceptedWeight", { | |||||
| // required: "acceptedWeight required!", | |||||
| // })} | |||||
| disabled={disabled} | |||||
| error={Boolean(errors.acceptedWeight)} | |||||
| helperText={errors.acceptedWeight?.message} | |||||
| /> | |||||
| </Grid> */} | |||||
| </Grid> | |||||
| <Grid | |||||
| container | |||||
| justifyContent="flex-start" | |||||
| alignItems="flex-start" | |||||
| spacing={2} | |||||
| sx={{ mt: 0.5 }} | |||||
| > | |||||
| {/* <Grid item xs={12}> | |||||
| <InputDataGrid<PurchaseQCInput, PurchaseQcResult, EntryError> | |||||
| apiRef={apiRef} | |||||
| checkboxSelection={false} | |||||
| _formKey={"qcCheck"} | |||||
| columns={columns} | |||||
| validateRow={validationTest} | |||||
| /> | |||||
| </Grid> */} | |||||
| </Grid> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default FgStockInForm; | |||||
| @@ -3,25 +3,37 @@ | |||||
| "Create Job Order": "創建工單", | "Create Job Order": "創建工單", | ||||
| "Edit Job Order Detail": "編輯工單", | "Edit Job Order Detail": "編輯工單", | ||||
| "Details": "細節", | "Details": "細節", | ||||
| "Actions": "操作", | |||||
| "Code": "工單編號", | "Code": "工單編號", | ||||
| "Name": "成品/半成品名稱", | "Name": "成品/半成品名稱", | ||||
| "Picked Qty": "已提料數量", | "Picked Qty": "已提料數量", | ||||
| "Req. Qty": "需求數量", | "Req. Qty": "需求數量", | ||||
| "UoM": "銷售單位", | "UoM": "銷售單位", | ||||
| "Status": "來貨狀態", | |||||
| "Status": "工單狀態", | |||||
| "Lot No.": "批號", | "Lot No.": "批號", | ||||
| "Bom": "物料清單", | "Bom": "物料清單", | ||||
| "Release": "放單", | "Release": "放單", | ||||
| "Pending": "待掃碼", | "Pending": "待掃碼", | ||||
| "Pending for pick": "待提料", | "Pending for pick": "待提料", | ||||
| "Planning": "計劃中", | "Planning": "計劃中", | ||||
| "Scanned": "已掃碼", | |||||
| "Processing": "已開始工序", | "Processing": "已開始工序", | ||||
| "Storing": "入倉中", | |||||
| "Storing": "成品入倉中", | |||||
| "completed": "已完成", | "completed": "已完成", | ||||
| "Completed": "已完成", | "Completed": "已完成", | ||||
| "Cancel": "取消", | "Cancel": "取消", | ||||
| "Create": "創建", | "Create": "創建", | ||||
| "view stockin": "查看入倉詳情", | |||||
| "view putaway": "查看上架詳情", | |||||
| "process epqc": "進行成品檢驗", | |||||
| "scan picked material": "掃碼確認提料", | |||||
| "escalation processing": "處理上報記錄", | |||||
| "process stockIn": "進行收貨程序", | |||||
| "release jo": "確認發佈工單", | |||||
| "complete jo": "完成工單", | |||||
| "update success": "成功更新資料", | |||||
| "Scanned": "已掃碼", | |||||
| "Scan Status": "掃碼狀態", | "Scan Status": "掃碼狀態", | ||||
| "Start Job Order": "開始工單", | "Start Job Order": "開始工單", | ||||
| "Target Production Date" : "預計生產日期", | "Target Production Date" : "預計生產日期", | ||||
| @@ -80,7 +92,7 @@ | |||||
| "Scanning...": "掃碼中", | "Scanning...": "掃碼中", | ||||
| "Unassigned Job Orders": "未分配工單", | "Unassigned Job Orders": "未分配工單", | ||||
| "Please scan the item qr code": "請掃描物料二維碼", | "Please scan the item qr code": "請掃描物料二維碼", | ||||
| "Please make sure the qty is enough": "請確保物料數量是足夠", | |||||
| "Please make sure the qty is enough": "物料數量不足", | |||||
| "Please make sure all required items are picked": "請確保所有物料已被提取", | "Please make sure all required items are picked": "請確保所有物料已被提取", | ||||
| "Do you want to start job order": "是否開始工單", | "Do you want to start job order": "是否開始工單", | ||||
| "Submit": "提交", | "Submit": "提交", | ||||
| @@ -45,6 +45,7 @@ | |||||
| "processedQty": "已上架數量", | "processedQty": "已上架數量", | ||||
| "expiryDate": "到期日", | "expiryDate": "到期日", | ||||
| "acceptedQty": "本批收貨數量", | "acceptedQty": "本批收貨數量", | ||||
| "acceptedPutawayQty": "本批上架數量", | |||||
| "putawayQty": "上架數量", | "putawayQty": "上架數量", | ||||
| "acceptQty": "揀收數量", | "acceptQty": "揀收數量", | ||||
| "printQty": "列印數量", | "printQty": "列印數量", | ||||
| @@ -151,5 +152,9 @@ | |||||
| "Qc Decision": "品檢詳情", | "Qc Decision": "品檢詳情", | ||||
| "Print Qty": "列印數量", | "Print Qty": "列印數量", | ||||
| "putawayDatetime": "上架時間", | "putawayDatetime": "上架時間", | ||||
| "putawayUser": "上架同事" | |||||
| "putawayUser": "上架同事", | |||||
| "joCode": "工單編號", | |||||
| "salesUnit": "銷售單位", | |||||
| "download Qr Code": "下載QR碼", | |||||
| "downloading": "下載中" | |||||
| } | } | ||||