| @@ -4,13 +4,14 @@ import { BASE_API_URL } from "../../../config/api"; | |||
| // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||
| import { revalidateTag } from "next/cache"; | |||
| import { cache } from "react"; | |||
| import { PoResult, StockInLine } from "."; | |||
| import { PoResult } from "."; | |||
| //import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { serverFetchJson, serverFetchWithNoContent } from "../../utils/fetchUtil"; | |||
| import { QcItemResult } from "../settings/qcItem"; | |||
| import { RecordsRes } from "../utils"; | |||
| import { Uom } from "../settings/uom"; | |||
| import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | |||
| import { StockInLine } from "../stockIn"; | |||
| // import { BASE_API_URL } from "@/config/api"; | |||
| export interface PostStockInLineResponse<T> { | |||
| @@ -24,11 +25,12 @@ export interface PostStockInLineResponse<T> { | |||
| // entity: StockInLine | StockInLine[] | |||
| } | |||
| // DEPRECIATED | |||
| export interface StockInLineEntry { | |||
| id?: number; | |||
| itemId: number; | |||
| purchaseOrderId: number; | |||
| purchaseOrderLineId: number; | |||
| purchaseOrderId?: number; | |||
| purchaseOrderLineId?: number; | |||
| acceptedQty: number; | |||
| status?: string; | |||
| expiryDate?: string; | |||
| @@ -38,6 +40,7 @@ export interface StockInLineEntry { | |||
| dnNo?: string; | |||
| } | |||
| // DEPRECIATED | |||
| export interface PurchaseQcResult{ | |||
| id?: number; | |||
| qcItemId: number; | |||
| @@ -46,6 +49,7 @@ export interface PurchaseQcResult{ | |||
| remarks?: string; | |||
| escalationLogId?: number; | |||
| } | |||
| // DEPRECIATED | |||
| export interface StockInInput { | |||
| status: string; | |||
| poCode: string; | |||
| @@ -53,6 +57,7 @@ export interface StockInInput { | |||
| dnNo?: string; | |||
| dnDate?: string; | |||
| itemName: string; | |||
| lotNo?: string; | |||
| invoiceNo?: string; | |||
| receiptDate: string; | |||
| supplier: string; | |||
| @@ -64,6 +69,7 @@ export interface StockInInput { | |||
| expiryDate: string; | |||
| uom: Uom; | |||
| } | |||
| // DEPRECIATED | |||
| export interface PurchaseQCInput { | |||
| status: string; | |||
| acceptQty: number; | |||
| @@ -75,6 +81,7 @@ export interface PurchaseQCInput { | |||
| qcDecision?: number; | |||
| qcResult: PurchaseQcResult[]; | |||
| } | |||
| // DEPRECIATED | |||
| export interface EscalationInput { | |||
| status: string; | |||
| remarks?: string; | |||
| @@ -84,6 +91,8 @@ export interface EscalationInput { | |||
| acceptedQty?: number; // this is the qty to be escalated | |||
| // escalationQty: number | |||
| } | |||
| // DEPRECIATED | |||
| export interface PutAwayLine { | |||
| id?: number | |||
| qty: number | |||
| @@ -92,6 +101,8 @@ export interface PutAwayLine { | |||
| printQty: number; | |||
| _isNew?: boolean; | |||
| } | |||
| // DEPRECIATED | |||
| export interface PutAwayInput { | |||
| status: string; | |||
| acceptedQty: number; | |||
| @@ -99,12 +110,14 @@ export interface PutAwayInput { | |||
| putAwayLines: PutAwayLine[] | |||
| } | |||
| // DEPRECIATED | |||
| export type ModalFormInput = Partial< | |||
| PurchaseQCInput & StockInInput & PutAwayInput | |||
| > & { | |||
| escalationLog? : Partial<EscalationInput> | |||
| }; | |||
| // DEPRECIATED | |||
| export interface PrintQrCodeForSilRequest { | |||
| stockInLineId: number; | |||
| printerId: number; | |||
| @@ -117,6 +130,7 @@ export const testFetch = cache(async (id: number) => { | |||
| }); | |||
| }); | |||
| // DEPRECIATED | |||
| export const fetchStockInLineInfo = cache(async (stockInLineId: number) => { | |||
| return serverFetchJson<StockInLine>( | |||
| `${BASE_API_URL}/stockInLine/${stockInLineId}`, | |||
| @@ -126,6 +140,7 @@ export const fetchStockInLineInfo = cache(async (stockInLineId: number) => { | |||
| ); | |||
| }); | |||
| // DEPRECIATED | |||
| export const createStockInLine = async (data: StockInLineEntry) => { | |||
| const stockInLine = await serverFetchJson< | |||
| PostStockInLineResponse<StockInLine> | |||
| @@ -138,6 +153,7 @@ export const createStockInLine = async (data: StockInLineEntry) => { | |||
| return stockInLine; | |||
| }; | |||
| // DEPRECIATED | |||
| export const updateStockInLine = async ( | |||
| data: StockInLineEntry & ModalFormInput, | |||
| ) => { | |||
| @@ -228,6 +244,7 @@ export const testing = cache(async (queryParams?: Record<string, any>) => { | |||
| } | |||
| }); | |||
| // DEPRECIATED | |||
| export const printQrCodeForSil = cache(async(data: PrintQrCodeForSilRequest) => { | |||
| const params = convertObjToURLSearchParams(data) | |||
| return serverFetchWithNoContent(`${BASE_API_URL}/stockInLine/printQrCode?${params}`, | |||
| @@ -7,6 +7,7 @@ import { BASE_API_URL } from "../../../config/api"; | |||
| import { Uom } from "../settings/uom"; | |||
| import { RecordsRes } from "../utils"; | |||
| import { PutAwayLine } from "./actions"; | |||
| import { StockInLine } from "../stockIn"; | |||
| export enum StockInStatus { | |||
| PENDING = "pending", | |||
| @@ -60,7 +61,8 @@ export interface StockUomForPoLine { | |||
| purchaseRatioD: number; | |||
| } | |||
| export interface StockInLine { | |||
| // DEPRECIATED | |||
| export interface StockInLine_old { | |||
| id: number; | |||
| stockInId?: number; | |||
| purchaseOrderId?: number; | |||
| @@ -15,6 +15,14 @@ export interface QcItemWithChecks { | |||
| description: string | undefined; | |||
| } | |||
| export interface QcResult{ | |||
| id?: number; | |||
| qcItemId: number; | |||
| qcPassed?: boolean; | |||
| failQty?: number; | |||
| remarks?: string; | |||
| escalationLogId?: number; | |||
| } | |||
| export interface QcData { | |||
| id?: number, | |||
| qcItemId: number, | |||
| @@ -0,0 +1,243 @@ | |||
| "use server"; | |||
| // import { BASE_API_URL } from "@/config/api"; | |||
| import { BASE_API_URL } from "../../../config/api"; | |||
| // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||
| import { revalidateTag } from "next/cache"; | |||
| import { cache } from "react"; | |||
| import { PoResult, StockInLine } from "."; | |||
| //import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { serverFetchJson, serverFetchWithNoContent } from "../../utils/fetchUtil"; | |||
| import { QcItemResult } from "../settings/qcItem"; | |||
| import { RecordsRes } from "../utils"; | |||
| import { Uom } from "../settings/uom"; | |||
| import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | |||
| // import { BASE_API_URL } from "@/config/api"; | |||
| export interface PostStockInLineResponse<T> { | |||
| id: number | null; | |||
| name: string; | |||
| code: string; | |||
| type?: string; | |||
| message: string | null; | |||
| errorPosition: string | keyof T; | |||
| entity: T | T[]; | |||
| // entity: StockInLine | StockInLine[] | |||
| } | |||
| export interface StockInLineEntry { | |||
| id?: number; | |||
| itemId: number; | |||
| acceptedQty: number; | |||
| purchaseOrderId?: number; | |||
| purchaseOrderLineId?: number; | |||
| status?: string; | |||
| expiryDate?: string; | |||
| productLotNo?: string; | |||
| receiptDate?: string; | |||
| dnDate?: string; | |||
| dnNo?: string; | |||
| } | |||
| export interface PurchaseQcResult{ | |||
| id?: number; | |||
| qcItemId: number; | |||
| qcPassed?: boolean; | |||
| failQty?: number; | |||
| remarks?: string; | |||
| escalationLogId?: number; | |||
| } | |||
| export interface StockInInput { | |||
| status: string; | |||
| poCode: string; | |||
| productLotNo?: string; | |||
| dnNo?: string; | |||
| dnDate?: string; | |||
| itemName: string; | |||
| lotNo?: string; | |||
| invoiceNo?: string; | |||
| receiptDate: string; | |||
| supplier: string; | |||
| acceptedQty: number; | |||
| qty: number; | |||
| receivedQty: number; | |||
| acceptedWeight?: number; | |||
| productionDate?: string; | |||
| expiryDate: string; | |||
| uom: Uom; | |||
| } | |||
| export interface PurchaseQCInput { | |||
| status: string; | |||
| acceptQty: number; | |||
| passingQty: number; | |||
| sampleRate?: number; | |||
| sampleWeight?: number; | |||
| totalWeight?: number; | |||
| qcAccept: boolean; | |||
| qcDecision?: number; | |||
| qcResult: PurchaseQcResult[]; | |||
| } | |||
| export interface EscalationInput { | |||
| status: string; | |||
| remarks?: string; | |||
| reason?: string; | |||
| handlerId: number; | |||
| productLotNo?: string; | |||
| acceptedQty?: number; // this is the qty to be escalated | |||
| // escalationQty: number | |||
| } | |||
| export interface PutAwayLine { | |||
| id?: number | |||
| qty: number | |||
| warehouseId: number; | |||
| warehouse: string; | |||
| printQty: number; | |||
| _isNew?: boolean; | |||
| } | |||
| export interface PutAwayInput { | |||
| status: string; | |||
| acceptedQty: number; | |||
| warehouseId: number; | |||
| putAwayLines: PutAwayLine[] | |||
| } | |||
| export type ModalFormInput = Partial< | |||
| PurchaseQCInput & StockInInput & PutAwayInput | |||
| > & { | |||
| escalationLog? : Partial<EscalationInput> | |||
| }; | |||
| export interface PrintQrCodeForSilRequest { | |||
| stockInLineId: number; | |||
| printerId: number; | |||
| printQty?: number; | |||
| } | |||
| export const testFetch = cache(async (id: number) => { | |||
| return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { | |||
| next: { tags: ["po"] }, | |||
| }); | |||
| }); | |||
| export const fetchStockInLineInfo = cache(async (stockInLineId: number) => { | |||
| return serverFetchJson<StockInLine>( | |||
| `${BASE_API_URL}/stockInLine/${stockInLineId}`, | |||
| { | |||
| next: { tags: ["stockInLine"] }, | |||
| }, | |||
| ); | |||
| }); | |||
| export const createStockInLine = async (data: StockInLineEntry) => { | |||
| const stockInLine = await serverFetchJson< | |||
| PostStockInLineResponse<StockInLine> | |||
| >(`${BASE_API_URL}/stockInLine/create`, { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }); | |||
| // revalidateTag("po"); | |||
| return stockInLine; | |||
| }; | |||
| export const updateStockInLine = async ( | |||
| data: StockInLineEntry & ModalFormInput, | |||
| ) => { | |||
| const stockInLine = await serverFetchJson< | |||
| PostStockInLineResponse<StockInLine & ModalFormInput> | |||
| >(`${BASE_API_URL}/stockInLine/update`, { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }); | |||
| // revalidateTag("po"); | |||
| return stockInLine; | |||
| }; | |||
| export const startPo = async (poId: number) => { | |||
| const po = await serverFetchJson<PostStockInLineResponse<PoResult>>( | |||
| `${BASE_API_URL}/po/start/${poId}`, | |||
| { | |||
| method: "POST", | |||
| body: JSON.stringify({ poId }), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }, | |||
| ); | |||
| revalidateTag("po"); | |||
| return po; | |||
| }; | |||
| export const checkPolAndCompletePo = async (poId: number) => { | |||
| const po = await serverFetchJson<PostStockInLineResponse<PoResult>>( | |||
| `${BASE_API_URL}/po/check/${poId}`, | |||
| { | |||
| method: "POST", | |||
| body: JSON.stringify({ poId }), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }, | |||
| ); | |||
| revalidateTag("po"); | |||
| return po; | |||
| }; | |||
| export const fetchPoInClient = cache(async (id: number) => { | |||
| return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { | |||
| next: { tags: ["po"] }, | |||
| }); | |||
| }); | |||
| export const fetchPoListClient = cache( | |||
| async (queryParams?: Record<string, any>) => { | |||
| if (queryParams) { | |||
| const queryString = new URLSearchParams(queryParams).toString(); | |||
| return serverFetchJson<RecordsRes<PoResult[]>>( | |||
| `${BASE_API_URL}/po/list?${queryString}`, | |||
| { | |||
| method: "GET", | |||
| next: { tags: ["po"] }, | |||
| }, | |||
| ); | |||
| } else { | |||
| return serverFetchJson<RecordsRes<PoResult[]>>( | |||
| `${BASE_API_URL}/po/list`, | |||
| { | |||
| method: "GET", | |||
| next: { tags: ["po"] }, | |||
| }, | |||
| ); | |||
| } | |||
| }, | |||
| ); | |||
| export const testing = cache(async (queryParams?: Record<string, any>) => { | |||
| if (queryParams) { | |||
| const queryString = new URLSearchParams(queryParams).toString(); | |||
| return serverFetchJson<RecordsRes<PoResult[]>>( | |||
| `${BASE_API_URL}/po/testing?${queryString}`, | |||
| { | |||
| method: "GET", | |||
| next: { tags: ["po"] }, | |||
| }, | |||
| ); | |||
| } else { | |||
| return serverFetchJson<RecordsRes<PoResult[]>>( | |||
| `${BASE_API_URL}/po/testing`, | |||
| { | |||
| method: "GET", | |||
| next: { tags: ["po"] }, | |||
| }, | |||
| ); | |||
| } | |||
| }); | |||
| export const printQrCodeForSil = cache(async(data: PrintQrCodeForSilRequest) => { | |||
| const params = convertObjToURLSearchParams(data) | |||
| return serverFetchWithNoContent(`${BASE_API_URL}/stockInLine/printQrCode?${params}`, | |||
| { | |||
| method: "GET", | |||
| headers: { "Content-Type": "application/json" }, | |||
| next: { | |||
| tags: ["printQrCodeForSil"], | |||
| }, | |||
| }, | |||
| ) | |||
| }) | |||
| @@ -0,0 +1,168 @@ | |||
| import { cache } from "react"; | |||
| import "server-only"; | |||
| // import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| // import { BASE_API_URL } from "@/config/api"; | |||
| import { serverFetchJson } from "../../utils/fetchUtil"; | |||
| import { BASE_API_URL } from "../../../config/api"; | |||
| import { Uom } from "../settings/uom"; | |||
| import { RecordsRes } from "../utils"; | |||
| import { QcResult } from "../qc"; | |||
| import { EscalationResult } from "../escalation"; | |||
| export enum StockInStatus { | |||
| PENDING = "pending", | |||
| RECEIVED = "received", | |||
| APPROVED = "escalated", | |||
| REJECTED = "rejected", | |||
| COMPLETED = "completed", | |||
| PARTIALLY_COMPLETED = "partially_completed", | |||
| } | |||
| export interface StockInLineInput { | |||
| id?: number; | |||
| itemId?: number; | |||
| acceptedQty?: number; | |||
| receivedQty?: number; | |||
| status?: string; | |||
| expiryDate?: string; | |||
| productLotNo?: string; | |||
| receiptDate?: string; | |||
| dnDate?: number[]; | |||
| dnNo?: string; | |||
| } | |||
| export interface StockInInput { | |||
| stockInId?: number; | |||
| poCode?: string; | |||
| productLotNo?: string; | |||
| dnNo?: string; | |||
| dnDate?: string; | |||
| itemName?: string; | |||
| lotNo?: string; | |||
| invoiceNo?: string; | |||
| receiptDate: string; | |||
| supplier?: string; | |||
| acceptedQty: number; | |||
| qty: number; | |||
| receivedQty: number; | |||
| acceptedWeight?: number; | |||
| productionDate?: string; | |||
| expiryDate: string; | |||
| uom?: Uom; | |||
| } | |||
| export interface PoResult { | |||
| id: number; | |||
| code: string; | |||
| orderDate: string; | |||
| supplier: string; | |||
| estimatedArrivalDate: string; | |||
| completedDate: string; | |||
| itemDetail?: string; | |||
| itemCode?: string; | |||
| itemName?: string; | |||
| itemQty?: string; | |||
| itemSumAcceptedQty?: string; | |||
| itemUom?: string; | |||
| escalated: boolean; | |||
| status: string; | |||
| pol?: PurchaseOrderLine[]; | |||
| } | |||
| export interface PurchaseOrderLine { | |||
| id: number; | |||
| purchaseOrderId: number; | |||
| itemId: number; | |||
| itemNo: string; | |||
| itemName: string; | |||
| qty: number; | |||
| processed: number; | |||
| receivedQty: number; | |||
| uom: Uom; | |||
| stockUom: StockUomForPoLine; | |||
| price: number; | |||
| status: string; | |||
| stockInLine: StockInLine[]; | |||
| } | |||
| export interface StockUomForPoLine { | |||
| id: number; | |||
| stockUomCode: string; | |||
| stockUomDesc: string; | |||
| stockQty: number; | |||
| stockRatioN: number; | |||
| stockRatioD: number; | |||
| purchaseRatioN: number; | |||
| purchaseRatioD: number; | |||
| } | |||
| export interface StockInLine { | |||
| id: number; | |||
| stockInId?: number; | |||
| purchaseOrderId?: number; | |||
| purchaseOrderLineId: number; | |||
| itemId: number; | |||
| itemNo: string; | |||
| itemName: string; | |||
| itemType: string; | |||
| demandQty: number; | |||
| acceptedQty: number; | |||
| qty?: number; | |||
| receivedQty?: number; | |||
| processed?: number; | |||
| price?: number; | |||
| priceUnit?: string; | |||
| shelfLife?: number; | |||
| receiptDate?: string; | |||
| productionDate?: string; | |||
| productLotNo?: string; | |||
| expiryDate?: string; | |||
| status: string; | |||
| supplier?: string; | |||
| lotNo?: string; | |||
| poCode?: string; | |||
| uom?: Uom; | |||
| defaultWarehouseId: number; // id for now | |||
| dnNo?: string; | |||
| dnDate?: number[]; | |||
| stockQty?: number; | |||
| handlerId?: number; | |||
| putAwayLines?: PutAwayLine[]; | |||
| qcResult?: QcResult[]; | |||
| escResult?: EscalationResult[]; | |||
| } | |||
| export interface EscalationInput { | |||
| status: string; | |||
| remarks?: string; | |||
| reason?: string; | |||
| handlerId: number; | |||
| productLotNo?: string; | |||
| acceptedQty?: number; // this is the qty to be escalated | |||
| // escalationQty: number | |||
| } | |||
| export interface PutAwayLine { | |||
| id?: number | |||
| qty: number | |||
| warehouseId: number; | |||
| warehouse: string; | |||
| printQty: number; | |||
| _isNew?: boolean; | |||
| } | |||
| export interface PutAwayInput { | |||
| status: string; | |||
| acceptedQty: number; | |||
| warehouseId: number; | |||
| putAwayLines: PutAwayLine[] | |||
| } | |||
| export interface QcInput { | |||
| acceptQty: number; | |||
| qcAccept: boolean; | |||
| qcDecision?: number; | |||
| qcResult: QcResult[]; | |||
| } | |||
| export type ModalFormInput = Partial< | |||
| QcInput & StockInInput & PutAwayInput | |||
| > & { | |||
| escalationLog? : Partial<EscalationInput> | |||
| }; | |||
| @@ -86,11 +86,11 @@ export const dayjsToDateString = (date: Dayjs) => { | |||
| return date.format(OUTPUT_DATE_FORMAT); | |||
| }; | |||
| export const dayjsToInputDateString = (date: Dayjs) => { | |||
| export const dayjsToInputDateString = (date: Dayjs) => { // TODO fix remove the time format (need global check) | |||
| return date.format(INPUT_DATE_FORMAT + "T" + INPUT_TIME_FORMAT); | |||
| }; | |||
| export const dayjsToInputDatetimeString = (date: Dayjs) => { | |||
| export const dayjsToInputDateStringFIX = (date: Dayjs) => { // TODO fix it after the above one is fixed | |||
| return date.format(INPUT_DATE_FORMAT); | |||
| }; | |||
| @@ -98,6 +98,17 @@ export const dayjsToInputDateTimeString = (date: Dayjs) => { | |||
| return date.format(`${INPUT_DATE_FORMAT}T${OUTPUT_TIME_FORMAT}`); | |||
| }; | |||
| export const dayjsToArray = (date: Dayjs) => { | |||
| return [ | |||
| date.year(), | |||
| date.month() + 1, // Months are 0-based in Day.js, so add 1 | |||
| date.date(), | |||
| date.hour(), // (24-hour format) | |||
| date.minute(), | |||
| date.second(), | |||
| ]; | |||
| }; | |||
| export const outputDateStringToInputDateString = (date: string) => { | |||
| return dayjsToInputDateString(dateStringToDayjs(date)) | |||
| } | |||
| @@ -22,7 +22,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | |||
| import ExpandLessIcon from '@mui/icons-material/ExpandLess'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { Controller, useFormContext } from 'react-hook-form'; | |||
| import { EscalationInput, ModalFormInput } from '@/app/api/po/actions'; | |||
| import { EscalationInput, ModalFormInput } from '@/app/api/stockIn/actions'; | |||
| import { EscalationCombo } from "@/app/api/user"; | |||
| import { fetchEscalationCombo } from "@/app/api/user/actions"; | |||
| import { FireExtinguisher } from '@mui/icons-material'; | |||
| @@ -1,6 +1,6 @@ | |||
| "use client"; | |||
| import { StockInLineEntry, EscalationInput } from "@/app/api/po/actions"; | |||
| import { StockInLineEntry, EscalationInput } from "@/app/api/stockIn/actions"; | |||
| import { | |||
| Box, | |||
| Card, | |||
| @@ -30,7 +30,7 @@ import TwoLineCell from "./TwoLineCell"; | |||
| import QcSelect from "./QcSelect"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { StockInLine } from "@/app/api/stockIn"; | |||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| interface Props { | |||
| @@ -4,7 +4,6 @@ import { | |||
| fetchPoWithStockInLines, | |||
| PoResult, | |||
| PurchaseOrderLine, | |||
| StockInLine, | |||
| } from "@/app/api/po"; | |||
| import { | |||
| Box, | |||
| @@ -44,11 +43,11 @@ import { | |||
| checkPolAndCompletePo, | |||
| fetchPoInClient, | |||
| fetchPoListClient, | |||
| fetchStockInLineInfo, | |||
| PurchaseQcResult, | |||
| startPo, | |||
| createStockInLine | |||
| } from "@/app/api/po/actions"; | |||
| import { | |||
| createStockInLine | |||
| } from "@/app/api/stockIn/actions"; | |||
| import { | |||
| useCallback, | |||
| useContext, | |||
| @@ -59,7 +58,7 @@ import { | |||
| import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; | |||
| import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; | |||
| import PoInputGrid from "./PoInputGrid"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| // import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { useRouter, useSearchParams, usePathname } from "next/navigation"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| import { calculateWeight, dateStringToDayjs, dayjsToDateString, OUTPUT_DATE_FORMAT, outputDateStringToInputDateString, returnWeightUnit } from "@/app/utils/formatUtil"; | |||
| @@ -81,12 +80,13 @@ import LoadingComponent from "../General/LoadingComponent"; | |||
| import { getMailTemplatePdfForStockInLine } from "@/app/api/mailTemplate/actions"; | |||
| import { PrinterCombo } from "@/app/api/settings/printer"; | |||
| import { EscalationCombo } from "@/app/api/user"; | |||
| import { StockInLine } from "@/app/api/stockIn"; | |||
| //import { useRouter } from "next/navigation"; | |||
| type Props = { | |||
| po: PoResult; | |||
| qc: QcItemWithChecks[]; | |||
| // qc: QcItemWithChecks[]; | |||
| warehouse: WarehouseResult[]; | |||
| printerCombo: PrinterCombo[]; | |||
| }; | |||
| @@ -191,7 +191,7 @@ interface PolInputResult { | |||
| dnQty: number, | |||
| } | |||
| const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => { | |||
| const cameras = useContext(CameraContext); | |||
| // console.log(cameras); | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| @@ -236,7 +236,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| const dnFormProps = useForm({ | |||
| defaultValues: { | |||
| dnNo: '', | |||
| dnDate: dayjsToDateString(dayjs()) | |||
| receiptDate: dayjsToDateString(dayjs()) | |||
| } | |||
| }) | |||
| const fetchPoList = useCallback(async () => { | |||
| @@ -263,7 +263,6 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| const fetchPoDetail = useCallback(async (poId: string) => { | |||
| try { | |||
| const result = await fetchPoInClient(parseInt(poId)); | |||
| console.log(result) | |||
| if (result) { | |||
| console.log("%c Fetched PO:", "color:orange", result); | |||
| setPurchaseOrder(result); | |||
| @@ -420,11 +419,11 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| const postData = { | |||
| dnNo: dnFormProps.watch("dnNo"), | |||
| dnDate: outputDateStringToInputDateString(dnFormProps.watch("dnDate")), | |||
| receiptDate: outputDateStringToInputDateString(dnFormProps.watch("receiptDate")), | |||
| itemId: row.itemId, | |||
| itemNo: row.itemNo, | |||
| itemName: row.itemName, | |||
| purchaseOrderId: row.purchaseOrderId, | |||
| // purchaseOrderId: row.purchaseOrderId, | |||
| purchaseOrderLineId: row.id, | |||
| acceptedQty: acceptedQty, | |||
| productLotNo: polInputList[rowIndex].lotNo || '', | |||
| @@ -765,11 +764,11 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| > | |||
| <Controller | |||
| control={dnFormProps.control} | |||
| name="dnDate" | |||
| name="receiptDate" | |||
| render={({ field }) => ( | |||
| <DatePicker | |||
| label={t("dnDate")} | |||
| label={t("receiptDate")} | |||
| format={`${OUTPUT_DATE_FORMAT}`} | |||
| defaultValue={dateStringToDayjs(field.value)} | |||
| onChange={(newValue: Dayjs | null) => { | |||
| @@ -854,7 +853,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| <TableCell align="right"> | |||
| <Box> | |||
| <PoInputGrid | |||
| qc={qc} | |||
| // qc={qc} | |||
| setRows={setRows} | |||
| stockInLine={stockInLine} | |||
| setStockInLine={setStockInLine} | |||
| @@ -24,18 +24,16 @@ type Props = { | |||
| const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | |||
| const [ | |||
| poWithStockInLine, | |||
| warehouse, | |||
| qc, | |||
| warehouse, | |||
| printerCombo, | |||
| ] = await Promise.all([ | |||
| fetchPoWithStockInLines(id), | |||
| fetchWarehouseList(), | |||
| fetchQcItemCheck(), | |||
| fetchPrinterCombo(), | |||
| ]); | |||
| // const poWithStockInLine = await fetchPoWithStockInLines(id) | |||
| console.log("%c pol:", "color:green", poWithStockInLine); | |||
| return <PoDetail po={poWithStockInLine} qc={qc} warehouse={warehouse} printerCombo={printerCombo} />; | |||
| return <PoDetail po={poWithStockInLine} warehouse={warehouse} printerCombo={printerCombo} />; | |||
| }; | |||
| PoDetailWrapper.Loading = PoDetailLoading; | |||
| @@ -31,10 +31,11 @@ import DeleteIcon from "@mui/icons-material/Delete"; | |||
| import CancelIcon from "@mui/icons-material/Cancel"; | |||
| import FactCheckIcon from "@mui/icons-material/FactCheck"; | |||
| import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; | |||
| import { QcItemWithChecks } from "src/app/api/qc"; | |||
| // import { QcItemWithChecks } from "src/app/api/qc"; | |||
| import PlayArrowIcon from "@mui/icons-material/PlayArrow"; | |||
| import { PurchaseOrderLine, StockInLine } from "@/app/api/po"; | |||
| import { createStockInLine, PurchaseQcResult } from "@/app/api/po/actions"; | |||
| import { PurchaseOrderLine } from "@/app/api/po"; | |||
| import { StockInLine } from "@/app/api/stockIn"; | |||
| import { createStockInLine, PurchaseQcResult } from "@/app/api/stockIn/actions"; | |||
| import { usePathname, useRouter, useSearchParams } from "next/navigation"; | |||
| import { | |||
| returnWeightUnit, | |||
| @@ -73,7 +74,7 @@ interface ResultWithId { | |||
| } | |||
| interface Props { | |||
| qc: QcItemWithChecks[]; | |||
| // qc: QcItemWithChecks[]; | |||
| setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | |||
| setStockInLine: Dispatch<SetStateAction<StockInLine[]>>; | |||
| setProcessedQty: Dispatch<SetStateAction<number>>; | |||
| @@ -114,7 +115,7 @@ class ProcessRowUpdateError extends Error { | |||
| } | |||
| function PoInputGrid({ | |||
| qc, | |||
| // qc, | |||
| setRows, | |||
| setStockInLine, | |||
| setProcessedQty, | |||
| @@ -136,7 +137,7 @@ function PoInputGrid({ | |||
| const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []); | |||
| useEffect(() => { | |||
| setEntries(stockInLine) | |||
| setEntries(stockInLine); | |||
| }, [stockInLine]) | |||
| const [modalInfo, setModalInfo] = useState< | |||
| StockInLine & { qcResult?: PurchaseQcResult[] } & { escalationResult?: EscalationResult[] } | |||
| @@ -215,7 +216,7 @@ function PoInputGrid({ | |||
| setRejectOpen(true); | |||
| }, []); | |||
| const handleStart = useCallback( | |||
| const handleStart = useCallback( // NOTE: Seems unused!!!!!!!! | |||
| (id: GridRowId, params: any) => () => { | |||
| setBtnIsLoading(true); | |||
| setRowModesModel((prev) => ({ | |||
| @@ -229,7 +230,7 @@ function PoInputGrid({ | |||
| itemId: params.row.itemId, | |||
| itemNo: params.row.itemNo, | |||
| itemName: params.row.itemName, | |||
| purchaseOrderId: params.row.purchaseOrderId, | |||
| // purchaseOrderId: params.row.purchaseOrderId, | |||
| purchaseOrderLineId: params.row.purchaseOrderLineId, | |||
| acceptedQty: params.row.acceptedQty, | |||
| }; | |||
| @@ -263,7 +264,6 @@ function PoInputGrid({ | |||
| [id]: { mode: GridRowModes.View }, | |||
| })); | |||
| const qcResult = await fetchQcDefaultValue(id); | |||
| const escResult = await fetchEscalationLogsByStockInLines([Number(id)]); | |||
| // console.log(params.row); | |||
| console.log("Fetched QC Result:", qcResult); | |||
| @@ -291,9 +291,9 @@ const closeNewModal = useCallback(() => { | |||
| newParams.delete("stockInLineId"); // Remove the parameter | |||
| router.replace(`${pathname}?${newParams.toString()}`); | |||
| fetchPoDetail(itemDetail.purchaseOrderId.toString()); | |||
| setTimeout(() => { | |||
| setNewOpen(false); // Close the modal first | |||
| }, 300); // Add a delay to avoid immediate re-trigger of useEffect | |||
| setNewOpen(false); // Close the modal first | |||
| // setTimeout(() => { | |||
| // }, 300); // Add a delay to avoid immediate re-trigger of useEffect | |||
| }, [searchParams, pathname, router]); | |||
| // Open modal | |||
| @@ -304,31 +304,27 @@ const closeNewModal = useCallback(() => { | |||
| // Button handler to update the URL and open the modal | |||
| const handleNewQC = useCallback( | |||
| (id: GridRowId, params: any) => async() => { | |||
| // console.log(id) | |||
| // setBtnIsLoading(true); | |||
| setRowModesModel((prev) => ({ | |||
| ...prev, | |||
| [id]: { mode: GridRowModes.View }, | |||
| })); | |||
| const qcResult = await fetchQcDefaultValue(id); | |||
| const escResult = await fetchEscalationLogsByStockInLines([Number(id)]); | |||
| // const qcResult = await fetchQcDefaultValue(id); | |||
| // const escResult = await fetchEscalationLogsByStockInLines([Number(id)]); | |||
| setModalInfo(() => ({ | |||
| ...params.row, | |||
| qcResult: qcResult, | |||
| escResult: escResult, | |||
| // qcResult: qcResult, | |||
| // escResult: escResult, | |||
| receivedQty: itemDetail.receivedQty, | |||
| })); | |||
| setTimeout(() => { | |||
| const newParams = new URLSearchParams(searchParams.toString()); | |||
| newParams.set("stockInLineId", id.toString()); // Ensure `set` to avoid duplicates | |||
| router.replace(`${pathname}?${newParams.toString()}`); | |||
| // console.log("hello") | |||
| openNewModal() | |||
| // setBtnIsLoading(false); | |||
| }, 200); | |||
| const newParams = new URLSearchParams(searchParams.toString()); | |||
| newParams.set("stockInLineId", id.toString()); // Ensure `set` to avoid duplicates | |||
| router.replace(`${pathname}?${newParams.toString()}`); | |||
| openNewModal() | |||
| // setTimeout(() => { | |||
| // }, 200); | |||
| }, | |||
| [fetchQcDefaultValue, openNewModal, pathname, router, searchParams] | |||
| ); | |||
| @@ -337,7 +333,6 @@ const closeNewModal = useCallback(() => { | |||
| const [firstCheckForSil, setFirstCheckForSil] = useState(false) | |||
| useEffect(() => { | |||
| if (stockInLineId && itemDetail && !firstCheckForSil) { | |||
| // console.log("heeloo") | |||
| // console.log(stockInLineId) | |||
| // console.log(apiRef.current.getRow(stockInLineId)) | |||
| setFirstCheckForSil(true) | |||
| @@ -492,8 +487,8 @@ const closeNewModal = useCallback(() => { | |||
| // flex: 0.4, | |||
| }, | |||
| { | |||
| field: "dnDate", | |||
| headerName: t("dnDate"), | |||
| field: "receiptDate", | |||
| headerName: t("receiptDate"), | |||
| width: 125, | |||
| renderCell: (params) => { | |||
| // console.log(params.row) | |||
| @@ -952,100 +947,19 @@ const closeNewModal = useCallback(() => { | |||
| footer: { child: footer }, | |||
| }} | |||
| /> | |||
| {modalInfo !== undefined && ( | |||
| {/* {modalInfo !== undefined && ( */} | |||
| <> | |||
| <QcStockInModal | |||
| // setRows={setRows} | |||
| setEntries={setEntries} | |||
| setStockInLine={setStockInLine} | |||
| setItemDetail={setModalInfo} | |||
| session={sessionToken} | |||
| qc={qc} | |||
| warehouse={warehouse} | |||
| open={newOpen} | |||
| onClose={closeNewModal} | |||
| itemDetail={modalInfo} | |||
| handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} | |||
| // itemDetail={modalInfo} | |||
| inputDetail={modalInfo} | |||
| printerCombo={printerCombo} | |||
| /> | |||
| </> | |||
| ) | |||
| } | |||
| {/* {modalInfo !== undefined && ( | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"qc"} | |||
| // setRows={setRows} | |||
| setEntries={setEntries} | |||
| setStockInLine={setStockInLine} | |||
| setItemDetail={setModalInfo} | |||
| qc={qc} | |||
| open={qcOpen} | |||
| onClose={closeQcModal} | |||
| itemDetail={modalInfo} | |||
| /> | |||
| </> | |||
| )} | |||
| {modalInfo !== undefined && ( | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"escalation"} | |||
| // setRows={setRows} | |||
| setEntries={setEntries} | |||
| setStockInLine={setStockInLine} | |||
| setItemDetail={setModalInfo} | |||
| // qc={qc} | |||
| open={escalOpen} | |||
| onClose={closeEscalationModal} | |||
| itemDetail={modalInfo} | |||
| /> | |||
| </> | |||
| )} | |||
| {modalInfo !== undefined && ( | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"reject"} | |||
| // setRows={setRows} | |||
| setEntries={setEntries} | |||
| setStockInLine={setStockInLine} | |||
| setItemDetail={setModalInfo} | |||
| // qc={qc} | |||
| open={rejectOpen} | |||
| onClose={closeRejectModal} | |||
| itemDetail={modalInfo} | |||
| /> | |||
| </> | |||
| )} | |||
| {modalInfo !== undefined && ( | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"stockIn"} | |||
| // setRows={setRows} | |||
| setEntries={setEntries} | |||
| setStockInLine={setStockInLine} | |||
| // qc={qc} | |||
| setItemDetail={setModalInfo} | |||
| open={stockInOpen} | |||
| onClose={closeStockInModal} | |||
| itemDetail={modalInfo} | |||
| /> | |||
| </> | |||
| )} | |||
| {modalInfo !== undefined && ( | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"putaway"} | |||
| // setRows={setRows} | |||
| setEntries={setEntries} | |||
| setStockInLine={setStockInLine} | |||
| setItemDetail={setModalInfo} | |||
| open={putAwayOpen} | |||
| warehouse={warehouse} | |||
| onClose={closePutAwayModal} | |||
| itemDetail={modalInfo} | |||
| /> | |||
| </> | |||
| )} */} | |||
| {/* ) | |||
| } */} | |||
| </> | |||
| ); | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| "use client"; | |||
| import { PurchaseQcResult, PutAwayInput, PutAwayLine } from "@/app/api/po/actions"; | |||
| import { PurchaseQcResult, PutAwayInput, PutAwayLine } from "@/app/api/stockIn/actions"; | |||
| import { | |||
| Autocomplete, | |||
| Box, | |||
| @@ -36,7 +36,7 @@ import TwoLineCell from "./TwoLineCell"; | |||
| import QcSelect from "./QcSelect"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { StockInLine } from "@/app/api/stockIn"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| import { | |||
| arrayToDateTimeString, | |||
| @@ -58,7 +58,7 @@ dayjs.extend(arraySupport); | |||
| interface Props { | |||
| itemDetail: StockInLine; | |||
| warehouse: WarehouseResult[]; | |||
| warehouse?: WarehouseResult[]; | |||
| disabled: boolean; | |||
| // qc: QcItemWithChecks[]; | |||
| setRowModesModel: Dispatch<SetStateAction<GridRowModesModel>>; | |||
| @@ -84,7 +84,7 @@ const style = { | |||
| width: "auto", | |||
| }; | |||
| const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowModesModel, setRowSelectionModel }) => { | |||
| const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse=[], disabled, setRowModesModel, setRowSelectionModel }) => { | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| const apiRef = useGridApiRef(); | |||
| const { | |||
| @@ -100,7 +100,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||
| clearErrors, | |||
| } = useFormContext<PutAwayInput>(); | |||
| // const [recordQty, setRecordQty] = useState(0); | |||
| const [warehouseId, setWarehouseId] = useState(itemDetail.defaultWarehouseId); | |||
| const [warehouseId, setWarehouseId] = useState(itemDetail.defaultWarehouseId ?? 1); | |||
| const filteredWarehouse = useMemo(() => { | |||
| // do filtering here if any | |||
| return warehouse; | |||
| @@ -113,11 +113,11 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||
| }; | |||
| const options = useMemo(() => { | |||
| return [ | |||
| // { | |||
| // value: 0, // think think sin | |||
| // label: t("Select warehouse"), | |||
| // group: "default", | |||
| // }, | |||
| { | |||
| value: 1, | |||
| label: t("W001 - 憶兆 3樓A倉"), | |||
| group: "default", | |||
| }, | |||
| ...filteredWarehouse.map((w) => ({ | |||
| value: w.id, | |||
| label: `${w.code} - ${w.name}`, | |||
| @@ -175,32 +175,10 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||
| // validateForm(); | |||
| // }, [validateForm]); | |||
| const qrContent = useMemo( | |||
| () => ({ | |||
| stockInLineId: itemDetail.id, | |||
| itemId: itemDetail.itemId, | |||
| lotNo: itemDetail.lotNo, | |||
| // warehouseId: 2 // for testing | |||
| // expiryDate: itemDetail.expiryDate, | |||
| // productionDate: itemDetail.productionDate, | |||
| // supplier: itemDetail.supplier, | |||
| // poCode: itemDetail.poCode, | |||
| }), | |||
| [itemDetail], | |||
| ); | |||
| const [isOpenScanner, setOpenScanner] = useState(false); | |||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||
| (...args) => { | |||
| setOpenScanner(false); | |||
| }, | |||
| [], | |||
| ); | |||
| useEffect(() => { | |||
| setValue("status", "received"); | |||
| // setValue("status", "completed"); | |||
| setValue("warehouseId", options[0].value); //TODO: save all warehouse entry? | |||
| // setValue("warehouseId", options[0].value); //TODO: save all warehouse entry? | |||
| }, []); | |||
| useEffect(() => { | |||
| @@ -342,7 +320,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||
| const defaultMaxQty = Number(itemDetail.demandQty?? itemDetail.acceptedQty)//watch("acceptedQty") | |||
| - watch("putAwayLines").reduce((acc, cur) => acc + cur.qty, 0) | |||
| const defaultWarehouseId = itemDetail.defaultWarehouseId ?? 1 | |||
| const defaultWarehouse = options.find((o) => o.value === defaultWarehouseId)?.label | |||
| const defaultWarehouse = "W001 - 憶兆 3樓A倉"//options.find((o) => o.value === defaultWarehouseId)?.label | |||
| return {qty: defaultMaxQty, warehouseId: defaultWarehouseId, warehouse: defaultWarehouse, printQty: 1, _isNew: true } as Partial<PutAwayLine> | |||
| }, []) | |||
| @@ -360,27 +338,27 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| <Grid item xs={12}> | |||
| {/* <Grid item xs={6}> | |||
| <TextField | |||
| label={t("LotNo")} | |||
| label={t("Supplier")} | |||
| fullWidth | |||
| value={itemDetail.lotNo} | |||
| value={itemDetail.supplier} | |||
| disabled | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| </Grid> */} | |||
| {/* <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Supplier")} | |||
| label={t("Po Code")} | |||
| fullWidth | |||
| value={itemDetail.supplier} | |||
| value={itemDetail.poCode} | |||
| disabled | |||
| /> | |||
| </Grid> | |||
| </Grid> */} | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Po Code")} | |||
| label={t("itemNo")} | |||
| fullWidth | |||
| value={itemDetail.poCode} | |||
| value={itemDetail.itemNo} | |||
| disabled | |||
| /> | |||
| </Grid> | |||
| @@ -394,44 +372,52 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("itemNo")} | |||
| label={t("stockLotNo")} | |||
| fullWidth | |||
| value={itemDetail.itemNo} | |||
| value={itemDetail.lotNo} | |||
| disabled | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("qty")} | |||
| label={t("expiryDate")} | |||
| fullWidth | |||
| value={ | |||
| // dayjs(itemDetail.expiryDate) | |||
| dayjs() | |||
| .add(20, "day") | |||
| .format(OUTPUT_DATE_FORMAT)} | |||
| disabled | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={3}> | |||
| <TextField | |||
| label={t("acceptedQty")} | |||
| fullWidth | |||
| value={itemDetail.acceptedQty} | |||
| disabled | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <Grid item xs={3}> | |||
| <TextField | |||
| label={t("productionDate")} | |||
| label={t("uom")} | |||
| fullWidth | |||
| value={ | |||
| // dayjs(itemDetail.productionDate) | |||
| dayjs() | |||
| // .add(-1, "month") | |||
| .format(OUTPUT_DATE_FORMAT)} | |||
| value={itemDetail.uom?.udfudesc} | |||
| disabled | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| {/* <Grid item xs={6}> | |||
| <TextField | |||
| label={t("expiryDate")} | |||
| label={t("productionDate")} | |||
| fullWidth | |||
| value={ | |||
| // dayjs(itemDetail.expiryDate) | |||
| // dayjs(itemDetail.productionDate) | |||
| dayjs() | |||
| .add(20, "day") | |||
| // .add(-1, "month") | |||
| .format(OUTPUT_DATE_FORMAT)} | |||
| disabled | |||
| /> | |||
| </Grid> | |||
| </Grid> */} | |||
| <Grid item xs={6}> | |||
| <FormControl fullWidth> | |||
| <Autocomplete | |||
| @@ -530,7 +516,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||
| <Grid | |||
| item | |||
| xs={12} | |||
| style={{ display: "flex", justifyContent: "center" }} | |||
| style={{ display: "flex", justifyContent: "center", marginTop:5 }} | |||
| > | |||
| {/* <QrCode content={qrContent} sx={{ width: 200, height: 200 }} /> */} | |||
| <InputDataGrid<PutAwayInput, PutAwayLine, EntryError> | |||
| @@ -548,24 +534,6 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| {/* <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| <Button onClick={onOpenScanner}>bind</Button> | |||
| </Grid> */} | |||
| <Modal open={isOpenScanner} onClose={closeHandler}> | |||
| <Box sx={style}> | |||
| <Typography variant="h4"> | |||
| {t("Please scan warehouse qr code.")} | |||
| </Typography> | |||
| {/* <ReactQrCodeScanner scannerConfig={scannerConfig} /> */} | |||
| </Box> | |||
| </Modal> | |||
| </Grid> | |||
| ); | |||
| }; | |||
| @@ -1,6 +1,6 @@ | |||
| "use client"; | |||
| import { PurchaseQcResult, PurchaseQCInput } from "@/app/api/po/actions"; | |||
| import { PurchaseQcResult, PurchaseQCInput } from "@/app/api/stockIn/actions"; | |||
| import { | |||
| Box, | |||
| Card, | |||
| @@ -39,7 +39,7 @@ import { TableRow } from "../InputDataGrid/InputDataGrid"; | |||
| import TwoLineCell from "./TwoLineCell"; | |||
| import QcSelect from "./QcSelect"; | |||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { ModalFormInput, StockInLine } from "@/app/api/stockIn"; | |||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; | |||
| import { QcItemWithChecks, QcData } from "@/app/api/qc"; | |||
| @@ -49,16 +49,17 @@ import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||
| import EscalationComponent from "./EscalationComponent"; | |||
| import QcDataGrid from "./QCDatagrid"; | |||
| import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate"; | |||
| import { ModalFormInput } from "@/app/api/po/actions"; | |||
| import { escape, min } from "lodash"; | |||
| import { PanoramaSharp } from "@mui/icons-material"; | |||
| import EscalationLogTable from "../DashboardPage/escalation/EscalationLogTable"; | |||
| import { EscalationResult } from "@/app/api/escalation"; | |||
| import { EscalationCombo } from "@/app/api/user"; | |||
| import CollapsibleCard from "../CollapsibleCard/CollapsibleCard"; | |||
| import LoadingComponent from "../General/LoadingComponent"; | |||
| interface Props { | |||
| itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||
| itemDetail: StockInLine; | |||
| // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||
| // qc: QcItemWithChecks[]; | |||
| disabled: boolean; | |||
| // qcItems: QcData[] | |||
| @@ -97,7 +98,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||
| // const qcResult = useMemo(() => [...watch("qcResult")], [watch("qcResult")]); | |||
| const qcRecord = useMemo(() => { // Need testing | |||
| const value = watch('qcResult'); console.log("%c QC update!", "color:green", value); | |||
| const value = watch('qcResult'); //console.log("%c QC update!", "color:green", value); | |||
| return Array.isArray(value) ? [...value] : []; | |||
| }, [watch('qcResult')]); | |||
| const [qcHistory, setQcHistory] = useState<PurchaseQcResult[]>([]); | |||
| @@ -168,7 +169,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||
| if (validateFieldFail("acceptQty", isNaN(accQty), t("value must be a number"))) return; | |||
| } | |||
| const qcResultItems = qcResult; console.log("Validating:", qcResultItems); | |||
| const qcResultItems = qcResult; //console.log("Validating:", qcResultItems); | |||
| // Check if failed items have failed quantity | |||
| const failedItemsWithoutQty = qcResultItems.filter(item => | |||
| item.qcPassed === false && (!item.failQty || item.failQty <= 0) | |||
| @@ -194,21 +195,6 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||
| validateForm(); | |||
| }, [clearErrors, validateForm]); | |||
| const columns = useMemo<GridColDef[]>( | |||
| () => [ | |||
| { | |||
| field: "escalation", | |||
| headerName: t("escalation"), | |||
| flex: 1, | |||
| }, | |||
| { | |||
| field: "supervisor", | |||
| headerName: t("supervisor"), | |||
| flex: 1, | |||
| }, | |||
| ], | |||
| [], | |||
| ); | |||
| /// validate datagrid | |||
| const validation = useCallback( | |||
| (newRow: GridRowModel<QcRow>): EntryError => { | |||
| @@ -220,16 +206,16 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||
| ); | |||
| function BooleanEditCell(params: GridRenderEditCellParams) { | |||
| const apiRef = useGridApiContext(); | |||
| const { id, field, value } = params; | |||
| const apiRef = useGridApiContext(); | |||
| const { id, field, value } = params; | |||
| const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |||
| apiRef.current.setEditCellValue({ id, field, value: e.target.checked }); | |||
| apiRef.current.stopCellEditMode({ id, field }); // commit immediately | |||
| }; | |||
| const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |||
| apiRef.current.setEditCellValue({ id, field, value: e.target.checked }); | |||
| apiRef.current.stopCellEditMode({ id, field }); // commit immediately | |||
| }; | |||
| return <Checkbox checked={!!value} onChange={handleChange} sx={{ p: 0 }} />; | |||
| } | |||
| return <Checkbox checked={!!value} onChange={handleChange} sx={{ p: 0 }} />; | |||
| } | |||
| const qcDisabled = (row : PurchaseQcResult) => { | |||
| return disabled || isExist(row.escalationLogId); | |||
| @@ -396,12 +382,12 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||
| }, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]); | |||
| useEffect(() => { | |||
| console.log("%c Qc Record updated:", "color:red", qcRecord); | |||
| console.log("%c Qc Record updated:", "color:green", qcRecord); | |||
| if (qcRecord.length < 1) { // New QC | |||
| const fetchedQcData = dummyQCData; //TODO fetch from DB | |||
| setValue("qcResult", fetchedQcData); | |||
| } else { | |||
| if (itemDetail.status == "escalated") { // Copy the previous QC data for editing | |||
| if (itemDetail?.status == "escalated") { // Copy the previous QC data for editing | |||
| if (qcRecord.find((qc) => !isExist(qc.escalationLogId)) === undefined) { | |||
| const copiedQcData = qcRecord.map(qc => ({ ...qc, escalationLogId: undefined })); | |||
| const mutableQcData = [...qcRecord, ...copiedQcData]; | |||
| @@ -442,7 +428,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||
| useEffect(() => { | |||
| console.log("%c QC ItemDetail updated:", "color: gold", itemDetail); | |||
| // console.log("%c QC ItemDetail updated:", "color: gold", itemDetail); | |||
| }, [itemDetail]); | |||
| @@ -482,6 +468,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||
| return ( | |||
| <> | |||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
| {itemDetail ? ( | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| @@ -577,127 +564,129 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||
| </> | |||
| )} | |||
| <Grid item xs={12}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| {t("Qc Decision")} | |||
| </Typography> | |||
| <FormControl> | |||
| <Controller | |||
| name="qcDecision" | |||
| // name="qcAccept" | |||
| control={control} | |||
| defaultValue={setDefaultQcDecision(itemDetail?.status)} | |||
| // defaultValue={true} | |||
| render={({ field }) => ( | |||
| <> | |||
| {/* <Typography sx={{color:"red"}}> | |||
| {errors.qcDecision?.message} | |||
| </Typography> */} | |||
| <RadioGroup | |||
| row | |||
| aria-labelledby="demo-radio-buttons-group-label" | |||
| {...field} | |||
| value={field.value} | |||
| // value={field.value?.toString() || "true"} | |||
| onChange={(e) => { | |||
| const value = e.target.value.toString();// === 'true'; | |||
| const input = document.getElementById('accQty') as HTMLInputElement; //TODO improve | |||
| console.log("%c AccQty Error", "color:pink", errors.acceptQty); | |||
| if (input) { // Selected Reject in new flow with Error | |||
| if (value == "1") { // Selected Accept | |||
| input.value = Number(accQty).toString(); | |||
| } else { | |||
| if (Boolean(errors.acceptQty)) { | |||
| setValue("acceptQty", 0); | |||
| } | |||
| input.value = '0'; | |||
| } | |||
| } | |||
| // setValue("acceptQty", itemDetail.acceptedQty ?? 0); | |||
| // clearErrors("acceptQty"); | |||
| // } | |||
| field.onChange(value); | |||
| }} | |||
| > | |||
| <FormControlLabel disabled={disabled} | |||
| value="1" control={<Radio />} label="接受來貨" /> | |||
| {(itemDetail.status == "escalated"|| (disabled && accQty != itemDetail.acceptedQty && qcDecision == 1)) && ( //TODO Improve | |||
| <Box sx={{mr:2}}> | |||
| <TextField | |||
| // type="number" | |||
| id="accQty" | |||
| label={t("acceptQty")} | |||
| sx={{ width: '150px' }} | |||
| // value={Number(accQty)} | |||
| defaultValue={Number(accQty)} | |||
| // defaultValue={(qcDecision == 1)? Number(accQty) : 0} | |||
| // value={(qcDecision == 1)? Number(accQty) : undefined } | |||
| // value={qcAccept? accQty : 0 } | |||
| disabled={qcDecision != 1 || disabled} | |||
| // disabled={!qcAccept || disabled} | |||
| onBlur={(e) => { | |||
| const value = e.target.value; | |||
| const input = document.getElementById('accQty') as HTMLInputElement; | |||
| input.value = Number(value).toString() | |||
| setValue(`acceptQty`, Number(value)); | |||
| }} | |||
| onInput={(e: React.ChangeEvent<HTMLInputElement>) => { | |||
| const input = e.target.value; | |||
| const numReg = /^[0-9]+$/ | |||
| let r = ''; | |||
| if (!numReg.test(input)) { | |||
| const result = input.replace(/\D/g, ""); | |||
| r = (result === '' ? result : Number(result)).toString(); | |||
| <Card sx={{p:2}}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| {t("Qc Decision")} | |||
| </Typography> | |||
| <FormControl> | |||
| <Controller | |||
| name="qcDecision" | |||
| // name="qcAccept" | |||
| control={control} | |||
| defaultValue={setDefaultQcDecision(itemDetail?.status)} | |||
| // defaultValue={true} | |||
| render={({ field }) => ( | |||
| <> | |||
| {/* <Typography sx={{color:"red"}}> | |||
| {errors.qcDecision?.message} | |||
| </Typography> */} | |||
| <RadioGroup | |||
| row | |||
| aria-labelledby="demo-radio-buttons-group-label" | |||
| {...field} | |||
| value={field.value} | |||
| // value={field.value?.toString() || "true"} | |||
| onChange={(e) => { | |||
| const value = e.target.value.toString();// === 'true'; | |||
| const input = document.getElementById('accQty') as HTMLInputElement; //TODO improve | |||
| // console.log("%c AccQty Error", "color:red", errors.acceptQty); | |||
| if (input) { // Selected Reject in new flow with Error | |||
| if (value == "1") { // Selected Accept | |||
| input.value = Number(accQty).toString(); | |||
| } else { | |||
| r = Number(input).toString() | |||
| if (Boolean(errors.acceptQty)) { | |||
| setValue("acceptQty", 0); | |||
| } | |||
| input.value = '0'; | |||
| } | |||
| e.target.value = r; | |||
| }} | |||
| inputProps={{ min: 1, max:itemDetail.acceptedQty }} | |||
| // onChange={(e) => { | |||
| // const inputValue = e.target.value; | |||
| // if (inputValue === '' || /^[0-9]*$/.test(inputValue)) { | |||
| // setValue("acceptQty", Number(inputValue === '' ? null : parseInt(inputValue, 10))); | |||
| // } | |||
| // }} | |||
| // {...register("acceptQty", { | |||
| // required: "acceptQty required!", | |||
| // })} | |||
| error={Boolean(errors.acceptQty)} | |||
| helperText={errors.acceptQty?.message} | |||
| /> | |||
| <TextField | |||
| type="number" | |||
| label={t("rejectQty")} | |||
| sx={{ width: '150px' }} | |||
| value={ | |||
| (!Boolean(errors.acceptQty)) ? | |||
| (qcDecision == 1 ? itemDetail.acceptedQty - accQty : itemDetail.acceptedQty) | |||
| : "" | |||
| } | |||
| error={Boolean(errors.acceptQty)} | |||
| disabled={true} | |||
| /> | |||
| </Box>)} | |||
| <FormControlLabel disabled={disabled} | |||
| value="2" control={<Radio />} | |||
| sx={{"& .Mui-checked": {color: "red"}}} | |||
| label= {itemDetail.status == "escalated" ? "全部拒絕並退貨" : "不接受並退貨"} /> | |||
| // setValue("acceptQty", itemDetail.acceptedQty ?? 0); | |||
| // clearErrors("acceptQty"); | |||
| // } | |||
| field.onChange(value); | |||
| }} | |||
| > | |||
| <FormControlLabel disabled={disabled} | |||
| value="1" control={<Radio />} label="接受來貨" /> | |||
| {(itemDetail.status == "pending" || disabled) && (<> | |||
| <FormControlLabel disabled={disabled} | |||
| value="3" control={<Radio />} | |||
| sx={{"& .Mui-checked": {color: "blue"}}} | |||
| label="上報品檢結果" /> | |||
| </>)} | |||
| </RadioGroup> | |||
| </> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| {(itemDetail.status == "escalated"|| (disabled && accQty != itemDetail.acceptedQty && qcDecision == 1)) && ( //TODO Improve | |||
| <Box sx={{mr:2}}> | |||
| <TextField | |||
| // type="number" | |||
| id="accQty" | |||
| label={t("acceptQty")} | |||
| sx={{ width: '150px' }} | |||
| // value={Number(accQty)} | |||
| defaultValue={Number(accQty)} | |||
| // defaultValue={(qcDecision == 1)? Number(accQty) : 0} | |||
| // value={(qcDecision == 1)? Number(accQty) : undefined } | |||
| // value={qcAccept? accQty : 0 } | |||
| disabled={qcDecision != 1 || disabled} | |||
| // disabled={!qcAccept || disabled} | |||
| onBlur={(e) => { | |||
| const value = e.target.value; | |||
| const input = document.getElementById('accQty') as HTMLInputElement; | |||
| input.value = Number(value).toString() | |||
| setValue(`acceptQty`, Number(value)); | |||
| }} | |||
| onInput={(e: React.ChangeEvent<HTMLInputElement>) => { | |||
| const input = e.target.value; | |||
| const numReg = /^[0-9]+$/ | |||
| let r = ''; | |||
| if (!numReg.test(input)) { | |||
| const result = input.replace(/\D/g, ""); | |||
| r = (result === '' ? result : Number(result)).toString(); | |||
| } else { | |||
| r = Number(input).toString() | |||
| } | |||
| e.target.value = r; | |||
| }} | |||
| inputProps={{ min: 1, max:itemDetail.acceptedQty }} | |||
| // onChange={(e) => { | |||
| // const inputValue = e.target.value; | |||
| // if (inputValue === '' || /^[0-9]*$/.test(inputValue)) { | |||
| // setValue("acceptQty", Number(inputValue === '' ? null : parseInt(inputValue, 10))); | |||
| // } | |||
| // }} | |||
| // {...register("acceptQty", { | |||
| // required: "acceptQty required!", | |||
| // })} | |||
| error={Boolean(errors.acceptQty)} | |||
| helperText={errors.acceptQty?.message} | |||
| /> | |||
| <TextField | |||
| type="number" | |||
| label={t("rejectQty")} | |||
| sx={{ width: '150px' }} | |||
| value={ | |||
| (!Boolean(errors.acceptQty)) ? | |||
| (qcDecision == 1 ? itemDetail.acceptedQty - accQty : itemDetail.acceptedQty) | |||
| : "" | |||
| } | |||
| error={Boolean(errors.acceptQty)} | |||
| disabled={true} | |||
| /> | |||
| </Box>)} | |||
| <FormControlLabel disabled={disabled} | |||
| value="2" control={<Radio />} | |||
| sx={{"& .Mui-checked": {color: "red"}}} | |||
| label= {itemDetail.status == "escalated" ? "全部拒絕並退貨" : "不接受並退貨"} /> | |||
| {(itemDetail.status == "pending" || disabled) && (<> | |||
| <FormControlLabel disabled={disabled} | |||
| value="3" control={<Radio />} | |||
| sx={{"& .Mui-checked": {color: "blue"}}} | |||
| label="上報品檢結果" /> | |||
| </>)} | |||
| </RadioGroup> | |||
| </> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| </Card> | |||
| </Grid> | |||
| {qcDecision == 3 && ( | |||
| // {!qcAccept && ( | |||
| @@ -721,6 +710,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||
| /> | |||
| </Grid>} */} | |||
| </Grid> | |||
| ) : <LoadingComponent/>} | |||
| </Grid> | |||
| </> | |||
| ); | |||
| @@ -1,6 +1,4 @@ | |||
| "use client"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine, PurchaseQCInput, printQrCodeForSil, PrintQrCodeForSilRequest } from "@/app/api/po/actions"; | |||
| import { QcItemWithChecks, QcData } from "@/app/api/qc"; | |||
| import { | |||
| Autocomplete, | |||
| @@ -38,6 +36,12 @@ import { GridRowModesModel } from "@mui/x-data-grid"; | |||
| import { isEmpty } from "lodash"; | |||
| import { EscalationCombo } from "@/app/api/user"; | |||
| import { truncateSync } from "fs"; | |||
| import { ModalFormInput, StockInLineInput, StockInLine } from "@/app/api/stockIn"; | |||
| import { PurchaseQcResult, StockInLineEntry, updateStockInLine, printQrCodeForSil, PrintQrCodeForSilRequest } from "@/app/api/stockIn/actions"; | |||
| import { fetchStockInLineInfo } from "@/app/api/stockIn/actions"; | |||
| import { fetchQcResult } from "@/app/api/qc/actions"; | |||
| import { fetchEscalationLogsByStockInLines } from "@/app/api/escalation/actions"; | |||
| import LoadingComponent from "../General/LoadingComponent"; | |||
| const style = { | |||
| @@ -54,42 +58,23 @@ const style = { | |||
| height: { xs: "90%", sm: "90%", md: "90%" }, | |||
| }; | |||
| interface CommonProps extends Omit<ModalProps, "children"> { | |||
| // setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | |||
| setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>; | |||
| setStockInLine?: Dispatch<SetStateAction<StockInLine[]>>; | |||
| itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||
| setItemDetail: Dispatch< | |||
| SetStateAction< | |||
| | (StockInLine & { | |||
| warehouseId?: number; | |||
| }) | |||
| | undefined | |||
| > | |||
| >; | |||
| // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] } | undefined; | |||
| inputDetail: StockInLineInput | undefined; | |||
| session: SessionWithTokens | null; | |||
| qc?: QcItemWithChecks[]; | |||
| warehouse?: any[]; | |||
| // type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; | |||
| handleMailTemplateForStockInLine: (stockInLineId: number) => void; | |||
| printerCombo: PrinterCombo[]; | |||
| onClose: () => void; | |||
| } | |||
| interface Props extends CommonProps { | |||
| itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||
| // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||
| } | |||
| const PoQcStockInModalVer2: React.FC<Props> = ({ | |||
| // type, | |||
| // setRows, | |||
| setEntries, | |||
| setStockInLine, | |||
| open, | |||
| onClose, | |||
| itemDetail, | |||
| setItemDetail, | |||
| // itemDetail, | |||
| inputDetail, | |||
| session, | |||
| qc, | |||
| warehouse, | |||
| handleMailTemplateForStockInLine, | |||
| printerCombo, | |||
| }) => { | |||
| const { | |||
| @@ -97,6 +82,10 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||
| i18n: { language }, | |||
| } = useTranslation("purchaseOrder"); | |||
| const [stockInLineInfo, setStockInLineInfo] = useState<StockInLine>(); | |||
| const [isLoading, setIsLoading] = useState<Boolean>(false); | |||
| // const [viewOnly, setViewOnly] = useState(false); | |||
| // Select Printer | |||
| const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0]); | |||
| const [printQty, setPrintQty] = useState(1); | |||
| @@ -109,28 +98,108 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||
| [], | |||
| ); | |||
| const defaultNewValue = useMemo(() => { | |||
| return ( | |||
| { | |||
| ...itemDetail, | |||
| status: itemDetail.status ?? "pending", | |||
| dnDate: arrayToDateString(itemDetail.dnDate, "input")?? dayjsToInputDateString(dayjs()), | |||
| // putAwayLines: dummyPutAwayLine, | |||
| // putAwayLines: itemDetail.putAwayLines.map((line) => (return {...line, printQty: 1})) ?? [], | |||
| putAwayLines: itemDetail.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false, _disableDelete: true})) ?? [], | |||
| // qcResult: (itemDetail.qcResult && itemDetail.qcResult?.length > 0) ? itemDetail.qcResult : [],//[...dummyQCData], | |||
| escResult: (itemDetail.escResult && itemDetail.escResult?.length > 0) ? itemDetail.escResult : [], | |||
| productionDate: itemDetail.productionDate ? arrayToDateString(itemDetail.productionDate, "input") : undefined, | |||
| expiryDate: itemDetail.expiryDate ? arrayToDateString(itemDetail.expiryDate, "input") : undefined, | |||
| receiptDate: itemDetail.receiptDate ? arrayToDateString(itemDetail.receiptDate, "input") | |||
| : dayjs().add(0, "month").format(INPUT_DATE_FORMAT), | |||
| acceptQty: itemDetail.demandQty?? itemDetail.acceptedQty, | |||
| warehouseId: itemDetail.defaultWarehouseId ?? 1, | |||
| const fetchStockInLineData = useCallback( | |||
| async (stockInLineId: number) => { | |||
| try { | |||
| const res = await fetchStockInLineInfo(stockInLineId); | |||
| if (res) { | |||
| console.log("%c Fetched Stock In Line: ", "color:orange", res); | |||
| setStockInLineInfo({...inputDetail, ...res}); | |||
| fetchQcResultData(stockInLineId); | |||
| } else throw("Result is undefined"); | |||
| } catch (e) { | |||
| console.log("%c Error when fetching Stock In Line: ", "color:red", e); | |||
| } | |||
| },[fetchStockInLineInfo, inputDetail] | |||
| ); | |||
| const fetchQcResultData = useCallback( // TODO: put this inside QC Component | |||
| async (stockInLineId: number) => { | |||
| try { | |||
| const res = await fetchQcResult(stockInLineId); | |||
| if (res.length > 0) { | |||
| console.log("%c Fetched Qc Result: ", "color:orange", res); | |||
| setStockInLineInfo((prev) => ({...prev, qcResult: res} as StockInLine)); | |||
| formProps.setValue("qcResult", res); | |||
| fetchEscalationLogData(stockInLineId); | |||
| } else {setStockInLineInfo((prev) => ({...prev, qcResult: []} as StockInLine));} | |||
| // } else throw("Result is undefined"); | |||
| } catch (e) { | |||
| console.log("%c Error when fetching Qc Result: ", "color:red", e); | |||
| } | |||
| },[fetchQcResult] | |||
| ); | |||
| const fetchEscalationLogData = useCallback( | |||
| async (stockInLineId: number) => { | |||
| try { | |||
| const res = await fetchEscalationLogsByStockInLines([stockInLineId]); | |||
| if (res.length > 0) { | |||
| console.log("%c Fetched Escalation Log: ", "color:orange", res[0]); | |||
| setStockInLineInfo((prev) => ({...prev, escResult: res} as StockInLine)); | |||
| // formProps.setValue("escalationLog", res[0]); | |||
| } else throw("Result is undefined"); | |||
| } catch (e) { | |||
| console.log("%c Error when fetching EscalationLog: ", "color:red", e); | |||
| } | |||
| },[fetchEscalationLogsByStockInLines] | |||
| ); | |||
| // Fetch info if id is input | |||
| useEffect(() => { | |||
| setIsLoading(true); | |||
| if (inputDetail && open) { | |||
| console.log("%c Opened Modal with input:", "color:yellow", inputDetail); | |||
| if (inputDetail.id) { | |||
| const id = inputDetail.id; | |||
| fetchStockInLineData(id); | |||
| } | |||
| } | |||
| ) | |||
| },[itemDetail]) | |||
| }, [open]); | |||
| const [qcItems, setQcItems] = useState(dummyQCData) | |||
| // Make sure stock in line info is fetched | |||
| useEffect(() => { | |||
| if (stockInLineInfo) { | |||
| if (stockInLineInfo.id) { | |||
| if (isLoading) { | |||
| formProps.reset({ | |||
| ...defaultNewValue | |||
| }); | |||
| console.log("%c Modal loaded successfully", "color:lime"); | |||
| setIsLoading(false); | |||
| } | |||
| } | |||
| } | |||
| }, [stockInLineInfo]); | |||
| const defaultNewValue = useMemo(() => { | |||
| const d = stockInLineInfo; | |||
| if (d !== undefined) { | |||
| // console.log("%c sil info", "color:yellow", d ) | |||
| return ( | |||
| { | |||
| ...d, | |||
| // status: d.status ?? "pending", | |||
| productionDate: d.productionDate ? arrayToDateString(d.productionDate, "input") : undefined, | |||
| expiryDate: d.expiryDate ? arrayToDateString(d.expiryDate, "input") : undefined, | |||
| receiptDate: d.receiptDate ? arrayToDateString(d.receiptDate, "input") | |||
| : dayjs().add(0, "month").format(INPUT_DATE_FORMAT), | |||
| acceptQty: d.demandQty?? d.acceptedQty, | |||
| // escResult: (d.escResult && d.escResult?.length > 0) ? d.escResult : [], | |||
| // qcResult: (d.qcResult && d.qcResult?.length > 0) ? d.qcResult : [],//[...dummyQCData], | |||
| warehouseId: d.defaultWarehouseId ?? 1, | |||
| putAwayLines: d.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false, _disableDelete: true})) ?? [], | |||
| } as ModalFormInput | |||
| ) | |||
| } return undefined | |||
| }, [stockInLineInfo]) | |||
| // const [qcItems, setQcItems] = useState(dummyQCData) | |||
| const formProps = useForm<ModalFormInput>({ | |||
| defaultValues: { | |||
| ...defaultNewValue, | |||
| @@ -139,41 +208,45 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||
| () => { | |||
| setStockInLineInfo(undefined); | |||
| formProps.reset({}); | |||
| onClose?.(); | |||
| // reset(); | |||
| }, | |||
| [onClose], | |||
| ); | |||
| const isPutaway = () => { | |||
| if (itemDetail) { | |||
| const status = itemDetail.status; | |||
| if (stockInLineInfo) { | |||
| const status = stockInLineInfo.status; | |||
| return status == "received"; | |||
| } else return false; | |||
| }; | |||
| const [viewOnly, setViewOnly] = useState(false); | |||
| useEffect(() => { | |||
| if (itemDetail && itemDetail.status) { | |||
| const isViewOnly = itemDetail.status.toLowerCase() == "completed" | |||
| || itemDetail.status.toLowerCase() == "partially_completed" // TODO update DB | |||
| || itemDetail.status.toLowerCase() == "rejected" | |||
| || (itemDetail.status.toLowerCase() == "escalated" && session?.id != itemDetail.handlerId) | |||
| setViewOnly(isViewOnly) | |||
| } | |||
| console.log("Modal ItemDetail updated:", itemDetail); | |||
| if (showPutaway) { setTabIndex(1); } else { setTabIndex(0); } | |||
| }, [itemDetail]); | |||
| useEffect(() => { | |||
| formProps.reset({ | |||
| ...defaultNewValue | |||
| }) | |||
| setQcItems(dummyQCData); | |||
| // setOpenPutaway(isPutaway); | |||
| }, [open]) | |||
| // Get show putaway | |||
| const showPutaway = useMemo(() => { | |||
| if (stockInLineInfo) { | |||
| const status = stockInLineInfo.status; | |||
| return status !== "pending" && status !== "escalated" && status !== "rejected"; | |||
| } | |||
| return false; | |||
| }, [stockInLineInfo]); | |||
| // Get is view only | |||
| const viewOnly = useMemo(() => { | |||
| if (stockInLineInfo) { | |||
| if (stockInLineInfo.status) { | |||
| const status = stockInLineInfo.status; | |||
| const isViewOnly = status.toLowerCase() == "completed" | |||
| || status.toLowerCase() == "partially_completed" // TODO update DB | |||
| || status.toLowerCase() == "rejected" | |||
| || (status.toLowerCase() == "escalated" && session?.id != stockInLineInfo.handlerId) | |||
| if (showPutaway) { setTabIndex(1); } else { setTabIndex(0); } | |||
| return isViewOnly; | |||
| } | |||
| } | |||
| return true; | |||
| }, [stockInLineInfo]) | |||
| const [openPutaway, setOpenPutaway] = useState(false); | |||
| const onOpenPutaway = useCallback(() => { | |||
| @@ -269,7 +342,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| alert("請輸入到期日!"); | |||
| return; | |||
| } | |||
| if (!qcResults.every((qc) => qc.qcPassed) && qcAccept && itemDetail.status != "escalated") { //TODO: fix it please! | |||
| if (!qcResults.every((qc) => qc.qcPassed) && qcAccept && stockInLineInfo?.status != "escalated") { //TODO: fix it please! | |||
| validationErrors.push("有不合格檢查項目,無法收貨!"); | |||
| // submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", | |||
| // confirmButtonText: t("confirm putaway"), html: ""}); | |||
| @@ -279,7 +352,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| // Check if all QC items have results | |||
| const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined); | |||
| if (itemsWithoutResult.length > 0 && itemDetail.status != "escalated") { //TODO: fix it please! | |||
| if (itemsWithoutResult.length > 0 && stockInLineInfo?.status != "escalated") { //TODO: fix it please! | |||
| validationErrors.push(`${t("QC items without result")}`); | |||
| // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`); | |||
| } | |||
| @@ -292,7 +365,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| const qcData = { | |||
| dnNo : data.dnNo? data.dnNo : "DN00000", | |||
| dnDate : data.dnDate? arrayToDateString(data.dnDate, "input") : dayjsToInputDateString(dayjs()), | |||
| // dnDate : data.dnDate? arrayToDateString(data.dnDate, "input") : dayjsToInputDateString(dayjs()), | |||
| productionDate : arrayToDateString(data.productionDate, "input"), | |||
| expiryDate : arrayToDateString(data.expiryDate, "input"), | |||
| receiptDate : arrayToDateString(data.receiptDate, "input"), | |||
| @@ -345,69 +418,36 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| return ; | |||
| }, | |||
| [onOpenPutaway, qcItems, formProps.formState.errors], | |||
| [onOpenPutaway, formProps.formState.errors], | |||
| ); | |||
| const postStockInLine = useCallback(async (args: ModalFormInput) => { | |||
| const submitData = { | |||
| ...itemDetail, ...args | |||
| ...stockInLineInfo, ...args | |||
| } as StockInLineEntry & ModalFormInput; | |||
| console.log("Submitting", submitData); | |||
| const res = await updateStockInLine(submitData); | |||
| return res; | |||
| },[itemDetail]) | |||
| // Email supplier handler | |||
| const onSubmitEmailSupplier = useCallback<SubmitHandler<ModalFormInput>>( | |||
| async (data, event) => { | |||
| console.log("Email Supplier Submission:", event!.nativeEvent); | |||
| // Extract only email supplier related fields | |||
| const emailData = { | |||
| // supplierEmail: data.supplierEmail, | |||
| // issueDescription: data.issueDescription, | |||
| // qcComments: data.qcComments, | |||
| // defectNotes: data.defectNotes, | |||
| // attachments: data.attachments, | |||
| // escalationReason: data.escalationReason, | |||
| data: data, | |||
| // Add other email-specific fields | |||
| }; | |||
| console.log("Email Supplier Data:", emailData); | |||
| // Handle email supplier logic here | |||
| // e.g., send email to supplier, log escalation, etc. | |||
| }, | |||
| [], | |||
| ); | |||
| }, [stockInLineInfo]) | |||
| // Put away model | |||
| const [pafRowModesModel, setPafRowModesModel] = useState<GridRowModesModel>({}) | |||
| const [pafRowSelectionModel, setPafRowSelectionModel] = useState<GridRowSelectionModel>([]) | |||
| const pafSubmitDisable = useMemo(() => { | |||
| // console.log("%c mode: ", "background:#90EE90; color:red", Object.entries(pafRowModesModel)) | |||
| // console.log("%c mode: ", "background:pink; color:#87CEEB", Object.entries(pafRowModesModel)) | |||
| return Object.entries(pafRowModesModel).length > 0 || Object.entries(pafRowModesModel).some(([key, value], index) => value.mode === GridRowModes.Edit) | |||
| }, [pafRowModesModel]) | |||
| // Putaway submission handler | |||
| const onSubmitPutaway = useCallback<SubmitHandler<ModalFormInput>>( | |||
| async (data, event) => { | |||
| // console.log("Putaway Submission:", event!.nativeEvent); | |||
| // console.log(data.putAwayLines) | |||
| // console.log(data.putAwayLines?.filter((line) => line._isNew !== false)) | |||
| // Extract only putaway related fields | |||
| const putawayData = { | |||
| // putawayLine: data.putawayLine, | |||
| // putawayLocation: data.putawayLocation, | |||
| // binLocation: data.binLocation, | |||
| // putawayQuantity: data.putawayQuantity, | |||
| // putawayNotes: data.putawayNotes, | |||
| acceptQty: Number(data.acceptQty?? (itemDetail.demandQty?? (itemDetail.acceptedQty))), //TODO improve | |||
| acceptQty: Number(data.acceptQty?? (stockInLineInfo?.demandQty?? (stockInLineInfo?.acceptedQty))), //TODO improve | |||
| warehouseId: data.warehouseId, | |||
| status: data.status, //TODO Fix it! | |||
| // ...data, | |||
| dnDate : data.dnDate? arrayToDateString(data.dnDate, "input") : dayjsToInputDateString(dayjs()), | |||
| // dnDate : data.dnDate? arrayToDateString(data.dnDate, "input") : dayjsToInputDateString(dayjs()), | |||
| productionDate : arrayToDateString(data.productionDate, "input"), | |||
| expiryDate : arrayToDateString(data.expiryDate, "input"), | |||
| receiptDate : arrayToDateString(data.receiptDate, "input"), | |||
| @@ -464,7 +504,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| // const printQty = printList.reduce((acc, cur) => acc + cur.printQty, 0) | |||
| // console.log(printQty) | |||
| const data: PrintQrCodeForSilRequest = { | |||
| stockInLineId: itemDetail.id, | |||
| stockInLineId: stockInLineInfo?.id ?? 0, | |||
| printerId: selectedPrinter.id, | |||
| printQty: printQty | |||
| } | |||
| @@ -475,32 +515,22 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| } finally { | |||
| setIsPrinting(() => false) | |||
| } | |||
| }, [itemDetail.id, pafRowSelectionModel, printQty, selectedPrinter]); | |||
| // }, [pafRowSelectionModel, printQty, selectedPrinter]); | |||
| }, [stockInLineInfo?.id, pafRowSelectionModel, printQty, selectedPrinter]); | |||
| const acceptQty = formProps.watch("acceptedQty") | |||
| const showPutaway = useMemo(() => { | |||
| const status = itemDetail.status; | |||
| return status !== "pending" && status !== "escalated" && status !== "rejected"; | |||
| }, [itemDetail]); | |||
| const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => { | |||
| const isPassed = qcItems.every((qc) => qc.qcPassed); | |||
| console.log(isPassed) | |||
| if (isPassed) { | |||
| formProps.setValue("passingQty", acceptQty) | |||
| } else { | |||
| formProps.setValue("passingQty", 0) | |||
| } | |||
| return isPassed | |||
| }, [acceptQty, formProps]) | |||
| // useEffect(() => { | |||
| // // maybe check if submitted before | |||
| // console.log("Modal QC Items updated:", qcItems); | |||
| // // checkQcIsPassed(qcItems) | |||
| // }, [qcItems, checkQcIsPassed]) | |||
| // const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => { | |||
| // const isPassed = qcItems.every((qc) => qc.qcPassed); | |||
| // console.log(isPassed) | |||
| // if (isPassed) { | |||
| // formProps.setValue("passingQty", acceptQty) | |||
| // } else { | |||
| // formProps.setValue("passingQty", 0) | |||
| // } | |||
| // return isPassed | |||
| // }, [acceptQty, formProps]) | |||
| return ( | |||
| <> | |||
| <FormProvider {...formProps}> | |||
| @@ -514,8 +544,11 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| marginLeft: 3, | |||
| marginRight: 3, | |||
| // overflow: "hidden", | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| }} | |||
| > | |||
| {(!isLoading && stockInLineInfo) ? (<> | |||
| <Box sx={{ position: 'sticky', top: 0, bgcolor: 'background.paper', | |||
| zIndex: 5, borderBottom: 2, borderColor: 'divider', width: "100%"}}> | |||
| <Tabs | |||
| @@ -537,124 +570,92 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| sx={{padding: 2}} | |||
| > | |||
| <Grid item xs={12}> | |||
| {tabIndex === 0 && <> | |||
| <Grid item xs={12}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| {t("Delivery Detail")} | |||
| </Typography> | |||
| </Grid> | |||
| <StockInForm itemDetail={itemDetail} disabled={viewOnly || showPutaway} /> | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| > | |||
| <QcComponent | |||
| // qc={qc!} | |||
| itemDetail={itemDetail} | |||
| disabled={viewOnly || showPutaway} | |||
| // qcItems={qcItems} | |||
| // setQcItems={setQcItems} | |||
| /> | |||
| </Grid> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| {(!viewOnly && !showPutaway) && (<Button | |||
| id="qcSubmit" | |||
| type="button" | |||
| variant="contained" | |||
| color="primary" | |||
| sx={{ mt: 1 }} | |||
| onClick={formProps.handleSubmit(onSubmitQc, onSubmitErrorQc)} | |||
| > | |||
| {t("confirm qc result")} | |||
| </Button>)} | |||
| </Stack> | |||
| </>} | |||
| {tabIndex === 0 && | |||
| <Box> | |||
| <Grid item xs={12}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| {t("Delivery Detail")} | |||
| </Typography> | |||
| </Grid> | |||
| <StockInForm itemDetail={stockInLineInfo} disabled={viewOnly || showPutaway} /> | |||
| {stockInLineInfo.qcResult ? | |||
| <QcComponent | |||
| itemDetail={stockInLineInfo} | |||
| disabled={viewOnly || showPutaway} | |||
| /> : <LoadingComponent/> | |||
| } | |||
| <Stack direction="row" justifyContent="flex-end" gap={1} sx={{pt:2}}> | |||
| {(!viewOnly && !showPutaway) && (<Button | |||
| id="Submit" | |||
| type="button" | |||
| variant="contained" | |||
| color="primary" | |||
| sx={{ mt: 1 }} | |||
| onClick={formProps.handleSubmit(onSubmitQc, onSubmitErrorQc)} | |||
| > | |||
| {t("confirm qc result")} | |||
| </Button>)} | |||
| </Stack> | |||
| </Box> | |||
| } | |||
| {tabIndex === 1 && | |||
| <Box | |||
| // component="form" | |||
| // onSubmit={formProps.handleSubmit(onSubmitPutaway)} | |||
| > | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| > | |||
| <Grid item xs={12}> | |||
| <PutAwayForm | |||
| itemDetail={itemDetail} | |||
| warehouse={warehouse!} | |||
| disabled={viewOnly} | |||
| setRowModesModel={setPafRowModesModel} | |||
| setRowSelectionModel={setPafRowSelectionModel} | |||
| /> | |||
| </Grid> | |||
| {/* <PutAwayGrid | |||
| itemDetail={itemDetail} | |||
| warehouse={warehouse!} | |||
| disabled={viewOnly} | |||
| /> */} | |||
| <Grid item xs={12}> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Autocomplete | |||
| disableClearable | |||
| options={printerCombo} | |||
| defaultValue={selectedPrinter} | |||
| onChange={(event, value) => { | |||
| setSelectedPrinter(value) | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField | |||
| {...params} | |||
| variant="outlined" | |||
| label={t("Printer")} | |||
| sx={{ width: 300}} | |||
| /> | |||
| )} | |||
| /> | |||
| <TextField | |||
| variant="outlined" | |||
| label={t("Print Qty")} | |||
| defaultValue={printQty} | |||
| onChange={(event) => { | |||
| event.target.value = event.target.value.replace(/[^0-9]/g, '') | |||
| setPrintQty(Number(event.target.value)) | |||
| }} | |||
| sx={{ width: 300}} | |||
| /> | |||
| <Button | |||
| id="printButton" | |||
| type="button" | |||
| variant="contained" | |||
| color="primary" | |||
| sx={{ mt: 1 }} | |||
| onClick={handlePrint} | |||
| disabled={isPrinting || printerCombo.length <= 0 || pafSubmitDisable} | |||
| > | |||
| {isPrinting ? t("Printing") : t("print")} | |||
| </Button> | |||
| {/* <Button | |||
| id="putawaySubmit" | |||
| type="submit" | |||
| variant="contained" | |||
| color="primary" | |||
| sx={{ mt: 1 }} | |||
| onClick={formProps.handleSubmit(onSubmitPutaway)} | |||
| disabled={pafSubmitDisable} | |||
| > | |||
| {t("confirm putaway")} | |||
| </Button> */} | |||
| </Stack> | |||
| </Grid> | |||
| </Grid> | |||
| <Box> | |||
| <PutAwayForm | |||
| itemDetail={stockInLineInfo} | |||
| warehouse={warehouse!} | |||
| disabled={viewOnly} | |||
| setRowModesModel={setPafRowModesModel} | |||
| setRowSelectionModel={setPafRowSelectionModel} | |||
| /> | |||
| </Box> | |||
| } | |||
| </Grid> | |||
| </Grid> | |||
| {tabIndex == 1 && ( | |||
| <Stack direction="row" justifyContent="flex-end" gap={1} sx={{m:3, mt:"auto"}}> | |||
| <Autocomplete | |||
| disableClearable | |||
| options={printerCombo} | |||
| defaultValue={selectedPrinter} | |||
| onChange={(event, value) => { | |||
| setSelectedPrinter(value) | |||
| }} | |||
| renderInput={(params) => ( | |||
| <TextField | |||
| {...params} | |||
| variant="outlined" | |||
| label={t("Printer")} | |||
| sx={{ width: 300}} | |||
| /> | |||
| )} | |||
| /> | |||
| <TextField | |||
| variant="outlined" | |||
| label={t("Print Qty")} | |||
| defaultValue={printQty} | |||
| onChange={(event) => { | |||
| event.target.value = event.target.value.replace(/[^0-9]/g, '') | |||
| setPrintQty(Number(event.target.value)) | |||
| }} | |||
| sx={{ width: 300}} | |||
| /> | |||
| <Button | |||
| id="printButton" | |||
| type="button" | |||
| variant="contained" | |||
| color="primary" | |||
| sx={{ mt: 1 }} | |||
| onClick={handlePrint} | |||
| disabled={isPrinting || printerCombo.length <= 0 || pafSubmitDisable} | |||
| > | |||
| {isPrinting ? t("Printing") : t("print")} | |||
| </Button> | |||
| </Stack> | |||
| )} | |||
| </>) : <LoadingComponent/>} | |||
| </Box> | |||
| </Modal> | |||
| </FormProvider> | |||
| @@ -4,7 +4,7 @@ import { | |||
| PurchaseQcResult, | |||
| PurchaseQCInput, | |||
| StockInInput, | |||
| } from "@/app/api/po/actions"; | |||
| } from "@/app/api/stockIn/actions"; | |||
| import { | |||
| Box, | |||
| Card, | |||
| @@ -34,7 +34,7 @@ import TwoLineCell from "./TwoLineCell"; | |||
| import QcSelect from "./QcSelect"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| 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"; | |||
| @@ -249,6 +249,19 @@ const StockInForm: React.FC<Props> = ({ | |||
| </> | |||
| )} | |||
| <Grid item xs={6}> | |||
| {putawayMode ? ( | |||
| <TextField | |||
| label={t("stockLotNo")} | |||
| fullWidth | |||
| {...register("lotNo", { | |||
| // required: "productLotNo required!", | |||
| })} | |||
| sx={textfieldSx} | |||
| disabled={disabled} | |||
| error={Boolean(errors.productLotNo)} | |||
| helperText={errors.productLotNo?.message} | |||
| />) : ( | |||
| <TextField | |||
| label={t("productLotNo")} | |||
| fullWidth | |||
| @@ -259,7 +272,7 @@ const StockInForm: React.FC<Props> = ({ | |||
| disabled={disabled} | |||
| error={Boolean(errors.productLotNo)} | |||
| helperText={errors.productLotNo?.message} | |||
| /> | |||
| />)} | |||
| </Grid> | |||
| {putawayMode || (<> | |||
| <Grid item xs={6}> | |||
| @@ -350,8 +363,20 @@ const StockInForm: React.FC<Props> = ({ | |||
| }} | |||
| /> | |||
| </Grid> | |||
| {putawayMode || ( | |||
| <Grid item xs={6}> | |||
| <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 | |||
| @@ -361,8 +386,8 @@ const StockInForm: React.FC<Props> = ({ | |||
| sx={textfieldSx} | |||
| disabled={true} | |||
| /> | |||
| </Grid> | |||
| )} | |||
| )} | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("uom")} | |||
| @@ -375,21 +400,8 @@ const StockInForm: React.FC<Props> = ({ | |||
| disabled={true} | |||
| /> | |||
| </Grid> | |||
| {putawayMode ? (<> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("acceptedQty")} | |||
| fullWidth | |||
| sx={textfieldSx} | |||
| disabled={true} | |||
| value={itemDetail.acceptedQty} | |||
| // disabled={true} | |||
| // disabled={disabled} | |||
| // error={Boolean(errors.acceptedQty)} | |||
| // helperText={errors.acceptedQty?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <Grid item xs={6}> | |||
| {putawayMode ? ( | |||
| <TextField | |||
| label={t("processedQty")} | |||
| fullWidth | |||
| @@ -401,9 +413,7 @@ const StockInForm: React.FC<Props> = ({ | |||
| // error={Boolean(errors.acceptedQty)} | |||
| // helperText={errors.acceptedQty?.message} | |||
| /> | |||
| </Grid></> | |||
| ) : ( | |||
| <Grid item xs={6}> | |||
| ) : ( | |||
| <TextField | |||
| label={t("acceptedQty")} | |||
| fullWidth | |||
| @@ -417,9 +427,8 @@ const StockInForm: React.FC<Props> = ({ | |||
| // error={Boolean(errors.acceptedQty)} | |||
| // helperText={errors.acceptedQty?.message} | |||
| /> | |||
| </Grid> | |||
| ) | |||
| } | |||
| )} | |||
| </Grid> | |||
| {/* <Grid item xs={4}> | |||
| <TextField | |||
| label={t("acceptedWeight")} | |||
| @@ -15,7 +15,7 @@ import { useSession } from "next-auth/react"; | |||
| import { defaultPagingController } from "../SearchResults/SearchResults"; | |||
| import { fetchPoListClient, testing } from "@/app/api/po/actions"; | |||
| import dayjs from "dayjs"; | |||
| import { arrayToDateString, OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||
| import { arrayToDateString, dayjsToInputDateStringFIX } from "@/app/utils/formatUtil"; | |||
| import arraySupport from "dayjs/plugin/arraySupport"; | |||
| import { Checkbox, Box } from "@mui/material"; | |||
| dayjs.extend(arraySupport); | |||
| @@ -38,7 +38,7 @@ const PoSearch: React.FC<Props> = ({ | |||
| const [selectedPoIds, setSelectedPoIds] = useState<number[]>([]); | |||
| const [selectAll, setSelectAll] = useState(false); | |||
| const [filteredPo, setFilteredPo] = useState<PoResult[]>(po); | |||
| const [filterArgs, setFilterArgs] = useState<Record<string, any>>({}); | |||
| const [filterArgs, setFilterArgs] = useState<Record<string, any>>({estimatedArrivalDate : dayjsToInputDateStringFIX(dayjs())}); | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| const router = useRouter(); | |||
| const [pagingController, setPagingController] = useState( | |||
| @@ -66,7 +66,8 @@ const PoSearch: React.FC<Props> = ({ | |||
| { label: t(`completed`), value: `completed` }, | |||
| ], | |||
| }, | |||
| { label: t("ETA"), label2: t("ETA To"), paramName: "estimatedArrivalDate", type: "dateRange" }, | |||
| { label: t("ETA"), label2: t("ETA To"), paramName: "estimatedArrivalDate", type: "dateRange", | |||
| preFilledValue: dayjsToInputDateStringFIX(dayjs()) }, | |||
| ]; | |||
| return searchCriteria; | |||
| @@ -19,11 +19,10 @@ import ReactQrCodeScanner, { | |||
| import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||
| import { | |||
| fetchStockInLineInfo, | |||
| ModalFormInput, | |||
| StockInLineEntry, | |||
| updateStockInLine, | |||
| } from "@/app/api/po/actions"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| } from "@/app/api/stockIn/actions"; | |||
| import { ModalFormInput, StockInLine } from "@/app/api/stockIn"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| // import { QrCodeInfo } from "@/app/api/qrcde"; | |||
| import { Check, QrCode, ErrorOutline, CheckCircle } from "@mui/icons-material"; | |||
| @@ -94,7 +93,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| { | |||
| ...itemDetail, | |||
| // status: itemDetail.status ?? "pending", | |||
| dnDate: arrayToDateString(itemDetail?.dnDate, "input")?? undefined, | |||
| // dnDate: arrayToDateString(itemDetail?.dnDate, "input")?? undefined, | |||
| // // putAwayLines: dummyPutAwayLine, | |||
| // // putAwayLines: itemDetail.putAwayLines.map((line) => (return {...line, printQty: 1})) ?? [], | |||
| // putAwayLines: itemDetail.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false})) ?? [], | |||
| @@ -105,7 +104,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| receiptDate: itemDetail?.receiptDate ? arrayToDateString(itemDetail?.receiptDate, "input") : undefined, | |||
| // acceptQty: itemDetail.demandQty?? itemDetail.acceptedQty, | |||
| defaultWarehouseId: itemDetail?.defaultWarehouseId ?? 1, | |||
| } | |||
| } as ModalFormInput | |||
| ) | |||
| }, [itemDetail]) | |||
| @@ -218,7 +217,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| useEffect(() => { | |||
| if (stockInLineId) { fetchStockInLine(stockInLineId); } | |||
| }, [stockInLineId]); | |||
| }, [stockInLineId]); | |||
| const validateQty = useCallback((qty : number = putQty) => { | |||
| // if (isNaN(putQty) || putQty === undefined || putQty === null || typeof(putQty) != "number") { | |||
| @@ -15,13 +15,7 @@ import { | |||
| ScannerConfig, | |||
| } from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||
| import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||
| import { | |||
| fetchStockInLineInfo, | |||
| ModalFormInput, | |||
| StockInLineEntry, | |||
| updateStockInLine, | |||
| } from "@/app/api/po/actions"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { StockInLine } from "@/app/api/stockIn"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| import { QrCodeInfo } from "@/app/api/qrcode"; | |||
| import { Check, QrCodeScanner, Warehouse } from "@mui/icons-material"; | |||
| @@ -36,6 +36,8 @@ interface BaseCriterion<T extends string> { | |||
| paramName: T; | |||
| paramName2?: T; | |||
| // options?: T[] | string[]; | |||
| defaultValue?: string; | |||
| preFilledValue?: string; | |||
| filterObj?: T; | |||
| handleSelectionChange?: (selectedOptions: T[]) => void; | |||
| } | |||
| @@ -136,18 +138,31 @@ function SearchBox<T extends string>({ | |||
| if (c.type === "dateRange") { | |||
| tempCriteria = { | |||
| ...tempCriteria, | |||
| [c.paramName]: "", | |||
| [c.paramName]: c.defaultValue ?? "", | |||
| [`${c.paramName}To`]: "", | |||
| }; | |||
| } | |||
| return tempCriteria; | |||
| }, | |||
| {} as Record<T | `${T}To`, string>, | |||
| ), | |||
| [criteria], | |||
| ); | |||
| const [inputs, setInputs] = useState(defaultInputs); | |||
| const preFilledInputs = useMemo(() => { | |||
| const preFilledCriteria = criteria.reduce<Record<T | `${T}To`, string>>( | |||
| (acc, c) => { | |||
| if (c.preFilledValue !== undefined) { | |||
| return { | |||
| ...acc, | |||
| [c.paramName]: c.preFilledValue, | |||
| }; | |||
| } else return acc; | |||
| }, | |||
| {} as Record<T | `${T}To`, string>,); | |||
| return {...defaultInputs, ...preFilledCriteria} | |||
| }, [defaultInputs]) | |||
| const [inputs, setInputs] = useState(preFilledInputs); | |||
| const [isReset, setIsReset] = useState(false); | |||
| const makeInputChangeHandler = useCallback( | |||
| @@ -44,7 +44,7 @@ | |||
| "price": "訂單貨值", | |||
| "processedQty": "已上架數量", | |||
| "expiryDate": "到期日", | |||
| "acceptedQty": "是次來貨數量", | |||
| "acceptedQty": "本批收貨數量", | |||
| "putawayQty": "上架數量", | |||
| "acceptQty": "揀收數量", | |||
| "printQty": "列印數量", | |||
| @@ -91,7 +91,7 @@ | |||
| "to be processed": "待處理", | |||
| "supervisor": "管理層", | |||
| "Stock In Detail": "入庫詳情", | |||
| "productLotNo": "貨品批號", | |||
| "productLotNo": "來貨批號", | |||
| "receiptDate": "收貨日期", | |||
| "acceptedWeight": "接受重量", | |||
| "productionDate": "生產日期", | |||
| @@ -100,7 +100,7 @@ | |||
| "Select warehouse": "選擇倉庫", | |||
| "Putaway Detail": "上架詳情", | |||
| "Delivery Detail": "來貨詳情", | |||
| "LotNo": "批號", | |||
| "stockLotNo": "入倉批號", | |||
| "Po Code": "採購訂單編號", | |||
| "No Warehouse": "沒有倉庫", | |||
| "Please scan warehouse qr code.": "請掃描倉庫 QR 碼。", | |||