# Conflicts: # src/components/FinishedGoodSearch/FinishedGoodSearch.tsx # src/i18n/zh/pickOrder.jsonmaster
| @@ -267,6 +267,84 @@ export interface AutoAssignReleaseByStoreRequest { | |||||
| userId: number; | userId: number; | ||||
| storeId: string; // "2/F" | "4/F" | storeId: string; // "2/F" | "4/F" | ||||
| } | } | ||||
| export interface UpdateDoPickOrderHideStatusRequest { | |||||
| id: number; | |||||
| name: string; | |||||
| code: string; | |||||
| type: string; | |||||
| message: string; | |||||
| errorPosition: string; | |||||
| } | |||||
| export interface CompletedDoPickOrderResponse { | |||||
| id: number; | |||||
| pickOrderId: number; | |||||
| pickOrderCode: string; | |||||
| pickOrderConsoCode: string; | |||||
| pickOrderStatus: string; | |||||
| deliveryOrderId: number; | |||||
| deliveryNo: string; | |||||
| deliveryDate: string; | |||||
| shopId: number; | |||||
| shopCode: string; | |||||
| shopName: string; | |||||
| shopAddress: string; | |||||
| ticketNo: string; | |||||
| shopPoNo: string; | |||||
| numberOfCartons: number; | |||||
| truckNo: string; | |||||
| storeId: string; | |||||
| completedDate: string; | |||||
| fgPickOrders: FGPickOrderResponse[]; | |||||
| } | |||||
| // ✅ 新增:搜索参数接口 | |||||
| export interface CompletedDoPickOrderSearchParams { | |||||
| pickOrderCode?: string; | |||||
| shopName?: string; | |||||
| deliveryNo?: string; | |||||
| ticketNo?: string; | |||||
| } | |||||
| // ✅ 新增:获取已完成的 DO Pick Orders API | |||||
| export const fetchCompletedDoPickOrders = async ( | |||||
| userId: number, | |||||
| searchParams?: CompletedDoPickOrderSearchParams | |||||
| ): Promise<CompletedDoPickOrderResponse[]> => { | |||||
| const params = new URLSearchParams(); | |||||
| if (searchParams?.pickOrderCode) { | |||||
| params.append('pickOrderCode', searchParams.pickOrderCode); | |||||
| } | |||||
| if (searchParams?.shopName) { | |||||
| params.append('shopName', searchParams.shopName); | |||||
| } | |||||
| if (searchParams?.deliveryNo) { | |||||
| params.append('deliveryNo', searchParams.deliveryNo); | |||||
| } | |||||
| if (searchParams?.ticketNo) { | |||||
| params.append('ticketNo', searchParams.ticketNo); | |||||
| } | |||||
| const queryString = params.toString(); | |||||
| const url = `${BASE_API_URL}/pickOrder/completed-do-pick-orders/${userId}${queryString ? `?${queryString}` : ''}`; | |||||
| const response = await serverFetchJson<CompletedDoPickOrderResponse[]>(url, { | |||||
| method: "GET", | |||||
| }); | |||||
| return response; | |||||
| }; | |||||
| export const updatePickOrderHideStatus = async (pickOrderId: number, hide: boolean) => { | |||||
| const response = await serverFetchJson<UpdateDoPickOrderHideStatusRequest>( | |||||
| `${BASE_API_URL}/pickOrder/update-hide-status/${pickOrderId}?hide=${hide}`, | |||||
| { | |||||
| method: "POST", | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }, | |||||
| ); | |||||
| revalidateTag("pickorder"); | |||||
| return response; | |||||
| }; | |||||
| export const fetchFGPickOrders = async (pickOrderId: number) => { | export const fetchFGPickOrders = async (pickOrderId: number) => { | ||||
| const response = await serverFetchJson<FGPickOrderResponse>( | const response = await serverFetchJson<FGPickOrderResponse>( | ||||
| @@ -581,7 +659,28 @@ const fetchSuggestionsWithStatus = async (pickOrderLineId: number) => { | |||||
| return []; | return []; | ||||
| } | } | ||||
| }; | }; | ||||
| export const fetchAllPickOrderLotsHierarchical = cache(async (userId: number): Promise<any> => { | |||||
| try { | |||||
| console.log("🔍 Fetching hierarchical pick order lots for userId:", userId); | |||||
| const data = await serverFetchJson<any>( | |||||
| `${BASE_API_URL}/pickOrder/all-lots-hierarchical/${userId}`, | |||||
| { | |||||
| method: 'GET', | |||||
| next: { tags: ["pickorder"] }, | |||||
| } | |||||
| ); | |||||
| console.log("✅ Fetched hierarchical lot details:", data); | |||||
| return data; | |||||
| } catch (error) { | |||||
| console.error("❌ Error fetching hierarchical lot details:", error); | |||||
| return { | |||||
| pickOrder: null, | |||||
| pickOrderLines: [] | |||||
| }; | |||||
| } | |||||
| }); | |||||
| // Update the existing function to use the non-auto-assign endpoint | // Update the existing function to use the non-auto-assign endpoint | ||||
| export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Promise<any[]> => { | export const fetchALLPickOrderLineLotDetails = cache(async (userId: number): Promise<any[]> => { | ||||
| try { | try { | ||||
| @@ -4,13 +4,14 @@ import { BASE_API_URL } from "../../../config/api"; | |||||
| // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | ||||
| import { revalidateTag } from "next/cache"; | import { revalidateTag } from "next/cache"; | ||||
| import { cache } from "react"; | import { cache } from "react"; | ||||
| import { PoResult, StockInLine } from "."; | |||||
| import { PoResult } from "."; | |||||
| //import { serverFetchJson } from "@/app/utils/fetchUtil"; | //import { serverFetchJson } from "@/app/utils/fetchUtil"; | ||||
| import { serverFetchJson, serverFetchWithNoContent } from "../../utils/fetchUtil"; | import { serverFetchJson, serverFetchWithNoContent } from "../../utils/fetchUtil"; | ||||
| import { QcItemResult } from "../settings/qcItem"; | import { QcItemResult } from "../settings/qcItem"; | ||||
| import { RecordsRes } from "../utils"; | import { RecordsRes } from "../utils"; | ||||
| import { Uom } from "../settings/uom"; | import { Uom } from "../settings/uom"; | ||||
| import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | ||||
| import { StockInLine } from "../stockIn"; | |||||
| // import { BASE_API_URL } from "@/config/api"; | // import { BASE_API_URL } from "@/config/api"; | ||||
| export interface PostStockInLineResponse<T> { | export interface PostStockInLineResponse<T> { | ||||
| @@ -24,11 +25,12 @@ export interface PostStockInLineResponse<T> { | |||||
| // entity: StockInLine | StockInLine[] | // entity: StockInLine | StockInLine[] | ||||
| } | } | ||||
| // DEPRECIATED | |||||
| export interface StockInLineEntry { | export interface StockInLineEntry { | ||||
| id?: number; | id?: number; | ||||
| itemId: number; | itemId: number; | ||||
| purchaseOrderId: number; | |||||
| purchaseOrderLineId: number; | |||||
| purchaseOrderId?: number; | |||||
| purchaseOrderLineId?: number; | |||||
| acceptedQty: number; | acceptedQty: number; | ||||
| status?: string; | status?: string; | ||||
| expiryDate?: string; | expiryDate?: string; | ||||
| @@ -38,6 +40,7 @@ export interface StockInLineEntry { | |||||
| dnNo?: string; | dnNo?: string; | ||||
| } | } | ||||
| // DEPRECIATED | |||||
| export interface PurchaseQcResult{ | export interface PurchaseQcResult{ | ||||
| id?: number; | id?: number; | ||||
| qcItemId: number; | qcItemId: number; | ||||
| @@ -46,6 +49,7 @@ export interface PurchaseQcResult{ | |||||
| remarks?: string; | remarks?: string; | ||||
| escalationLogId?: number; | escalationLogId?: number; | ||||
| } | } | ||||
| // DEPRECIATED | |||||
| export interface StockInInput { | export interface StockInInput { | ||||
| status: string; | status: string; | ||||
| poCode: string; | poCode: string; | ||||
| @@ -53,6 +57,7 @@ export interface StockInInput { | |||||
| dnNo?: string; | dnNo?: string; | ||||
| dnDate?: string; | dnDate?: string; | ||||
| itemName: string; | itemName: string; | ||||
| lotNo?: string; | |||||
| invoiceNo?: string; | invoiceNo?: string; | ||||
| receiptDate: string; | receiptDate: string; | ||||
| supplier: string; | supplier: string; | ||||
| @@ -64,6 +69,7 @@ export interface StockInInput { | |||||
| expiryDate: string; | expiryDate: string; | ||||
| uom: Uom; | uom: Uom; | ||||
| } | } | ||||
| // DEPRECIATED | |||||
| export interface PurchaseQCInput { | export interface PurchaseQCInput { | ||||
| status: string; | status: string; | ||||
| acceptQty: number; | acceptQty: number; | ||||
| @@ -75,6 +81,7 @@ export interface PurchaseQCInput { | |||||
| qcDecision?: number; | qcDecision?: number; | ||||
| qcResult: PurchaseQcResult[]; | qcResult: PurchaseQcResult[]; | ||||
| } | } | ||||
| // DEPRECIATED | |||||
| export interface EscalationInput { | export interface EscalationInput { | ||||
| status: string; | status: string; | ||||
| remarks?: string; | remarks?: string; | ||||
| @@ -84,6 +91,8 @@ export interface EscalationInput { | |||||
| acceptedQty?: number; // this is the qty to be escalated | acceptedQty?: number; // this is the qty to be escalated | ||||
| // escalationQty: number | // escalationQty: number | ||||
| } | } | ||||
| // DEPRECIATED | |||||
| export interface PutAwayLine { | export interface PutAwayLine { | ||||
| id?: number | id?: number | ||||
| qty: number | qty: number | ||||
| @@ -92,6 +101,8 @@ export interface PutAwayLine { | |||||
| printQty: number; | printQty: number; | ||||
| _isNew?: boolean; | _isNew?: boolean; | ||||
| } | } | ||||
| // DEPRECIATED | |||||
| export interface PutAwayInput { | export interface PutAwayInput { | ||||
| status: string; | status: string; | ||||
| acceptedQty: number; | acceptedQty: number; | ||||
| @@ -99,12 +110,14 @@ export interface PutAwayInput { | |||||
| putAwayLines: PutAwayLine[] | putAwayLines: PutAwayLine[] | ||||
| } | } | ||||
| // DEPRECIATED | |||||
| export type ModalFormInput = Partial< | export type ModalFormInput = Partial< | ||||
| PurchaseQCInput & StockInInput & PutAwayInput | PurchaseQCInput & StockInInput & PutAwayInput | ||||
| > & { | > & { | ||||
| escalationLog? : Partial<EscalationInput> | escalationLog? : Partial<EscalationInput> | ||||
| }; | }; | ||||
| // DEPRECIATED | |||||
| export interface PrintQrCodeForSilRequest { | export interface PrintQrCodeForSilRequest { | ||||
| stockInLineId: number; | stockInLineId: number; | ||||
| printerId: number; | printerId: number; | ||||
| @@ -117,6 +130,7 @@ export const testFetch = cache(async (id: number) => { | |||||
| }); | }); | ||||
| }); | }); | ||||
| // DEPRECIATED | |||||
| export const fetchStockInLineInfo = cache(async (stockInLineId: number) => { | export const fetchStockInLineInfo = cache(async (stockInLineId: number) => { | ||||
| return serverFetchJson<StockInLine>( | return serverFetchJson<StockInLine>( | ||||
| `${BASE_API_URL}/stockInLine/${stockInLineId}`, | `${BASE_API_URL}/stockInLine/${stockInLineId}`, | ||||
| @@ -126,6 +140,7 @@ export const fetchStockInLineInfo = cache(async (stockInLineId: number) => { | |||||
| ); | ); | ||||
| }); | }); | ||||
| // DEPRECIATED | |||||
| export const createStockInLine = async (data: StockInLineEntry) => { | export const createStockInLine = async (data: StockInLineEntry) => { | ||||
| const stockInLine = await serverFetchJson< | const stockInLine = await serverFetchJson< | ||||
| PostStockInLineResponse<StockInLine> | PostStockInLineResponse<StockInLine> | ||||
| @@ -138,6 +153,7 @@ export const createStockInLine = async (data: StockInLineEntry) => { | |||||
| return stockInLine; | return stockInLine; | ||||
| }; | }; | ||||
| // DEPRECIATED | |||||
| export const updateStockInLine = async ( | export const updateStockInLine = async ( | ||||
| data: StockInLineEntry & ModalFormInput, | data: StockInLineEntry & ModalFormInput, | ||||
| ) => { | ) => { | ||||
| @@ -228,6 +244,7 @@ export const testing = cache(async (queryParams?: Record<string, any>) => { | |||||
| } | } | ||||
| }); | }); | ||||
| // DEPRECIATED | |||||
| export const printQrCodeForSil = cache(async(data: PrintQrCodeForSilRequest) => { | export const printQrCodeForSil = cache(async(data: PrintQrCodeForSilRequest) => { | ||||
| const params = convertObjToURLSearchParams(data) | const params = convertObjToURLSearchParams(data) | ||||
| return serverFetchWithNoContent(`${BASE_API_URL}/stockInLine/printQrCode?${params}`, | 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 { Uom } from "../settings/uom"; | ||||
| import { RecordsRes } from "../utils"; | import { RecordsRes } from "../utils"; | ||||
| import { PutAwayLine } from "./actions"; | import { PutAwayLine } from "./actions"; | ||||
| import { StockInLine } from "../stockIn"; | |||||
| export enum StockInStatus { | export enum StockInStatus { | ||||
| PENDING = "pending", | PENDING = "pending", | ||||
| @@ -60,7 +61,8 @@ export interface StockUomForPoLine { | |||||
| purchaseRatioD: number; | purchaseRatioD: number; | ||||
| } | } | ||||
| export interface StockInLine { | |||||
| // DEPRECIATED | |||||
| export interface StockInLine_old { | |||||
| id: number; | id: number; | ||||
| stockInId?: number; | stockInId?: number; | ||||
| purchaseOrderId?: number; | purchaseOrderId?: number; | ||||
| @@ -15,6 +15,14 @@ export interface QcItemWithChecks { | |||||
| description: string | undefined; | description: string | undefined; | ||||
| } | } | ||||
| export interface QcResult{ | |||||
| id?: number; | |||||
| qcItemId: number; | |||||
| qcPassed?: boolean; | |||||
| failQty?: number; | |||||
| remarks?: string; | |||||
| escalationLogId?: number; | |||||
| } | |||||
| export interface QcData { | export interface QcData { | ||||
| id?: number, | id?: number, | ||||
| qcItemId: 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); | 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); | 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); | 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}`); | 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) => { | export const outputDateStringToInputDateString = (date: string) => { | ||||
| return dayjsToInputDateString(dateStringToDayjs(date)) | return dayjsToInputDateString(dateStringToDayjs(date)) | ||||
| } | } | ||||
| @@ -64,7 +64,7 @@ const DoDetail: React.FC<Props> = ({ | |||||
| if (response) { | if (response) { | ||||
| formProps.setValue("status", response.entity.status) | formProps.setValue("status", response.entity.status) | ||||
| setSuccessMessage("DO released successfully! Pick orders created.") | |||||
| setSuccessMessage(t("DO released successfully! Pick orders created.")) | |||||
| } | } | ||||
| } | } | ||||
| } catch (e) { | } catch (e) { | ||||
| @@ -109,16 +109,7 @@ const FGPickOrderCard: React.FC<Props> = ({ fgOrder, onQrCodeClick }) => { | |||||
| value={fgOrder.ticketNo} | value={fgOrder.ticketNo} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'flex-end', mt: 2 }}> | |||||
| <Button | |||||
| variant="contained" | |||||
| startIcon={<QrCodeIcon />} | |||||
| onClick={() => onQrCodeClick(fgOrder.pickOrderId)} | |||||
| sx={{ minWidth: 120 }} | |||||
| > | |||||
| {t("Print DN/Label")} | |||||
| </Button> | |||||
| </Grid> | |||||
| </Grid> | </Grid> | ||||
| </Box> | </Box> | ||||
| </CardContent> | </CardContent> | ||||
| @@ -29,6 +29,7 @@ import Jobcreatitem from "./Jobcreatitem"; | |||||
| import { useSession } from "next-auth/react"; | import { useSession } from "next-auth/react"; | ||||
| import { SessionWithTokens } from "@/config/authConfig"; | import { SessionWithTokens } from "@/config/authConfig"; | ||||
| import PickExecutionDetail from "./GoodPickExecutiondetail"; | import PickExecutionDetail from "./GoodPickExecutiondetail"; | ||||
| import GoodPickExecutionRecord from "./GoodPickExecutionRecord"; | |||||
| import Swal from "sweetalert2"; | import Swal from "sweetalert2"; | ||||
| import { PrintDeliveryNoteRequest, printDN } from "@/app/api/do/actions"; | import { PrintDeliveryNoteRequest, printDN } from "@/app/api/do/actions"; | ||||
| @@ -50,12 +51,24 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| const [isOpenCreateModal, setIsOpenCreateModal] = useState(false) | const [isOpenCreateModal, setIsOpenCreateModal] = useState(false) | ||||
| const [items, setItems] = useState<ItemCombo[]>([]) | const [items, setItems] = useState<ItemCombo[]>([]) | ||||
| const [printButtonsEnabled, setPrintButtonsEnabled] = useState(false); | |||||
| const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders); | const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders); | ||||
| const [filterArgs, setFilterArgs] = useState<Record<string, any>>({}); | const [filterArgs, setFilterArgs] = useState<Record<string, any>>({}); | ||||
| const [searchQuery, setSearchQuery] = useState<Record<string, any>>({}); | const [searchQuery, setSearchQuery] = useState<Record<string, any>>({}); | ||||
| const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
| const [totalCount, setTotalCount] = useState<number>(); | const [totalCount, setTotalCount] = useState<number>(); | ||||
| const [isAssigning, setIsAssigning] = useState(false); | const [isAssigning, setIsAssigning] = useState(false); | ||||
| const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>( | |||||
| typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true' | |||||
| ); | |||||
| useEffect(() => { | |||||
| const onAssigned = () => { | |||||
| localStorage.removeItem('hideCompletedUntilNext'); | |||||
| setHideCompletedUntilNext(false); | |||||
| }; | |||||
| window.addEventListener('pickOrderAssigned', onAssigned); | |||||
| return () => window.removeEventListener('pickOrderAssigned', onAssigned); | |||||
| }, []); | |||||
| const [fgPickOrdersData, setFgPickOrdersData] = useState<FGPickOrderResponse[]>([]); | const [fgPickOrdersData, setFgPickOrdersData] = useState<FGPickOrderResponse[]>([]); | ||||
| @@ -231,6 +244,19 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| } | } | ||||
| } | } | ||||
| }, [tabIndex, items.length]); | }, [tabIndex, items.length]); | ||||
| useEffect(() => { | |||||
| const handleCompletionStatusChange = (event: CustomEvent) => { | |||||
| const { allLotsCompleted } = event.detail; | |||||
| setPrintButtonsEnabled(allLotsCompleted); | |||||
| console.log("Print buttons enabled:", allLotsCompleted); | |||||
| }; | |||||
| window.addEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener); | |||||
| return () => { | |||||
| window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener); | |||||
| }; | |||||
| }, []); | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | ||||
| () => { | () => { | ||||
| @@ -391,6 +417,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | |||||
| {/* First 4 buttons aligned left */} | {/* First 4 buttons aligned left */} | ||||
| @@ -404,7 +431,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| </Grid> | </Grid> | ||||
| {/* Last 2 buttons aligned right */} | {/* Last 2 buttons aligned right */} | ||||
| <Grid item xs={6} display="flex" justifyContent="flex-end"> | |||||
| <Grid item xs={6} > | |||||
| <Stack direction="row" spacing={1}> | <Stack direction="row" spacing={1}> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| @@ -422,6 +449,56 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| </Button> | </Button> | ||||
| </Stack> | </Stack> | ||||
| </Grid> | </Grid> | ||||
| {/* ✅ Updated print buttons with completion status */} | |||||
| <Grid item xs={6} display="flex" justifyContent="flex-end"> | |||||
| <Stack direction="row" spacing={1}> | |||||
| {/* | |||||
| <Button | |||||
| variant={hideCompletedUntilNext ? "contained" : "outlined"} | |||||
| color={hideCompletedUntilNext ? "warning" : "inherit"} | |||||
| onClick={() => { | |||||
| const next = !hideCompletedUntilNext; | |||||
| setHideCompletedUntilNext(next); | |||||
| if (next) localStorage.setItem('hideCompletedUntilNext', 'true'); | |||||
| else localStorage.removeItem('hideCompletedUntilNext'); | |||||
| window.dispatchEvent(new Event('pickOrderAssigned')); // ask detail to re-fetch | |||||
| }} | |||||
| > | |||||
| {hideCompletedUntilNext ? t("Hide Completed: ON") : t("Hide Completed: OFF")} | |||||
| </Button> | |||||
| */} | |||||
| <Button | |||||
| variant="contained" | |||||
| disabled={!printButtonsEnabled} | |||||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||||
| > | |||||
| {t("Print Draft")} | |||||
| </Button> | |||||
| <Button | |||||
| variant="contained" | |||||
| disabled={!printButtonsEnabled} | |||||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||||
| > | |||||
| {t("Print Pick Order and DN Label")} | |||||
| </Button> | |||||
| <Button | |||||
| variant="contained" | |||||
| disabled={!printButtonsEnabled} | |||||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||||
| > | |||||
| {t("Print Pick Order")} | |||||
| </Button> | |||||
| <Button | |||||
| variant="contained" | |||||
| disabled={!printButtonsEnabled} | |||||
| title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""} | |||||
| > | |||||
| {t("Print DN Label")} | |||||
| </Button> | |||||
| </Stack> | |||||
| </Grid> | |||||
| </Grid> | </Grid> | ||||
| </Stack> | </Stack> | ||||
| </Box> | </Box> | ||||
| @@ -433,6 +510,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | ||||
| <Tab label={t("Pick Order Detail")} iconPosition="end" /> | <Tab label={t("Pick Order Detail")} iconPosition="end" /> | ||||
| <Tab label={t("Pick Execution Detail")} iconPosition="end" /> | <Tab label={t("Pick Execution Detail")} iconPosition="end" /> | ||||
| <Tab label={t("Pick Execution Record")} iconPosition="end" /> | |||||
| </Tabs> | </Tabs> | ||||
| </Box> | </Box> | ||||
| @@ -443,6 +521,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => { | |||||
| }}> | }}> | ||||
| {tabIndex === 0 && <PickExecution filterArgs={filterArgs} />} | {tabIndex === 0 && <PickExecution filterArgs={filterArgs} />} | ||||
| {tabIndex === 1 && <PickExecutionDetail filterArgs={filterArgs} />} | {tabIndex === 1 && <PickExecutionDetail filterArgs={filterArgs} />} | ||||
| {tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} />} | |||||
| </Box> | </Box> | ||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| @@ -351,7 +351,7 @@ const PickExecutionForm: React.FC<PickExecutionFormProps> = ({ | |||||
| </DialogContent> | </DialogContent> | ||||
| <DialogActions> | <DialogActions> | ||||
| <Button onClick={handleClose} disabled={loading}> | <Button onClick={handleClose} disabled={loading}> | ||||
| {t('cancel')} | |||||
| {t('Cancel')} | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| onClick={handleSubmit} | onClick={handleSubmit} | ||||
| @@ -0,0 +1,416 @@ | |||||
| "use client"; | |||||
| import { | |||||
| Box, | |||||
| Button, | |||||
| Stack, | |||||
| TextField, | |||||
| Typography, | |||||
| Alert, | |||||
| CircularProgress, | |||||
| Table, | |||||
| TableBody, | |||||
| TableCell, | |||||
| TableContainer, | |||||
| TableHead, | |||||
| TableRow, | |||||
| Paper, | |||||
| TablePagination, | |||||
| Modal, | |||||
| Card, | |||||
| CardContent, | |||||
| CardActions, | |||||
| Chip, | |||||
| Accordion, | |||||
| AccordionSummary, | |||||
| AccordionDetails, | |||||
| } from "@mui/material"; | |||||
| import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | |||||
| import { useCallback, useEffect, useState, useRef, useMemo } from "react"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import { useRouter } from "next/navigation"; | |||||
| import { | |||||
| fetchALLPickOrderLineLotDetails, | |||||
| updateStockOutLineStatus, | |||||
| createStockOutLine, | |||||
| recordPickExecutionIssue, | |||||
| fetchFGPickOrders, | |||||
| FGPickOrderResponse, | |||||
| autoAssignAndReleasePickOrder, | |||||
| AutoAssignReleaseResponse, | |||||
| checkPickOrderCompletion, | |||||
| PickOrderCompletionResponse, | |||||
| checkAndCompletePickOrderByConsoCode, | |||||
| fetchCompletedDoPickOrders, // ✅ 新增:使用新的 API | |||||
| CompletedDoPickOrderResponse, | |||||
| CompletedDoPickOrderSearchParams // ✅ 修复:导入类型 | |||||
| } from "@/app/api/pickOrder/actions"; | |||||
| import { fetchNameList, NameList } from "@/app/api/user/actions"; | |||||
| import { | |||||
| FormProvider, | |||||
| useForm, | |||||
| } from "react-hook-form"; | |||||
| import SearchBox, { Criterion } from "../SearchBox"; | |||||
| import { CreateStockOutLine } from "@/app/api/pickOrder/actions"; | |||||
| import { updateInventoryLotLineQuantities } from "@/app/api/inventory/actions"; | |||||
| import QrCodeIcon from '@mui/icons-material/QrCode'; | |||||
| import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider'; | |||||
| import { useSession } from "next-auth/react"; | |||||
| import { SessionWithTokens } from "@/config/authConfig"; | |||||
| import { fetchStockInLineInfo } from "@/app/api/po/actions"; | |||||
| import GoodPickExecutionForm from "./GoodPickExecutionForm"; | |||||
| import FGPickOrderCard from "./FGPickOrderCard"; | |||||
| interface Props { | |||||
| filterArgs: Record<string, any>; | |||||
| } | |||||
| // ✅ 新增:已完成的 DO Pick Order 接口 | |||||
| interface CompletedDoPickOrder { | |||||
| id: number; | |||||
| pickOrderId: number; | |||||
| pickOrderCode: string; | |||||
| pickOrderConsoCode: string; | |||||
| pickOrderStatus: string; | |||||
| deliveryOrderId: number; | |||||
| deliveryNo: string; | |||||
| deliveryDate: string; | |||||
| shopId: number; | |||||
| shopCode: string; | |||||
| shopName: string; | |||||
| shopAddress: string; | |||||
| ticketNo: string; | |||||
| shopPoNo: string; | |||||
| numberOfCartons: number; | |||||
| truckNo: string; | |||||
| storeId: string; | |||||
| completedDate: string; | |||||
| fgPickOrders: FGPickOrderResponse[]; | |||||
| } | |||||
| // ✅ 新增:Pick Order 数据接口 | |||||
| interface PickOrderData { | |||||
| pickOrderId: number; | |||||
| pickOrderCode: string; | |||||
| pickOrderConsoCode: string; | |||||
| pickOrderStatus: string; | |||||
| completedDate: string; | |||||
| lots: any[]; | |||||
| } | |||||
| const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| const { t } = useTranslation("pickOrder"); | |||||
| const router = useRouter(); | |||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | |||||
| // ✅ 新增:已完成 DO Pick Orders 状态 | |||||
| const [completedDoPickOrders, setCompletedDoPickOrders] = useState<CompletedDoPickOrder[]>([]); | |||||
| const [completedDoPickOrdersLoading, setCompletedDoPickOrdersLoading] = useState(false); | |||||
| // ✅ 新增:详情视图状态 | |||||
| const [selectedDoPickOrder, setSelectedDoPickOrder] = useState<CompletedDoPickOrder | null>(null); | |||||
| const [showDetailView, setShowDetailView] = useState(false); | |||||
| const [detailLotData, setDetailLotData] = useState<any[]>([]); | |||||
| // ✅ 新增:搜索状态 | |||||
| const [searchQuery, setSearchQuery] = useState<Record<string, any>>({}); | |||||
| const [filteredDoPickOrders, setFilteredDoPickOrders] = useState<CompletedDoPickOrder[]>([]); | |||||
| // ✅ 新增:分页状态 | |||||
| const [paginationController, setPaginationController] = useState({ | |||||
| pageNum: 0, | |||||
| pageSize: 10, | |||||
| }); | |||||
| const formProps = useForm(); | |||||
| const errors = formProps.formState.errors; | |||||
| // ✅ 修改:使用新的 API 获取已完成的 DO Pick Orders | |||||
| const fetchCompletedDoPickOrdersData = useCallback(async (searchParams?: CompletedDoPickOrderSearchParams) => { | |||||
| if (!currentUserId) return; | |||||
| setCompletedDoPickOrdersLoading(true); | |||||
| try { | |||||
| console.log("🔍 Fetching completed DO pick orders with params:", searchParams); | |||||
| const completedDoPickOrders = await fetchCompletedDoPickOrders(currentUserId, searchParams); | |||||
| setCompletedDoPickOrders(completedDoPickOrders); | |||||
| setFilteredDoPickOrders(completedDoPickOrders); | |||||
| console.log("✅ Fetched completed DO pick orders:", completedDoPickOrders); | |||||
| } catch (error) { | |||||
| console.error("❌ Error fetching completed DO pick orders:", error); | |||||
| setCompletedDoPickOrders([]); | |||||
| setFilteredDoPickOrders([]); | |||||
| } finally { | |||||
| setCompletedDoPickOrdersLoading(false); | |||||
| } | |||||
| }, [currentUserId]); | |||||
| // ✅ 初始化时获取数据 | |||||
| useEffect(() => { | |||||
| if (currentUserId) { | |||||
| fetchCompletedDoPickOrdersData(); | |||||
| } | |||||
| }, [currentUserId, fetchCompletedDoPickOrdersData]); | |||||
| // ✅ 修改:搜索功能使用新的 API | |||||
| const handleSearch = useCallback((query: Record<string, any>) => { | |||||
| setSearchQuery({ ...query }); | |||||
| console.log("Search query:", query); | |||||
| const searchParams: CompletedDoPickOrderSearchParams = { | |||||
| pickOrderCode: query.pickOrderCode || undefined, | |||||
| shopName: query.shopName || undefined, | |||||
| deliveryNo: query.deliveryNo || undefined, | |||||
| ticketNo: query.ticketNo || undefined, | |||||
| }; | |||||
| // 使用新的 API 进行搜索 | |||||
| fetchCompletedDoPickOrdersData(searchParams); | |||||
| }, [fetchCompletedDoPickOrdersData]); | |||||
| // ✅ 修复:重命名函数避免重复声明 | |||||
| const handleSearchReset = useCallback(() => { | |||||
| setSearchQuery({}); | |||||
| fetchCompletedDoPickOrdersData(); // 重新获取所有数据 | |||||
| }, [fetchCompletedDoPickOrdersData]); | |||||
| // ✅ 分页功能 | |||||
| const handlePageChange = useCallback((event: unknown, newPage: number) => { | |||||
| setPaginationController(prev => ({ | |||||
| ...prev, | |||||
| pageNum: newPage, | |||||
| })); | |||||
| }, []); | |||||
| const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => { | |||||
| const newPageSize = parseInt(event.target.value, 10); | |||||
| setPaginationController({ | |||||
| pageNum: 0, | |||||
| pageSize: newPageSize, | |||||
| }); | |||||
| }, []); | |||||
| // ✅ 分页数据 | |||||
| const paginatedData = useMemo(() => { | |||||
| const startIndex = paginationController.pageNum * paginationController.pageSize; | |||||
| const endIndex = startIndex + paginationController.pageSize; | |||||
| return filteredDoPickOrders.slice(startIndex, endIndex); | |||||
| }, [filteredDoPickOrders, paginationController]); | |||||
| // ✅ 搜索条件 | |||||
| const searchCriteria: Criterion<any>[] = [ | |||||
| { | |||||
| label: t("Pick Order Code"), | |||||
| paramName: "pickOrderCode", | |||||
| type: "text", | |||||
| }, | |||||
| { | |||||
| label: t("Shop Name"), | |||||
| paramName: "shopName", | |||||
| type: "text", | |||||
| }, | |||||
| { | |||||
| label: t("Delivery No"), | |||||
| paramName: "deliveryNo", | |||||
| type: "text", | |||||
| }, | |||||
| { | |||||
| label: t("Ticket No"), | |||||
| paramName: "ticketNo", | |||||
| type: "text", | |||||
| }, | |||||
| ]; | |||||
| // ✅ 处理详情点击 - 显示类似 GoodPickExecution 的表格 | |||||
| const handleDetailClick = useCallback(async (doPickOrder: CompletedDoPickOrder) => { | |||||
| setSelectedDoPickOrder(doPickOrder); | |||||
| setShowDetailView(true); | |||||
| // 获取该 pick order 的详细 lot 数据 | |||||
| try { | |||||
| const allLotDetails = await fetchALLPickOrderLineLotDetails(currentUserId!); | |||||
| const filteredLots = allLotDetails.filter(lot => | |||||
| lot.pickOrderId === doPickOrder.pickOrderId | |||||
| ); | |||||
| setDetailLotData(filteredLots); | |||||
| console.log("✅ Loaded detail lot data for pick order:", doPickOrder.pickOrderCode, filteredLots); | |||||
| } catch (error) { | |||||
| console.error("❌ Error loading detail lot data:", error); | |||||
| setDetailLotData([]); | |||||
| } | |||||
| }, [currentUserId]); | |||||
| // ✅ 返回列表视图 | |||||
| const handleBackToList = useCallback(() => { | |||||
| setShowDetailView(false); | |||||
| setSelectedDoPickOrder(null); | |||||
| setDetailLotData([]); | |||||
| }, []); | |||||
| // ✅ 如果显示详情视图,渲染类似 GoodPickExecution 的表格 | |||||
| if (showDetailView && selectedDoPickOrder) { | |||||
| return ( | |||||
| <FormProvider {...formProps}> | |||||
| <Box> | |||||
| {/* 返回按钮和标题 */} | |||||
| <Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 2 }}> | |||||
| <Button variant="outlined" onClick={handleBackToList}> | |||||
| {t("Back to List")} | |||||
| </Button> | |||||
| <Typography variant="h6"> | |||||
| {t("Pick Order Details")}: {selectedDoPickOrder.pickOrderCode} | |||||
| </Typography> | |||||
| </Box> | |||||
| {/* FG Pick Orders 信息 */} | |||||
| <Box sx={{ mb: 2 }}> | |||||
| {selectedDoPickOrder.fgPickOrders.map((fgOrder, index) => ( | |||||
| <FGPickOrderCard | |||||
| key={index} | |||||
| fgOrder={fgOrder} | |||||
| onQrCodeClick={() => {}} // 只读模式 | |||||
| /> | |||||
| ))} | |||||
| </Box> | |||||
| {/* 类似 GoodPickExecution 的表格 */} | |||||
| <TableContainer component={Paper}> | |||||
| <Table> | |||||
| <TableHead> | |||||
| <TableRow> | |||||
| <TableCell>{t("Pick Order Code")}</TableCell> | |||||
| <TableCell>{t("Item Code")}</TableCell> | |||||
| <TableCell>{t("Item Name")}</TableCell> | |||||
| <TableCell>{t("Lot No")}</TableCell> | |||||
| <TableCell>{t("Location")}</TableCell> | |||||
| <TableCell>{t("Required Qty")}</TableCell> | |||||
| <TableCell>{t("Actual Pick Qty")}</TableCell> | |||||
| <TableCell>{t("Submitted Status")}</TableCell> | |||||
| </TableRow> | |||||
| </TableHead> | |||||
| <TableBody> | |||||
| {detailLotData.map((lot, index) => ( | |||||
| <TableRow key={index}> | |||||
| <TableCell>{lot.pickOrderCode}</TableCell> | |||||
| <TableCell>{lot.itemCode}</TableCell> | |||||
| <TableCell>{lot.itemName}</TableCell> | |||||
| <TableCell>{lot.lotNo}</TableCell> | |||||
| <TableCell>{lot.location}</TableCell> | |||||
| <TableCell>{lot.requiredQty}</TableCell> | |||||
| <TableCell>{lot.actualPickQty}</TableCell> | |||||
| <TableCell> | |||||
| <Chip | |||||
| label={lot.processingStatus} | |||||
| color={lot.processingStatus === 'completed' ? 'success' : 'default'} | |||||
| size="small" | |||||
| /> | |||||
| </TableCell> | |||||
| </TableRow> | |||||
| ))} | |||||
| </TableBody> | |||||
| </Table> | |||||
| </TableContainer> | |||||
| </Box> | |||||
| </FormProvider> | |||||
| ); | |||||
| } | |||||
| // ✅ 默认列表视图 | |||||
| return ( | |||||
| <FormProvider {...formProps}> | |||||
| <Box> | |||||
| {/* 搜索框 */} | |||||
| <Box sx={{ mb: 2 }}> | |||||
| <SearchBox | |||||
| criteria={searchCriteria} | |||||
| onSearch={handleSearch} | |||||
| onReset={handleSearchReset} | |||||
| /> | |||||
| </Box> | |||||
| {/* 加载状态 */} | |||||
| {completedDoPickOrdersLoading ? ( | |||||
| <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}> | |||||
| <CircularProgress /> | |||||
| </Box> | |||||
| ) : ( | |||||
| <Box> | |||||
| {/* 结果统计 */} | |||||
| <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}> | |||||
| {t("Total")}: {filteredDoPickOrders.length} {t("completed DO pick orders")} | |||||
| </Typography> | |||||
| {/* 列表 */} | |||||
| {filteredDoPickOrders.length === 0 ? ( | |||||
| <Box sx={{ p: 3, textAlign: 'center' }}> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {t("No completed DO pick orders found")} | |||||
| </Typography> | |||||
| </Box> | |||||
| ) : ( | |||||
| <Stack spacing={2}> | |||||
| {paginatedData.map((doPickOrder) => ( | |||||
| <Card key={doPickOrder.id}> | |||||
| <CardContent> | |||||
| <Stack direction="row" justifyContent="space-between" alignItems="center"> | |||||
| <Box> | |||||
| <Typography variant="h6"> | |||||
| {doPickOrder.pickOrderCode} | |||||
| </Typography> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {doPickOrder.shopName} - {doPickOrder.deliveryNo} | |||||
| </Typography> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {t("Completed")}: {new Date(doPickOrder.completedDate).toLocaleString()} | |||||
| </Typography> | |||||
| </Box> | |||||
| <Box> | |||||
| <Chip | |||||
| label={doPickOrder.pickOrderStatus} | |||||
| color={doPickOrder.pickOrderStatus === 'completed' ? 'success' : 'default'} | |||||
| size="small" | |||||
| sx={{ mb: 1 }} | |||||
| /> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {doPickOrder.fgPickOrders.length} {t("FG orders")} | |||||
| </Typography> | |||||
| </Box> | |||||
| </Stack> | |||||
| </CardContent> | |||||
| <CardActions> | |||||
| <Button | |||||
| variant="outlined" | |||||
| onClick={() => handleDetailClick(doPickOrder)} | |||||
| > | |||||
| {t("View Details")} | |||||
| </Button> | |||||
| </CardActions> | |||||
| </Card> | |||||
| ))} | |||||
| </Stack> | |||||
| )} | |||||
| {/* 分页 */} | |||||
| {filteredDoPickOrders.length > 0 && ( | |||||
| <TablePagination | |||||
| component="div" | |||||
| count={filteredDoPickOrders.length} | |||||
| page={paginationController.pageNum} | |||||
| rowsPerPage={paginationController.pageSize} | |||||
| onPageChange={handlePageChange} | |||||
| onRowsPerPageChange={handlePageSizeChange} | |||||
| rowsPerPageOptions={[5, 10, 25, 50]} | |||||
| /> | |||||
| )} | |||||
| </Box> | |||||
| )} | |||||
| </Box> | |||||
| </FormProvider> | |||||
| ); | |||||
| }; | |||||
| export default GoodPickExecutionRecord; | |||||
| @@ -34,6 +34,7 @@ import { | |||||
| autoAssignAndReleasePickOrder, | autoAssignAndReleasePickOrder, | ||||
| AutoAssignReleaseResponse, | AutoAssignReleaseResponse, | ||||
| checkPickOrderCompletion, | checkPickOrderCompletion, | ||||
| fetchAllPickOrderLotsHierarchical, | |||||
| PickOrderCompletionResponse, | PickOrderCompletionResponse, | ||||
| checkAndCompletePickOrderByConsoCode, | checkAndCompletePickOrderByConsoCode, | ||||
| updateSuggestedLotLineId, | updateSuggestedLotLineId, | ||||
| @@ -322,7 +323,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => { | |||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | const currentUserId = session?.id ? parseInt(session.id) : undefined; | ||||
| const [allLotsCompleted, setAllLotsCompleted] = useState(false); | |||||
| const [combinedLotData, setCombinedLotData] = useState<any[]>([]); | const [combinedLotData, setCombinedLotData] = useState<any[]>([]); | ||||
| const [combinedDataLoading, setCombinedDataLoading] = useState(false); | const [combinedDataLoading, setCombinedDataLoading] = useState(false); | ||||
| const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]); | const [originalCombinedData, setOriginalCombinedData] = useState<any[]>([]); | ||||
| @@ -366,6 +367,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set()); | const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set()); | ||||
| const [lastProcessedQr, setLastProcessedQr] = useState<string>(''); | const [lastProcessedQr, setLastProcessedQr] = useState<string>(''); | ||||
| const [isRefreshingData, setIsRefreshingData] = useState<boolean>(false); | const [isRefreshingData, setIsRefreshingData] = useState<boolean>(false); | ||||
| const fetchFgPickOrdersData = useCallback(async () => { | const fetchFgPickOrdersData = useCallback(async () => { | ||||
| if (!currentUserId) return; | if (!currentUserId) return; | ||||
| @@ -403,7 +405,7 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| fetchFgPickOrdersData(); | fetchFgPickOrdersData(); | ||||
| } | } | ||||
| }, [combinedLotData, fetchFgPickOrdersData]); | }, [combinedLotData, fetchFgPickOrdersData]); | ||||
| // ✅ Handle QR code button click | // ✅ Handle QR code button click | ||||
| const handleQrCodeClick = (pickOrderId: number) => { | const handleQrCodeClick = (pickOrderId: number) => { | ||||
| console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); | console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); | ||||
| @@ -416,7 +418,31 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| setScannedLotData(scannedLot); | setScannedLotData(scannedLot); | ||||
| setLotConfirmationOpen(true); | setLotConfirmationOpen(true); | ||||
| }, []); | }, []); | ||||
| const checkAllLotsCompleted = useCallback((lotData: any[]) => { | |||||
| if (lotData.length === 0) { | |||||
| setAllLotsCompleted(false); | |||||
| return false; | |||||
| } | |||||
| // Filter out rejected lots | |||||
| const nonRejectedLots = lotData.filter(lot => | |||||
| lot.lotAvailability !== 'rejected' && | |||||
| lot.stockOutLineStatus !== 'rejected' | |||||
| ); | |||||
| if (nonRejectedLots.length === 0) { | |||||
| setAllLotsCompleted(false); | |||||
| return false; | |||||
| } | |||||
| // Check if all non-rejected lots are completed | |||||
| const allCompleted = nonRejectedLots.every(lot => | |||||
| lot.stockOutLineStatus === 'completed' | |||||
| ); | |||||
| setAllLotsCompleted(allCompleted); | |||||
| return allCompleted; | |||||
| }, []); | |||||
| const fetchAllCombinedLotData = useCallback(async (userId?: number) => { | const fetchAllCombinedLotData = useCallback(async (userId?: number) => { | ||||
| setCombinedDataLoading(true); | setCombinedDataLoading(true); | ||||
| try { | try { | ||||
| @@ -428,22 +454,111 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| console.warn("⚠️ No userId available, skipping API call"); | console.warn("⚠️ No userId available, skipping API call"); | ||||
| setCombinedLotData([]); | setCombinedLotData([]); | ||||
| setOriginalCombinedData([]); | setOriginalCombinedData([]); | ||||
| setAllLotsCompleted(false); | |||||
| return; | return; | ||||
| } | } | ||||
| // ✅ Use the non-auto-assign endpoint - this only fetches existing data | |||||
| const allLotDetails = await fetchALLPickOrderLineLotDetails(userIdToUse); | |||||
| console.log("✅ All combined lot details:", allLotDetails); | |||||
| setCombinedLotData(allLotDetails); | |||||
| setOriginalCombinedData(allLotDetails); | |||||
| // ✅ Use the hierarchical endpoint that includes rejected lots | |||||
| const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse); | |||||
| console.log("✅ Hierarchical lot details:", hierarchicalData); | |||||
| // ✅ Transform hierarchical data to flat structure for the table | |||||
| const flatLotData: any[] = []; | |||||
| if (hierarchicalData.pickOrder && hierarchicalData.pickOrderLines) { | |||||
| hierarchicalData.pickOrderLines.forEach((line: any) => { | |||||
| if (line.lots && line.lots.length > 0) { | |||||
| line.lots.forEach((lot: any) => { | |||||
| flatLotData.push({ | |||||
| // Pick order info | |||||
| pickOrderId: hierarchicalData.pickOrder.id, | |||||
| pickOrderCode: hierarchicalData.pickOrder.code, | |||||
| pickOrderConsoCode: hierarchicalData.pickOrder.consoCode, | |||||
| pickOrderTargetDate: hierarchicalData.pickOrder.targetDate, | |||||
| pickOrderType: hierarchicalData.pickOrder.type, | |||||
| pickOrderStatus: hierarchicalData.pickOrder.status, | |||||
| pickOrderAssignTo: hierarchicalData.pickOrder.assignTo, | |||||
| // Pick order line info | |||||
| pickOrderLineId: line.id, | |||||
| pickOrderLineRequiredQty: line.requiredQty, | |||||
| pickOrderLineStatus: line.status, | |||||
| // Item info | |||||
| itemId: line.item.id, | |||||
| itemCode: line.item.code, | |||||
| itemName: line.item.name, | |||||
| uomCode: line.item.uomCode, | |||||
| uomDesc: line.item.uomDesc, | |||||
| // Lot info | |||||
| lotId: lot.id, | |||||
| lotNo: lot.lotNo, | |||||
| expiryDate: lot.expiryDate, | |||||
| location: lot.location, | |||||
| stockUnit: lot.stockUnit, | |||||
| availableQty: lot.availableQty, | |||||
| requiredQty: lot.requiredQty, | |||||
| actualPickQty: lot.actualPickQty, | |||||
| inQty: lot.inQty, | |||||
| outQty: lot.outQty, | |||||
| holdQty: lot.holdQty, | |||||
| lotStatus: lot.lotStatus, | |||||
| lotAvailability: lot.lotAvailability, | |||||
| processingStatus: lot.processingStatus, | |||||
| suggestedPickLotId: lot.suggestedPickLotId, | |||||
| stockOutLineId: lot.stockOutLineId, | |||||
| stockOutLineStatus: lot.stockOutLineStatus, | |||||
| stockOutLineQty: lot.stockOutLineQty, | |||||
| // Router info | |||||
| routerId: lot.router?.id, | |||||
| routerIndex: lot.router?.index, | |||||
| routerRoute: lot.router?.route, | |||||
| routerArea: lot.router?.area, | |||||
| uomShortDesc: lot.router?.uomId | |||||
| }); | |||||
| }); | |||||
| } | |||||
| }); | |||||
| } | |||||
| console.log("✅ Transformed flat lot data:", flatLotData); | |||||
| setCombinedLotData(flatLotData); | |||||
| setOriginalCombinedData(flatLotData); | |||||
| // ✅ Check completion status | |||||
| checkAllLotsCompleted(flatLotData); | |||||
| } catch (error) { | } catch (error) { | ||||
| console.error("❌ Error fetching combined lot data:", error); | console.error("❌ Error fetching combined lot data:", error); | ||||
| setCombinedLotData([]); | setCombinedLotData([]); | ||||
| setOriginalCombinedData([]); | setOriginalCombinedData([]); | ||||
| setAllLotsCompleted(false); | |||||
| } finally { | } finally { | ||||
| setCombinedDataLoading(false); | setCombinedDataLoading(false); | ||||
| } | } | ||||
| }, [currentUserId]); | |||||
| }, [currentUserId, checkAllLotsCompleted]); | |||||
| // ✅ Add effect to check completion when lot data changes | |||||
| useEffect(() => { | |||||
| if (combinedLotData.length > 0) { | |||||
| checkAllLotsCompleted(combinedLotData); | |||||
| } | |||||
| }, [combinedLotData, checkAllLotsCompleted]); | |||||
| // ✅ Add function to expose completion status to parent | |||||
| const getCompletionStatus = useCallback(() => { | |||||
| return allLotsCompleted; | |||||
| }, [allLotsCompleted]); | |||||
| // ✅ Expose completion status to parent component | |||||
| useEffect(() => { | |||||
| // Dispatch custom event with completion status | |||||
| const event = new CustomEvent('pickOrderCompletionStatus', { | |||||
| detail: { allLotsCompleted } | |||||
| }); | |||||
| window.dispatchEvent(event); | |||||
| }, [allLotsCompleted]); | |||||
| const handleLotConfirmation = useCallback(async () => { | const handleLotConfirmation = useCallback(async () => { | ||||
| if (!expectedLotData || !scannedLotData || !selectedLotForQr) return; | if (!expectedLotData || !scannedLotData || !selectedLotForQr) return; | ||||
| setIsConfirmingLot(true); | setIsConfirmingLot(true); | ||||
| @@ -465,6 +580,15 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| newInventoryLotLineId: newLotLineId | newInventoryLotLineId: newLotLineId | ||||
| }); | }); | ||||
| setQrScanError(false); | |||||
| setQrScanSuccess(false); | |||||
| setQrScanInput(''); | |||||
| setIsManualScanning(false); | |||||
| stopScan(); | |||||
| resetScan(); | |||||
| setProcessedQrCodes(new Set()); | |||||
| setLastProcessedQr(''); | |||||
| setLotConfirmationOpen(false); | setLotConfirmationOpen(false); | ||||
| setExpectedLotData(null); | setExpectedLotData(null); | ||||
| setScannedLotData(null); | setScannedLotData(null); | ||||
| @@ -672,11 +796,25 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| console.error("No item match in expected lots for scanned code"); | console.error("No item match in expected lots for scanned code"); | ||||
| setQrScanError(true); | setQrScanError(true); | ||||
| setQrScanSuccess(false); | setQrScanSuccess(false); | ||||
| return; | return; | ||||
| } | } | ||||
| // 2) Check if scanned lot is exactly in expected lots | |||||
| const exactLotMatch = sameItemLotsInExpected.find(l => | |||||
| // ✅ FIXED: Find the ACTIVE suggested lot (not rejected lots) | |||||
| const activeSuggestedLots = sameItemLotsInExpected.filter(lot => | |||||
| lot.lotAvailability !== 'rejected' && | |||||
| lot.stockOutLineStatus !== 'rejected' && | |||||
| lot.processingStatus !== 'rejected' | |||||
| ); | |||||
| if (activeSuggestedLots.length === 0) { | |||||
| console.error("No active suggested lots found for this item"); | |||||
| setQrScanError(true); | |||||
| setQrScanSuccess(false); | |||||
| return; | |||||
| } | |||||
| // 2) Check if scanned lot is exactly in active suggested lots | |||||
| const exactLotMatch = activeSuggestedLots.find(l => | |||||
| (scanned?.inventoryLotLineId && l.lotId === scanned.inventoryLotLineId) || | (scanned?.inventoryLotLineId && l.lotId === scanned.inventoryLotLineId) || | ||||
| (scanned?.lotNo && l.lotNo === scanned.lotNo) | (scanned?.lotNo && l.lotNo === scanned.lotNo) | ||||
| ); | ); | ||||
| @@ -689,7 +827,8 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| } | } | ||||
| // Case 2: Item matches but lot number differs -> open confirmation modal | // Case 2: Item matches but lot number differs -> open confirmation modal | ||||
| const expectedLot = sameItemLotsInExpected[0]; | |||||
| // ✅ FIXED: Use the first ACTIVE suggested lot, not just any lot | |||||
| const expectedLot = activeSuggestedLots[0]; | |||||
| if (!expectedLot) { | if (!expectedLot) { | ||||
| console.error("Could not determine expected lot for confirmation"); | console.error("Could not determine expected lot for confirmation"); | ||||
| setQrScanError(true); | setQrScanError(true); | ||||
| @@ -697,6 +836,14 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| return; | return; | ||||
| } | } | ||||
| // ✅ Check if the expected lot is already the scanned lot (after substitution) | |||||
| if (expectedLot.lotNo === scanned?.lotNo) { | |||||
| console.log(`Lot already substituted, proceeding with ${scanned.lotNo}`); | |||||
| handleQrCodeSubmit(scanned.lotNo); | |||||
| return; | |||||
| } | |||||
| console.log(`🔍 Lot mismatch: Expected ${expectedLot.lotNo}, Scanned ${scanned?.lotNo}`); | |||||
| setSelectedLotForQr(expectedLot); | setSelectedLotForQr(expectedLot); | ||||
| handleLotMismatch( | handleLotMismatch( | ||||
| { | { | ||||
| @@ -720,26 +867,27 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| } | } | ||||
| }, [combinedLotData, handleQrCodeSubmit, handleLotMismatch]); | }, [combinedLotData, handleQrCodeSubmit, handleLotMismatch]); | ||||
| // ✅ Update the outside QR scanning effect to use enhanced processing | // ✅ Update the outside QR scanning effect to use enhanced processing | ||||
| useEffect(() => { | |||||
| if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { | |||||
| return; | |||||
| } | |||||
| // ✅ Update the outside QR scanning effect to use enhanced processing | |||||
| useEffect(() => { | |||||
| if (!isManualScanning || qrValues.length === 0 || combinedLotData.length === 0 || isRefreshingData) { | |||||
| return; | |||||
| } | |||||
| const latestQr = qrValues[qrValues.length - 1]; | |||||
| const latestQr = qrValues[qrValues.length - 1]; | |||||
| if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) { | |||||
| console.log("QR code already processed, skipping..."); | |||||
| return; | |||||
| } | |||||
| if (latestQr && latestQr !== lastProcessedQr) { | |||||
| console.log(`🔍 Processing new QR code with enhanced validation: ${latestQr}`); | |||||
| setLastProcessedQr(latestQr); | |||||
| setProcessedQrCodes(prev => new Set(prev).add(latestQr)); | |||||
| if (processedQrCodes.has(latestQr) || lastProcessedQr === latestQr) { | |||||
| console.log("QR code already processed, skipping..."); | |||||
| return; | |||||
| } | |||||
| if (latestQr && latestQr !== lastProcessedQr) { | |||||
| console.log(`🔍 Processing new QR code with enhanced validation: ${latestQr}`); | |||||
| setLastProcessedQr(latestQr); | |||||
| setProcessedQrCodes(prev => new Set(prev).add(latestQr)); | |||||
| processOutsideQrCode(latestQr); | |||||
| } | |||||
| }, [qrValues, isManualScanning, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode]); | |||||
| processOutsideQrCode(latestQr); | |||||
| } | |||||
| }, [qrValues, isManualScanning, processedQrCodes, lastProcessedQr, isRefreshingData, processOutsideQrCode, combinedLotData]); | |||||
| // ✅ Only fetch existing data when session is ready, no auto-assignment | // ✅ Only fetch existing data when session is ready, no auto-assignment | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (session && currentUserId && !initializationRef.current) { | if (session && currentUserId && !initializationRef.current) { | ||||
| @@ -1003,7 +1151,14 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); | |||||
| setPickExecutionFormOpen(false); | setPickExecutionFormOpen(false); | ||||
| setSelectedLotForExecutionForm(null); | setSelectedLotForExecutionForm(null); | ||||
| setQrScanError(false); | |||||
| setQrScanSuccess(false); | |||||
| setQrScanInput(''); | |||||
| setIsManualScanning(false); | |||||
| stopScan(); | |||||
| resetScan(); | |||||
| setProcessedQrCodes(new Set()); | |||||
| setLastProcessedQr(''); | |||||
| await fetchAllCombinedLotData(); | await fetchAllCombinedLotData(); | ||||
| } catch (error) { | } catch (error) { | ||||
| console.error("Error submitting pick execution form:", error); | console.error("Error submitting pick execution form:", error); | ||||
| @@ -1315,7 +1470,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe | |||||
| {/* <TableCell>{t("Lot Location")}</TableCell> */} | {/* <TableCell>{t("Lot Location")}</TableCell> */} | ||||
| <TableCell align="right">{t("Lot Required Pick Qty")}</TableCell> | <TableCell align="right">{t("Lot Required Pick Qty")}</TableCell> | ||||
| {/* <TableCell align="right">{t("Original Available Qty")}</TableCell> */} | {/* <TableCell align="right">{t("Original Available Qty")}</TableCell> */} | ||||
| <TableCell align="right">{t("Scan Result")}</TableCell> | |||||
| <TableCell align="center">{t("Scan Result")}</TableCell> | |||||
| <TableCell align="center">{t("Submit Required Pick Qty")}</TableCell> | <TableCell align="center">{t("Submit Required Pick Qty")}</TableCell> | ||||
| {/* <TableCell align="right">{t("Remaining Available Qty")}</TableCell> */} | {/* <TableCell align="right">{t("Remaining Available Qty")}</TableCell> */} | ||||
| @@ -1380,21 +1535,36 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe | |||||
| return result.toLocaleString()+'('+lot.uomShortDesc+')'; | return result.toLocaleString()+'('+lot.uomShortDesc+')'; | ||||
| })()} | })()} | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell align="center"> | |||||
| {lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? ( | |||||
| <Checkbox | |||||
| checked={lot.stockOutLineStatus?.toLowerCase() !== 'pending'} | |||||
| disabled={true} | |||||
| readOnly={true} | |||||
| sx={{ | |||||
| color: lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? 'success.main' : 'grey.400', | |||||
| '&.Mui-checked': { | |||||
| color: 'success.main', | |||||
| }, | |||||
| }} | |||||
| /> | |||||
| ) : null} | |||||
| </TableCell> | |||||
| <TableCell align="center"> | |||||
| {lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? ( | |||||
| <Box sx={{ | |||||
| display: 'flex', | |||||
| justifyContent: 'center', | |||||
| alignItems: 'center', | |||||
| width: '100%', | |||||
| height: '100%' | |||||
| }}> | |||||
| <Checkbox | |||||
| checked={lot.stockOutLineStatus?.toLowerCase() !== 'pending'} | |||||
| disabled={true} | |||||
| readOnly={true} | |||||
| size="large" | |||||
| sx={{ | |||||
| color: lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? 'success.main' : 'grey.400', | |||||
| '&.Mui-checked': { | |||||
| color: 'success.main', | |||||
| }, | |||||
| transform: 'scale(1.3)', | |||||
| '& .MuiSvgIcon-root': { | |||||
| fontSize: '1.5rem', | |||||
| } | |||||
| }} | |||||
| /> | |||||
| </Box> | |||||
| ) : null} | |||||
| </TableCell> | |||||
| <TableCell align="center"> | <TableCell align="center"> | ||||
| <Box sx={{ display: 'flex', justifyContent: 'center' }}> | <Box sx={{ display: 'flex', justifyContent: 'center' }}> | ||||
| <Stack direction="row" spacing={1} alignItems="center"> | <Stack direction="row" spacing={1} alignItems="center"> | ||||
| @@ -92,7 +92,7 @@ const LotConfirmationModal: React.FC<LotConfirmationModalProps> = ({ | |||||
| </Box> | </Box> | ||||
| <Alert severity="info"> | <Alert severity="info"> | ||||
| {t("If you proceed, the system will:")} | |||||
| {t("If you confirm, the system will:")} | |||||
| <ul style={{ margin: '8px 0 0 16px' }}> | <ul style={{ margin: '8px 0 0 16px' }}> | ||||
| <li>{t("Update your suggested lot to the this scanned lot")}</li> | <li>{t("Update your suggested lot to the this scanned lot")}</li> | ||||
| </ul> | </ul> | ||||
| @@ -114,7 +114,7 @@ const LotConfirmationModal: React.FC<LotConfirmationModalProps> = ({ | |||||
| color="warning" | color="warning" | ||||
| disabled={isLoading} | disabled={isLoading} | ||||
| > | > | ||||
| {isLoading ? t("Processing...") : t("Yes, Use This Lot")} | |||||
| {isLoading ? t("Processing...") : t("Confirm")} | |||||
| </Button> | </Button> | ||||
| </DialogActions> | </DialogActions> | ||||
| </Dialog> | </Dialog> | ||||
| @@ -22,7 +22,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | |||||
| import ExpandLessIcon from '@mui/icons-material/ExpandLess'; | import ExpandLessIcon from '@mui/icons-material/ExpandLess'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { Controller, useFormContext } from 'react-hook-form'; | 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 { EscalationCombo } from "@/app/api/user"; | ||||
| import { fetchEscalationCombo } from "@/app/api/user/actions"; | import { fetchEscalationCombo } from "@/app/api/user/actions"; | ||||
| import { FireExtinguisher } from '@mui/icons-material'; | import { FireExtinguisher } from '@mui/icons-material'; | ||||
| @@ -1,6 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { StockInLineEntry, EscalationInput } from "@/app/api/po/actions"; | |||||
| import { StockInLineEntry, EscalationInput } from "@/app/api/stockIn/actions"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Card, | Card, | ||||
| @@ -30,7 +30,7 @@ import TwoLineCell from "./TwoLineCell"; | |||||
| import QcSelect from "./QcSelect"; | import QcSelect from "./QcSelect"; | ||||
| import { QcItemWithChecks } from "@/app/api/qc"; | import { QcItemWithChecks } from "@/app/api/qc"; | ||||
| import { GridEditInputCell } from "@mui/x-data-grid"; | 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"; | import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | ||||
| interface Props { | interface Props { | ||||
| @@ -4,7 +4,6 @@ import { | |||||
| fetchPoWithStockInLines, | fetchPoWithStockInLines, | ||||
| PoResult, | PoResult, | ||||
| PurchaseOrderLine, | PurchaseOrderLine, | ||||
| StockInLine, | |||||
| } from "@/app/api/po"; | } from "@/app/api/po"; | ||||
| import { | import { | ||||
| Box, | Box, | ||||
| @@ -44,11 +43,11 @@ import { | |||||
| checkPolAndCompletePo, | checkPolAndCompletePo, | ||||
| fetchPoInClient, | fetchPoInClient, | ||||
| fetchPoListClient, | fetchPoListClient, | ||||
| fetchStockInLineInfo, | |||||
| PurchaseQcResult, | |||||
| startPo, | startPo, | ||||
| createStockInLine | |||||
| } from "@/app/api/po/actions"; | } from "@/app/api/po/actions"; | ||||
| import { | |||||
| createStockInLine | |||||
| } from "@/app/api/stockIn/actions"; | |||||
| import { | import { | ||||
| useCallback, | useCallback, | ||||
| useContext, | useContext, | ||||
| @@ -59,7 +58,7 @@ import { | |||||
| import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; | import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; | ||||
| import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; | import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; | ||||
| import PoInputGrid from "./PoInputGrid"; | import PoInputGrid from "./PoInputGrid"; | ||||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| // import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| import { useRouter, useSearchParams, usePathname } from "next/navigation"; | import { useRouter, useSearchParams, usePathname } from "next/navigation"; | ||||
| import { WarehouseResult } from "@/app/api/warehouse"; | import { WarehouseResult } from "@/app/api/warehouse"; | ||||
| import { calculateWeight, dateStringToDayjs, dayjsToDateString, OUTPUT_DATE_FORMAT, outputDateStringToInputDateString, returnWeightUnit } from "@/app/utils/formatUtil"; | 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 { getMailTemplatePdfForStockInLine } from "@/app/api/mailTemplate/actions"; | ||||
| import { PrinterCombo } from "@/app/api/settings/printer"; | import { PrinterCombo } from "@/app/api/settings/printer"; | ||||
| import { EscalationCombo } from "@/app/api/user"; | import { EscalationCombo } from "@/app/api/user"; | ||||
| import { StockInLine } from "@/app/api/stockIn"; | |||||
| //import { useRouter } from "next/navigation"; | //import { useRouter } from "next/navigation"; | ||||
| type Props = { | type Props = { | ||||
| po: PoResult; | po: PoResult; | ||||
| qc: QcItemWithChecks[]; | |||||
| // qc: QcItemWithChecks[]; | |||||
| warehouse: WarehouseResult[]; | warehouse: WarehouseResult[]; | ||||
| printerCombo: PrinterCombo[]; | printerCombo: PrinterCombo[]; | ||||
| }; | }; | ||||
| @@ -191,7 +191,7 @@ interface PolInputResult { | |||||
| dnQty: number, | dnQty: number, | ||||
| } | } | ||||
| const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||||
| const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => { | |||||
| const cameras = useContext(CameraContext); | const cameras = useContext(CameraContext); | ||||
| // console.log(cameras); | // console.log(cameras); | ||||
| const { t } = useTranslation("purchaseOrder"); | const { t } = useTranslation("purchaseOrder"); | ||||
| @@ -236,7 +236,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||||
| const dnFormProps = useForm({ | const dnFormProps = useForm({ | ||||
| defaultValues: { | defaultValues: { | ||||
| dnNo: '', | dnNo: '', | ||||
| dnDate: dayjsToDateString(dayjs()) | |||||
| receiptDate: dayjsToDateString(dayjs()) | |||||
| } | } | ||||
| }) | }) | ||||
| const fetchPoList = useCallback(async () => { | const fetchPoList = useCallback(async () => { | ||||
| @@ -263,7 +263,6 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||||
| const fetchPoDetail = useCallback(async (poId: string) => { | const fetchPoDetail = useCallback(async (poId: string) => { | ||||
| try { | try { | ||||
| const result = await fetchPoInClient(parseInt(poId)); | const result = await fetchPoInClient(parseInt(poId)); | ||||
| console.log(result) | |||||
| if (result) { | if (result) { | ||||
| console.log("%c Fetched PO:", "color:orange", result); | console.log("%c Fetched PO:", "color:orange", result); | ||||
| setPurchaseOrder(result); | setPurchaseOrder(result); | ||||
| @@ -420,11 +419,11 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||||
| const postData = { | const postData = { | ||||
| dnNo: dnFormProps.watch("dnNo"), | dnNo: dnFormProps.watch("dnNo"), | ||||
| dnDate: outputDateStringToInputDateString(dnFormProps.watch("dnDate")), | |||||
| receiptDate: outputDateStringToInputDateString(dnFormProps.watch("receiptDate")), | |||||
| itemId: row.itemId, | itemId: row.itemId, | ||||
| itemNo: row.itemNo, | itemNo: row.itemNo, | ||||
| itemName: row.itemName, | itemName: row.itemName, | ||||
| purchaseOrderId: row.purchaseOrderId, | |||||
| // purchaseOrderId: row.purchaseOrderId, | |||||
| purchaseOrderLineId: row.id, | purchaseOrderLineId: row.id, | ||||
| acceptedQty: acceptedQty, | acceptedQty: acceptedQty, | ||||
| productLotNo: polInputList[rowIndex].lotNo || '', | productLotNo: polInputList[rowIndex].lotNo || '', | ||||
| @@ -765,11 +764,11 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||||
| > | > | ||||
| <Controller | <Controller | ||||
| control={dnFormProps.control} | control={dnFormProps.control} | ||||
| name="dnDate" | |||||
| name="receiptDate" | |||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <DatePicker | <DatePicker | ||||
| label={t("dnDate")} | |||||
| label={t("receiptDate")} | |||||
| format={`${OUTPUT_DATE_FORMAT}`} | format={`${OUTPUT_DATE_FORMAT}`} | ||||
| defaultValue={dateStringToDayjs(field.value)} | defaultValue={dateStringToDayjs(field.value)} | ||||
| onChange={(newValue: Dayjs | null) => { | onChange={(newValue: Dayjs | null) => { | ||||
| @@ -854,7 +853,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||||
| <TableCell align="right"> | <TableCell align="right"> | ||||
| <Box> | <Box> | ||||
| <PoInputGrid | <PoInputGrid | ||||
| qc={qc} | |||||
| // qc={qc} | |||||
| setRows={setRows} | setRows={setRows} | ||||
| stockInLine={stockInLine} | stockInLine={stockInLine} | ||||
| setStockInLine={setStockInLine} | setStockInLine={setStockInLine} | ||||
| @@ -24,18 +24,16 @@ type Props = { | |||||
| const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | ||||
| const [ | const [ | ||||
| poWithStockInLine, | poWithStockInLine, | ||||
| warehouse, | |||||
| qc, | |||||
| warehouse, | |||||
| printerCombo, | printerCombo, | ||||
| ] = await Promise.all([ | ] = await Promise.all([ | ||||
| fetchPoWithStockInLines(id), | fetchPoWithStockInLines(id), | ||||
| fetchWarehouseList(), | fetchWarehouseList(), | ||||
| fetchQcItemCheck(), | |||||
| fetchPrinterCombo(), | fetchPrinterCombo(), | ||||
| ]); | ]); | ||||
| // const poWithStockInLine = await fetchPoWithStockInLines(id) | // const poWithStockInLine = await fetchPoWithStockInLines(id) | ||||
| console.log("%c pol:", "color:green", poWithStockInLine); | 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; | PoDetailWrapper.Loading = PoDetailLoading; | ||||
| @@ -31,10 +31,11 @@ import DeleteIcon from "@mui/icons-material/Delete"; | |||||
| import CancelIcon from "@mui/icons-material/Cancel"; | import CancelIcon from "@mui/icons-material/Cancel"; | ||||
| import FactCheckIcon from "@mui/icons-material/FactCheck"; | import FactCheckIcon from "@mui/icons-material/FactCheck"; | ||||
| import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; | 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 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 { usePathname, useRouter, useSearchParams } from "next/navigation"; | ||||
| import { | import { | ||||
| returnWeightUnit, | returnWeightUnit, | ||||
| @@ -73,7 +74,7 @@ interface ResultWithId { | |||||
| } | } | ||||
| interface Props { | interface Props { | ||||
| qc: QcItemWithChecks[]; | |||||
| // qc: QcItemWithChecks[]; | |||||
| setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | ||||
| setStockInLine: Dispatch<SetStateAction<StockInLine[]>>; | setStockInLine: Dispatch<SetStateAction<StockInLine[]>>; | ||||
| setProcessedQty: Dispatch<SetStateAction<number>>; | setProcessedQty: Dispatch<SetStateAction<number>>; | ||||
| @@ -114,7 +115,7 @@ class ProcessRowUpdateError extends Error { | |||||
| } | } | ||||
| function PoInputGrid({ | function PoInputGrid({ | ||||
| qc, | |||||
| // qc, | |||||
| setRows, | setRows, | ||||
| setStockInLine, | setStockInLine, | ||||
| setProcessedQty, | setProcessedQty, | ||||
| @@ -136,7 +137,7 @@ function PoInputGrid({ | |||||
| const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []); | const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| setEntries(stockInLine) | |||||
| setEntries(stockInLine); | |||||
| }, [stockInLine]) | }, [stockInLine]) | ||||
| const [modalInfo, setModalInfo] = useState< | const [modalInfo, setModalInfo] = useState< | ||||
| StockInLine & { qcResult?: PurchaseQcResult[] } & { escalationResult?: EscalationResult[] } | StockInLine & { qcResult?: PurchaseQcResult[] } & { escalationResult?: EscalationResult[] } | ||||
| @@ -215,7 +216,7 @@ function PoInputGrid({ | |||||
| setRejectOpen(true); | setRejectOpen(true); | ||||
| }, []); | }, []); | ||||
| const handleStart = useCallback( | |||||
| const handleStart = useCallback( // NOTE: Seems unused!!!!!!!! | |||||
| (id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
| setBtnIsLoading(true); | setBtnIsLoading(true); | ||||
| setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
| @@ -229,7 +230,7 @@ function PoInputGrid({ | |||||
| itemId: params.row.itemId, | itemId: params.row.itemId, | ||||
| itemNo: params.row.itemNo, | itemNo: params.row.itemNo, | ||||
| itemName: params.row.itemName, | itemName: params.row.itemName, | ||||
| purchaseOrderId: params.row.purchaseOrderId, | |||||
| // purchaseOrderId: params.row.purchaseOrderId, | |||||
| purchaseOrderLineId: params.row.purchaseOrderLineId, | purchaseOrderLineId: params.row.purchaseOrderLineId, | ||||
| acceptedQty: params.row.acceptedQty, | acceptedQty: params.row.acceptedQty, | ||||
| }; | }; | ||||
| @@ -263,7 +264,6 @@ function PoInputGrid({ | |||||
| [id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
| })); | })); | ||||
| const qcResult = await fetchQcDefaultValue(id); | const qcResult = await fetchQcDefaultValue(id); | ||||
| const escResult = await fetchEscalationLogsByStockInLines([Number(id)]); | |||||
| // console.log(params.row); | // console.log(params.row); | ||||
| console.log("Fetched QC Result:", qcResult); | console.log("Fetched QC Result:", qcResult); | ||||
| @@ -291,9 +291,9 @@ const closeNewModal = useCallback(() => { | |||||
| newParams.delete("stockInLineId"); // Remove the parameter | newParams.delete("stockInLineId"); // Remove the parameter | ||||
| router.replace(`${pathname}?${newParams.toString()}`); | router.replace(`${pathname}?${newParams.toString()}`); | ||||
| fetchPoDetail(itemDetail.purchaseOrderId.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]); | }, [searchParams, pathname, router]); | ||||
| // Open modal | // Open modal | ||||
| @@ -304,31 +304,27 @@ const closeNewModal = useCallback(() => { | |||||
| // Button handler to update the URL and open the modal | // Button handler to update the URL and open the modal | ||||
| const handleNewQC = useCallback( | const handleNewQC = useCallback( | ||||
| (id: GridRowId, params: any) => async() => { | (id: GridRowId, params: any) => async() => { | ||||
| // console.log(id) | |||||
| // setBtnIsLoading(true); | // setBtnIsLoading(true); | ||||
| setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
| ...prev, | ...prev, | ||||
| [id]: { mode: GridRowModes.View }, | [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(() => ({ | setModalInfo(() => ({ | ||||
| ...params.row, | ...params.row, | ||||
| qcResult: qcResult, | |||||
| escResult: escResult, | |||||
| // qcResult: qcResult, | |||||
| // escResult: escResult, | |||||
| receivedQty: itemDetail.receivedQty, | 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] | [fetchQcDefaultValue, openNewModal, pathname, router, searchParams] | ||||
| ); | ); | ||||
| @@ -337,7 +333,6 @@ const closeNewModal = useCallback(() => { | |||||
| const [firstCheckForSil, setFirstCheckForSil] = useState(false) | const [firstCheckForSil, setFirstCheckForSil] = useState(false) | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (stockInLineId && itemDetail && !firstCheckForSil) { | if (stockInLineId && itemDetail && !firstCheckForSil) { | ||||
| // console.log("heeloo") | |||||
| // console.log(stockInLineId) | // console.log(stockInLineId) | ||||
| // console.log(apiRef.current.getRow(stockInLineId)) | // console.log(apiRef.current.getRow(stockInLineId)) | ||||
| setFirstCheckForSil(true) | setFirstCheckForSil(true) | ||||
| @@ -492,8 +487,8 @@ const closeNewModal = useCallback(() => { | |||||
| // flex: 0.4, | // flex: 0.4, | ||||
| }, | }, | ||||
| { | { | ||||
| field: "dnDate", | |||||
| headerName: t("dnDate"), | |||||
| field: "receiptDate", | |||||
| headerName: t("receiptDate"), | |||||
| width: 125, | width: 125, | ||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| // console.log(params.row) | // console.log(params.row) | ||||
| @@ -952,100 +947,19 @@ const closeNewModal = useCallback(() => { | |||||
| footer: { child: footer }, | footer: { child: footer }, | ||||
| }} | }} | ||||
| /> | /> | ||||
| {modalInfo !== undefined && ( | |||||
| {/* {modalInfo !== undefined && ( */} | |||||
| <> | <> | ||||
| <QcStockInModal | <QcStockInModal | ||||
| // setRows={setRows} | |||||
| setEntries={setEntries} | |||||
| setStockInLine={setStockInLine} | |||||
| setItemDetail={setModalInfo} | |||||
| session={sessionToken} | session={sessionToken} | ||||
| qc={qc} | |||||
| warehouse={warehouse} | |||||
| open={newOpen} | open={newOpen} | ||||
| onClose={closeNewModal} | onClose={closeNewModal} | ||||
| itemDetail={modalInfo} | |||||
| handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} | |||||
| // itemDetail={modalInfo} | |||||
| inputDetail={modalInfo} | |||||
| printerCombo={printerCombo} | 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"; | "use client"; | ||||
| import { PurchaseQcResult, PutAwayInput, PutAwayLine } from "@/app/api/po/actions"; | |||||
| import { PurchaseQcResult, PutAwayInput, PutAwayLine } from "@/app/api/stockIn/actions"; | |||||
| import { | import { | ||||
| Autocomplete, | Autocomplete, | ||||
| Box, | Box, | ||||
| @@ -36,7 +36,7 @@ import TwoLineCell from "./TwoLineCell"; | |||||
| import QcSelect from "./QcSelect"; | import QcSelect from "./QcSelect"; | ||||
| import { QcItemWithChecks } from "@/app/api/qc"; | import { QcItemWithChecks } from "@/app/api/qc"; | ||||
| import { GridEditInputCell } from "@mui/x-data-grid"; | 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 { WarehouseResult } from "@/app/api/warehouse"; | ||||
| import { | import { | ||||
| arrayToDateTimeString, | arrayToDateTimeString, | ||||
| @@ -58,7 +58,7 @@ dayjs.extend(arraySupport); | |||||
| interface Props { | interface Props { | ||||
| itemDetail: StockInLine; | itemDetail: StockInLine; | ||||
| warehouse: WarehouseResult[]; | |||||
| warehouse?: WarehouseResult[]; | |||||
| disabled: boolean; | disabled: boolean; | ||||
| // qc: QcItemWithChecks[]; | // qc: QcItemWithChecks[]; | ||||
| setRowModesModel: Dispatch<SetStateAction<GridRowModesModel>>; | setRowModesModel: Dispatch<SetStateAction<GridRowModesModel>>; | ||||
| @@ -84,7 +84,7 @@ const style = { | |||||
| width: "auto", | 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 { t } = useTranslation("purchaseOrder"); | ||||
| const apiRef = useGridApiRef(); | const apiRef = useGridApiRef(); | ||||
| const { | const { | ||||
| @@ -100,7 +100,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||||
| clearErrors, | clearErrors, | ||||
| } = useFormContext<PutAwayInput>(); | } = useFormContext<PutAwayInput>(); | ||||
| // const [recordQty, setRecordQty] = useState(0); | // const [recordQty, setRecordQty] = useState(0); | ||||
| const [warehouseId, setWarehouseId] = useState(itemDetail.defaultWarehouseId); | |||||
| const [warehouseId, setWarehouseId] = useState(itemDetail.defaultWarehouseId ?? 1); | |||||
| const filteredWarehouse = useMemo(() => { | const filteredWarehouse = useMemo(() => { | ||||
| // do filtering here if any | // do filtering here if any | ||||
| return warehouse; | return warehouse; | ||||
| @@ -113,11 +113,11 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||||
| }; | }; | ||||
| const options = useMemo(() => { | const options = useMemo(() => { | ||||
| return [ | return [ | ||||
| // { | |||||
| // value: 0, // think think sin | |||||
| // label: t("Select warehouse"), | |||||
| // group: "default", | |||||
| // }, | |||||
| { | |||||
| value: 1, | |||||
| label: t("W001 - 憶兆 3樓A倉"), | |||||
| group: "default", | |||||
| }, | |||||
| ...filteredWarehouse.map((w) => ({ | ...filteredWarehouse.map((w) => ({ | ||||
| value: w.id, | value: w.id, | ||||
| label: `${w.code} - ${w.name}`, | label: `${w.code} - ${w.name}`, | ||||
| @@ -175,32 +175,10 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||||
| // validateForm(); | // validateForm(); | ||||
| // }, [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(() => { | useEffect(() => { | ||||
| setValue("status", "received"); | setValue("status", "received"); | ||||
| // setValue("status", "completed"); | // setValue("status", "completed"); | ||||
| setValue("warehouseId", options[0].value); //TODO: save all warehouse entry? | |||||
| // setValue("warehouseId", options[0].value); //TODO: save all warehouse entry? | |||||
| }, []); | }, []); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -342,7 +320,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||||
| const defaultMaxQty = Number(itemDetail.demandQty?? itemDetail.acceptedQty)//watch("acceptedQty") | const defaultMaxQty = Number(itemDetail.demandQty?? itemDetail.acceptedQty)//watch("acceptedQty") | ||||
| - watch("putAwayLines").reduce((acc, cur) => acc + cur.qty, 0) | - watch("putAwayLines").reduce((acc, cur) => acc + cur.qty, 0) | ||||
| const defaultWarehouseId = itemDetail.defaultWarehouseId ?? 1 | 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> | 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} | spacing={2} | ||||
| sx={{ mt: 0.5 }} | sx={{ mt: 0.5 }} | ||||
| > | > | ||||
| <Grid item xs={12}> | |||||
| {/* <Grid item xs={6}> | |||||
| <TextField | <TextField | ||||
| label={t("LotNo")} | |||||
| label={t("Supplier")} | |||||
| fullWidth | fullWidth | ||||
| value={itemDetail.lotNo} | |||||
| value={itemDetail.supplier} | |||||
| disabled | disabled | ||||
| /> | /> | ||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| </Grid> */} | |||||
| {/* <Grid item xs={6}> | |||||
| <TextField | <TextField | ||||
| label={t("Supplier")} | |||||
| label={t("Po Code")} | |||||
| fullWidth | fullWidth | ||||
| value={itemDetail.supplier} | |||||
| value={itemDetail.poCode} | |||||
| disabled | disabled | ||||
| /> | /> | ||||
| </Grid> | |||||
| </Grid> */} | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("Po Code")} | |||||
| label={t("itemNo")} | |||||
| fullWidth | fullWidth | ||||
| value={itemDetail.poCode} | |||||
| value={itemDetail.itemNo} | |||||
| disabled | disabled | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| @@ -394,44 +372,52 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("itemNo")} | |||||
| label={t("stockLotNo")} | |||||
| fullWidth | fullWidth | ||||
| value={itemDetail.itemNo} | |||||
| value={itemDetail.lotNo} | |||||
| disabled | disabled | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <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 | fullWidth | ||||
| value={itemDetail.acceptedQty} | value={itemDetail.acceptedQty} | ||||
| disabled | disabled | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | |||||
| <Grid item xs={3}> | |||||
| <TextField | <TextField | ||||
| label={t("productionDate")} | |||||
| label={t("uom")} | |||||
| fullWidth | fullWidth | ||||
| value={ | |||||
| // dayjs(itemDetail.productionDate) | |||||
| dayjs() | |||||
| // .add(-1, "month") | |||||
| .format(OUTPUT_DATE_FORMAT)} | |||||
| value={itemDetail.uom?.udfudesc} | |||||
| disabled | disabled | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | |||||
| {/* <Grid item xs={6}> | |||||
| <TextField | <TextField | ||||
| label={t("expiryDate")} | |||||
| label={t("productionDate")} | |||||
| fullWidth | fullWidth | ||||
| value={ | value={ | ||||
| // dayjs(itemDetail.expiryDate) | |||||
| // dayjs(itemDetail.productionDate) | |||||
| dayjs() | dayjs() | ||||
| .add(20, "day") | |||||
| // .add(-1, "month") | |||||
| .format(OUTPUT_DATE_FORMAT)} | .format(OUTPUT_DATE_FORMAT)} | ||||
| disabled | disabled | ||||
| /> | /> | ||||
| </Grid> | |||||
| </Grid> */} | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <FormControl fullWidth> | <FormControl fullWidth> | ||||
| <Autocomplete | <Autocomplete | ||||
| @@ -530,7 +516,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||||
| <Grid | <Grid | ||||
| item | item | ||||
| xs={12} | xs={12} | ||||
| style={{ display: "flex", justifyContent: "center" }} | |||||
| style={{ display: "flex", justifyContent: "center", marginTop:5 }} | |||||
| > | > | ||||
| {/* <QrCode content={qrContent} sx={{ width: 200, height: 200 }} /> */} | {/* <QrCode content={qrContent} sx={{ width: 200, height: 200 }} /> */} | ||||
| <InputDataGrid<PutAwayInput, PutAwayLine, EntryError> | <InputDataGrid<PutAwayInput, PutAwayLine, EntryError> | ||||
| @@ -548,24 +534,6 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| </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> | </Grid> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,6 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { PurchaseQcResult, PurchaseQCInput } from "@/app/api/po/actions"; | |||||
| import { PurchaseQcResult, PurchaseQCInput } from "@/app/api/stockIn/actions"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Card, | Card, | ||||
| @@ -39,7 +39,7 @@ import { TableRow } from "../InputDataGrid/InputDataGrid"; | |||||
| import TwoLineCell from "./TwoLineCell"; | import TwoLineCell from "./TwoLineCell"; | ||||
| import QcSelect from "./QcSelect"; | import QcSelect from "./QcSelect"; | ||||
| import { GridEditInputCell } from "@mui/x-data-grid"; | 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 { stockInLineStatusMap } from "@/app/utils/formatUtil"; | ||||
| import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; | import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; | ||||
| import { QcItemWithChecks, QcData } from "@/app/api/qc"; | import { QcItemWithChecks, QcData } from "@/app/api/qc"; | ||||
| @@ -49,16 +49,17 @@ import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||||
| import EscalationComponent from "./EscalationComponent"; | import EscalationComponent from "./EscalationComponent"; | ||||
| import QcDataGrid from "./QCDatagrid"; | import QcDataGrid from "./QCDatagrid"; | ||||
| import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate"; | import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate"; | ||||
| import { ModalFormInput } from "@/app/api/po/actions"; | |||||
| import { escape, min } from "lodash"; | import { escape, min } from "lodash"; | ||||
| import { PanoramaSharp } from "@mui/icons-material"; | import { PanoramaSharp } from "@mui/icons-material"; | ||||
| import EscalationLogTable from "../DashboardPage/escalation/EscalationLogTable"; | import EscalationLogTable from "../DashboardPage/escalation/EscalationLogTable"; | ||||
| import { EscalationResult } from "@/app/api/escalation"; | import { EscalationResult } from "@/app/api/escalation"; | ||||
| import { EscalationCombo } from "@/app/api/user"; | import { EscalationCombo } from "@/app/api/user"; | ||||
| import CollapsibleCard from "../CollapsibleCard/CollapsibleCard"; | import CollapsibleCard from "../CollapsibleCard/CollapsibleCard"; | ||||
| import LoadingComponent from "../General/LoadingComponent"; | |||||
| interface Props { | interface Props { | ||||
| itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||||
| itemDetail: StockInLine; | |||||
| // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||||
| // qc: QcItemWithChecks[]; | // qc: QcItemWithChecks[]; | ||||
| disabled: boolean; | disabled: boolean; | ||||
| // qcItems: QcData[] | // qcItems: QcData[] | ||||
| @@ -97,7 +98,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| // const qcResult = useMemo(() => [...watch("qcResult")], [watch("qcResult")]); | // const qcResult = useMemo(() => [...watch("qcResult")], [watch("qcResult")]); | ||||
| const qcRecord = useMemo(() => { // Need testing | 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] : []; | return Array.isArray(value) ? [...value] : []; | ||||
| }, [watch('qcResult')]); | }, [watch('qcResult')]); | ||||
| const [qcHistory, setQcHistory] = useState<PurchaseQcResult[]>([]); | 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; | 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 | // Check if failed items have failed quantity | ||||
| const failedItemsWithoutQty = qcResultItems.filter(item => | const failedItemsWithoutQty = qcResultItems.filter(item => | ||||
| item.qcPassed === false && (!item.failQty || item.failQty <= 0) | item.qcPassed === false && (!item.failQty || item.failQty <= 0) | ||||
| @@ -194,21 +195,6 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| validateForm(); | validateForm(); | ||||
| }, [clearErrors, validateForm]); | }, [clearErrors, validateForm]); | ||||
| const columns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "escalation", | |||||
| headerName: t("escalation"), | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| field: "supervisor", | |||||
| headerName: t("supervisor"), | |||||
| flex: 1, | |||||
| }, | |||||
| ], | |||||
| [], | |||||
| ); | |||||
| /// validate datagrid | /// validate datagrid | ||||
| const validation = useCallback( | const validation = useCallback( | ||||
| (newRow: GridRowModel<QcRow>): EntryError => { | (newRow: GridRowModel<QcRow>): EntryError => { | ||||
| @@ -220,16 +206,16 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| ); | ); | ||||
| function BooleanEditCell(params: GridRenderEditCellParams) { | 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) => { | const qcDisabled = (row : PurchaseQcResult) => { | ||||
| return disabled || isExist(row.escalationLogId); | return disabled || isExist(row.escalationLogId); | ||||
| @@ -396,12 +382,12 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| }, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]); | }, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]); | ||||
| useEffect(() => { | 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 | if (qcRecord.length < 1) { // New QC | ||||
| const fetchedQcData = dummyQCData; //TODO fetch from DB | const fetchedQcData = dummyQCData; //TODO fetch from DB | ||||
| setValue("qcResult", fetchedQcData); | setValue("qcResult", fetchedQcData); | ||||
| } else { | } 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) { | if (qcRecord.find((qc) => !isExist(qc.escalationLogId)) === undefined) { | ||||
| const copiedQcData = qcRecord.map(qc => ({ ...qc, escalationLogId: undefined })); | const copiedQcData = qcRecord.map(qc => ({ ...qc, escalationLogId: undefined })); | ||||
| const mutableQcData = [...qcRecord, ...copiedQcData]; | const mutableQcData = [...qcRecord, ...copiedQcData]; | ||||
| @@ -442,7 +428,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| useEffect(() => { | useEffect(() => { | ||||
| console.log("%c QC ItemDetail updated:", "color: gold", itemDetail); | |||||
| // console.log("%c QC ItemDetail updated:", "color: gold", itemDetail); | |||||
| }, [itemDetail]); | }, [itemDetail]); | ||||
| @@ -482,6 +468,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | <Grid container justifyContent="flex-start" alignItems="flex-start"> | ||||
| {itemDetail ? ( | |||||
| <Grid | <Grid | ||||
| container | container | ||||
| justifyContent="flex-start" | justifyContent="flex-start" | ||||
| @@ -577,127 +564,129 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| </> | </> | ||||
| )} | )} | ||||
| <Grid item xs={12}> | <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 { | } 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> | </Grid> | ||||
| {qcDecision == 3 && ( | {qcDecision == 3 && ( | ||||
| // {!qcAccept && ( | // {!qcAccept && ( | ||||
| @@ -721,6 +710,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => { | |||||
| /> | /> | ||||
| </Grid>} */} | </Grid>} */} | ||||
| </Grid> | </Grid> | ||||
| ) : <LoadingComponent/>} | |||||
| </Grid> | </Grid> | ||||
| </> | </> | ||||
| ); | ); | ||||
| @@ -1,6 +1,4 @@ | |||||
| "use client"; | "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 { QcItemWithChecks, QcData } from "@/app/api/qc"; | ||||
| import { | import { | ||||
| Autocomplete, | Autocomplete, | ||||
| @@ -38,6 +36,12 @@ import { GridRowModesModel } from "@mui/x-data-grid"; | |||||
| import { isEmpty } from "lodash"; | import { isEmpty } from "lodash"; | ||||
| import { EscalationCombo } from "@/app/api/user"; | import { EscalationCombo } from "@/app/api/user"; | ||||
| import { truncateSync } from "fs"; | 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 = { | const style = { | ||||
| @@ -54,42 +58,23 @@ const style = { | |||||
| height: { xs: "90%", sm: "90%", md: "90%" }, | height: { xs: "90%", sm: "90%", md: "90%" }, | ||||
| }; | }; | ||||
| interface CommonProps extends Omit<ModalProps, "children"> { | 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; | session: SessionWithTokens | null; | ||||
| qc?: QcItemWithChecks[]; | |||||
| warehouse?: any[]; | warehouse?: any[]; | ||||
| // type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; | |||||
| handleMailTemplateForStockInLine: (stockInLineId: number) => void; | |||||
| printerCombo: PrinterCombo[]; | printerCombo: PrinterCombo[]; | ||||
| onClose: () => void; | onClose: () => void; | ||||
| } | } | ||||
| interface Props extends CommonProps { | interface Props extends CommonProps { | ||||
| itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||||
| // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||||
| } | } | ||||
| const PoQcStockInModalVer2: React.FC<Props> = ({ | const PoQcStockInModalVer2: React.FC<Props> = ({ | ||||
| // type, | |||||
| // setRows, | |||||
| setEntries, | |||||
| setStockInLine, | |||||
| open, | open, | ||||
| onClose, | onClose, | ||||
| itemDetail, | |||||
| setItemDetail, | |||||
| // itemDetail, | |||||
| inputDetail, | |||||
| session, | session, | ||||
| qc, | |||||
| warehouse, | warehouse, | ||||
| handleMailTemplateForStockInLine, | |||||
| printerCombo, | printerCombo, | ||||
| }) => { | }) => { | ||||
| const { | const { | ||||
| @@ -97,6 +82,10 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| i18n: { language }, | i18n: { language }, | ||||
| } = useTranslation("purchaseOrder"); | } = useTranslation("purchaseOrder"); | ||||
| const [stockInLineInfo, setStockInLineInfo] = useState<StockInLine>(); | |||||
| const [isLoading, setIsLoading] = useState<Boolean>(false); | |||||
| // const [viewOnly, setViewOnly] = useState(false); | |||||
| // Select Printer | // Select Printer | ||||
| const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0]); | const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0]); | ||||
| const [printQty, setPrintQty] = useState(1); | 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>({ | const formProps = useForm<ModalFormInput>({ | ||||
| defaultValues: { | defaultValues: { | ||||
| ...defaultNewValue, | ...defaultNewValue, | ||||
| @@ -139,41 +208,45 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | ||||
| () => { | () => { | ||||
| setStockInLineInfo(undefined); | |||||
| formProps.reset({}); | |||||
| onClose?.(); | onClose?.(); | ||||
| // reset(); | |||||
| }, | }, | ||||
| [onClose], | [onClose], | ||||
| ); | ); | ||||
| const isPutaway = () => { | const isPutaway = () => { | ||||
| if (itemDetail) { | |||||
| const status = itemDetail.status; | |||||
| if (stockInLineInfo) { | |||||
| const status = stockInLineInfo.status; | |||||
| return status == "received"; | return status == "received"; | ||||
| } else return false; | } 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 [openPutaway, setOpenPutaway] = useState(false); | ||||
| const onOpenPutaway = useCallback(() => { | const onOpenPutaway = useCallback(() => { | ||||
| @@ -269,7 +342,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| alert("請輸入到期日!"); | alert("請輸入到期日!"); | ||||
| return; | 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("有不合格檢查項目,無法收貨!"); | validationErrors.push("有不合格檢查項目,無法收貨!"); | ||||
| // submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", | // submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", | ||||
| // confirmButtonText: t("confirm putaway"), html: ""}); | // confirmButtonText: t("confirm putaway"), html: ""}); | ||||
| @@ -279,7 +352,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| // Check if all QC items have results | // Check if all QC items have results | ||||
| const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined); | 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")}`); | ||||
| // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`); | // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`); | ||||
| } | } | ||||
| @@ -292,7 +365,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| const qcData = { | const qcData = { | ||||
| dnNo : data.dnNo? data.dnNo : "DN00000", | 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"), | productionDate : arrayToDateString(data.productionDate, "input"), | ||||
| expiryDate : arrayToDateString(data.expiryDate, "input"), | expiryDate : arrayToDateString(data.expiryDate, "input"), | ||||
| receiptDate : arrayToDateString(data.receiptDate, "input"), | receiptDate : arrayToDateString(data.receiptDate, "input"), | ||||
| @@ -345,69 +418,36 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| return ; | return ; | ||||
| }, | }, | ||||
| [onOpenPutaway, qcItems, formProps.formState.errors], | |||||
| [onOpenPutaway, formProps.formState.errors], | |||||
| ); | ); | ||||
| const postStockInLine = useCallback(async (args: ModalFormInput) => { | const postStockInLine = useCallback(async (args: ModalFormInput) => { | ||||
| const submitData = { | const submitData = { | ||||
| ...itemDetail, ...args | |||||
| ...stockInLineInfo, ...args | |||||
| } as StockInLineEntry & ModalFormInput; | } as StockInLineEntry & ModalFormInput; | ||||
| console.log("Submitting", submitData); | console.log("Submitting", submitData); | ||||
| const res = await updateStockInLine(submitData); | const res = await updateStockInLine(submitData); | ||||
| return res; | 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 | // Put away model | ||||
| const [pafRowModesModel, setPafRowModesModel] = useState<GridRowModesModel>({}) | const [pafRowModesModel, setPafRowModesModel] = useState<GridRowModesModel>({}) | ||||
| const [pafRowSelectionModel, setPafRowSelectionModel] = useState<GridRowSelectionModel>([]) | const [pafRowSelectionModel, setPafRowSelectionModel] = useState<GridRowSelectionModel>([]) | ||||
| const pafSubmitDisable = useMemo(() => { | 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) | return Object.entries(pafRowModesModel).length > 0 || Object.entries(pafRowModesModel).some(([key, value], index) => value.mode === GridRowModes.Edit) | ||||
| }, [pafRowModesModel]) | }, [pafRowModesModel]) | ||||
| // Putaway submission handler | // Putaway submission handler | ||||
| const onSubmitPutaway = useCallback<SubmitHandler<ModalFormInput>>( | const onSubmitPutaway = useCallback<SubmitHandler<ModalFormInput>>( | ||||
| async (data, event) => { | 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 | // Extract only putaway related fields | ||||
| const putawayData = { | 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, | warehouseId: data.warehouseId, | ||||
| status: data.status, //TODO Fix it! | status: data.status, //TODO Fix it! | ||||
| // ...data, | // ...data, | ||||
| dnDate : data.dnDate? arrayToDateString(data.dnDate, "input") : dayjsToInputDateString(dayjs()), | |||||
| // dnDate : data.dnDate? arrayToDateString(data.dnDate, "input") : dayjsToInputDateString(dayjs()), | |||||
| productionDate : arrayToDateString(data.productionDate, "input"), | productionDate : arrayToDateString(data.productionDate, "input"), | ||||
| expiryDate : arrayToDateString(data.expiryDate, "input"), | expiryDate : arrayToDateString(data.expiryDate, "input"), | ||||
| receiptDate : arrayToDateString(data.receiptDate, "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) | // const printQty = printList.reduce((acc, cur) => acc + cur.printQty, 0) | ||||
| // console.log(printQty) | // console.log(printQty) | ||||
| const data: PrintQrCodeForSilRequest = { | const data: PrintQrCodeForSilRequest = { | ||||
| stockInLineId: itemDetail.id, | |||||
| stockInLineId: stockInLineInfo?.id ?? 0, | |||||
| printerId: selectedPrinter.id, | printerId: selectedPrinter.id, | ||||
| printQty: printQty | printQty: printQty | ||||
| } | } | ||||
| @@ -475,32 +515,22 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| } finally { | } finally { | ||||
| setIsPrinting(() => false) | setIsPrinting(() => false) | ||||
| } | } | ||||
| }, [itemDetail.id, pafRowSelectionModel, printQty, selectedPrinter]); | |||||
| // }, [pafRowSelectionModel, printQty, selectedPrinter]); | |||||
| }, [stockInLineInfo?.id, pafRowSelectionModel, printQty, selectedPrinter]); | |||||
| const acceptQty = formProps.watch("acceptedQty") | 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 ( | return ( | ||||
| <> | <> | ||||
| <FormProvider {...formProps}> | <FormProvider {...formProps}> | ||||
| @@ -514,8 +544,11 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| marginLeft: 3, | marginLeft: 3, | ||||
| marginRight: 3, | marginRight: 3, | ||||
| // overflow: "hidden", | // overflow: "hidden", | ||||
| display: 'flex', | |||||
| flexDirection: 'column', | |||||
| }} | }} | ||||
| > | > | ||||
| {(!isLoading && stockInLineInfo) ? (<> | |||||
| <Box sx={{ position: 'sticky', top: 0, bgcolor: 'background.paper', | <Box sx={{ position: 'sticky', top: 0, bgcolor: 'background.paper', | ||||
| zIndex: 5, borderBottom: 2, borderColor: 'divider', width: "100%"}}> | zIndex: 5, borderBottom: 2, borderColor: 'divider', width: "100%"}}> | ||||
| <Tabs | <Tabs | ||||
| @@ -537,124 +570,92 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| sx={{padding: 2}} | sx={{padding: 2}} | ||||
| > | > | ||||
| <Grid item xs={12}> | <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 && | {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> | </Box> | ||||
| } | } | ||||
| </Grid> | </Grid> | ||||
| </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> | </Box> | ||||
| </Modal> | </Modal> | ||||
| </FormProvider> | </FormProvider> | ||||
| @@ -4,7 +4,7 @@ import { | |||||
| PurchaseQcResult, | PurchaseQcResult, | ||||
| PurchaseQCInput, | PurchaseQCInput, | ||||
| StockInInput, | StockInInput, | ||||
| } from "@/app/api/po/actions"; | |||||
| } from "@/app/api/stockIn/actions"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Card, | Card, | ||||
| @@ -34,7 +34,7 @@ import TwoLineCell from "./TwoLineCell"; | |||||
| import QcSelect from "./QcSelect"; | import QcSelect from "./QcSelect"; | ||||
| import { QcItemWithChecks } from "@/app/api/qc"; | import { QcItemWithChecks } from "@/app/api/qc"; | ||||
| import { GridEditInputCell } from "@mui/x-data-grid"; | 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 { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; | ||||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | ||||
| import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | ||||
| @@ -249,6 +249,19 @@ const StockInForm: React.FC<Props> = ({ | |||||
| </> | </> | ||||
| )} | )} | ||||
| <Grid item xs={6}> | <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 | <TextField | ||||
| label={t("productLotNo")} | label={t("productLotNo")} | ||||
| fullWidth | fullWidth | ||||
| @@ -259,7 +272,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
| disabled={disabled} | disabled={disabled} | ||||
| error={Boolean(errors.productLotNo)} | error={Boolean(errors.productLotNo)} | ||||
| helperText={errors.productLotNo?.message} | helperText={errors.productLotNo?.message} | ||||
| /> | |||||
| />)} | |||||
| </Grid> | </Grid> | ||||
| {putawayMode || (<> | {putawayMode || (<> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| @@ -350,8 +363,20 @@ const StockInForm: React.FC<Props> = ({ | |||||
| }} | }} | ||||
| /> | /> | ||||
| </Grid> | </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 | <TextField | ||||
| label={t("receivedQty")} | label={t("receivedQty")} | ||||
| fullWidth | fullWidth | ||||
| @@ -361,8 +386,8 @@ const StockInForm: React.FC<Props> = ({ | |||||
| sx={textfieldSx} | sx={textfieldSx} | ||||
| disabled={true} | disabled={true} | ||||
| /> | /> | ||||
| </Grid> | |||||
| )} | |||||
| )} | |||||
| </Grid> | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("uom")} | label={t("uom")} | ||||
| @@ -375,21 +400,8 @@ const StockInForm: React.FC<Props> = ({ | |||||
| disabled={true} | disabled={true} | ||||
| /> | /> | ||||
| </Grid> | </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 | <TextField | ||||
| label={t("processedQty")} | label={t("processedQty")} | ||||
| fullWidth | fullWidth | ||||
| @@ -401,9 +413,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
| // error={Boolean(errors.acceptedQty)} | // error={Boolean(errors.acceptedQty)} | ||||
| // helperText={errors.acceptedQty?.message} | // helperText={errors.acceptedQty?.message} | ||||
| /> | /> | ||||
| </Grid></> | |||||
| ) : ( | |||||
| <Grid item xs={6}> | |||||
| ) : ( | |||||
| <TextField | <TextField | ||||
| label={t("acceptedQty")} | label={t("acceptedQty")} | ||||
| fullWidth | fullWidth | ||||
| @@ -417,9 +427,8 @@ const StockInForm: React.FC<Props> = ({ | |||||
| // error={Boolean(errors.acceptedQty)} | // error={Boolean(errors.acceptedQty)} | ||||
| // helperText={errors.acceptedQty?.message} | // helperText={errors.acceptedQty?.message} | ||||
| /> | /> | ||||
| </Grid> | |||||
| ) | |||||
| } | |||||
| )} | |||||
| </Grid> | |||||
| {/* <Grid item xs={4}> | {/* <Grid item xs={4}> | ||||
| <TextField | <TextField | ||||
| label={t("acceptedWeight")} | label={t("acceptedWeight")} | ||||
| @@ -15,7 +15,7 @@ import { useSession } from "next-auth/react"; | |||||
| import { defaultPagingController } from "../SearchResults/SearchResults"; | import { defaultPagingController } from "../SearchResults/SearchResults"; | ||||
| import { fetchPoListClient, testing } from "@/app/api/po/actions"; | import { fetchPoListClient, testing } from "@/app/api/po/actions"; | ||||
| import dayjs from "dayjs"; | 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 arraySupport from "dayjs/plugin/arraySupport"; | ||||
| import { Checkbox, Box } from "@mui/material"; | import { Checkbox, Box } from "@mui/material"; | ||||
| dayjs.extend(arraySupport); | dayjs.extend(arraySupport); | ||||
| @@ -38,7 +38,7 @@ const PoSearch: React.FC<Props> = ({ | |||||
| const [selectedPoIds, setSelectedPoIds] = useState<number[]>([]); | const [selectedPoIds, setSelectedPoIds] = useState<number[]>([]); | ||||
| const [selectAll, setSelectAll] = useState(false); | const [selectAll, setSelectAll] = useState(false); | ||||
| const [filteredPo, setFilteredPo] = useState<PoResult[]>(po); | 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 { t } = useTranslation("purchaseOrder"); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const [pagingController, setPagingController] = useState( | const [pagingController, setPagingController] = useState( | ||||
| @@ -66,7 +66,8 @@ const PoSearch: React.FC<Props> = ({ | |||||
| { label: t(`completed`), value: `completed` }, | { 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; | return searchCriteria; | ||||
| @@ -19,11 +19,10 @@ import ReactQrCodeScanner, { | |||||
| import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | ||||
| import { | import { | ||||
| fetchStockInLineInfo, | fetchStockInLineInfo, | ||||
| ModalFormInput, | |||||
| StockInLineEntry, | StockInLineEntry, | ||||
| updateStockInLine, | 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 { WarehouseResult } from "@/app/api/warehouse"; | ||||
| // import { QrCodeInfo } from "@/app/api/qrcde"; | // import { QrCodeInfo } from "@/app/api/qrcde"; | ||||
| import { Check, QrCode, ErrorOutline, CheckCircle } from "@mui/icons-material"; | import { Check, QrCode, ErrorOutline, CheckCircle } from "@mui/icons-material"; | ||||
| @@ -94,7 +93,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||||
| { | { | ||||
| ...itemDetail, | ...itemDetail, | ||||
| // status: itemDetail.status ?? "pending", | // status: itemDetail.status ?? "pending", | ||||
| dnDate: arrayToDateString(itemDetail?.dnDate, "input")?? undefined, | |||||
| // dnDate: arrayToDateString(itemDetail?.dnDate, "input")?? undefined, | |||||
| // // putAwayLines: dummyPutAwayLine, | // // putAwayLines: dummyPutAwayLine, | ||||
| // // putAwayLines: itemDetail.putAwayLines.map((line) => (return {...line, printQty: 1})) ?? [], | // // putAwayLines: itemDetail.putAwayLines.map((line) => (return {...line, printQty: 1})) ?? [], | ||||
| // putAwayLines: itemDetail.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false})) ?? [], | // 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, | receiptDate: itemDetail?.receiptDate ? arrayToDateString(itemDetail?.receiptDate, "input") : undefined, | ||||
| // acceptQty: itemDetail.demandQty?? itemDetail.acceptedQty, | // acceptQty: itemDetail.demandQty?? itemDetail.acceptedQty, | ||||
| defaultWarehouseId: itemDetail?.defaultWarehouseId ?? 1, | defaultWarehouseId: itemDetail?.defaultWarehouseId ?? 1, | ||||
| } | |||||
| } as ModalFormInput | |||||
| ) | ) | ||||
| }, [itemDetail]) | }, [itemDetail]) | ||||
| @@ -218,7 +217,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (stockInLineId) { fetchStockInLine(stockInLineId); } | if (stockInLineId) { fetchStockInLine(stockInLineId); } | ||||
| }, [stockInLineId]); | |||||
| }, [stockInLineId]); | |||||
| const validateQty = useCallback((qty : number = putQty) => { | const validateQty = useCallback((qty : number = putQty) => { | ||||
| // if (isNaN(putQty) || putQty === undefined || putQty === null || typeof(putQty) != "number") { | // if (isNaN(putQty) || putQty === undefined || putQty === null || typeof(putQty) != "number") { | ||||
| @@ -15,13 +15,7 @@ import { | |||||
| ScannerConfig, | ScannerConfig, | ||||
| } from "../ReactQrCodeScanner/ReactQrCodeScanner"; | } from "../ReactQrCodeScanner/ReactQrCodeScanner"; | ||||
| import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | 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 { WarehouseResult } from "@/app/api/warehouse"; | ||||
| import { QrCodeInfo } from "@/app/api/qrcode"; | import { QrCodeInfo } from "@/app/api/qrcode"; | ||||
| import { Check, QrCodeScanner, Warehouse } from "@mui/icons-material"; | import { Check, QrCodeScanner, Warehouse } from "@mui/icons-material"; | ||||
| @@ -36,6 +36,8 @@ interface BaseCriterion<T extends string> { | |||||
| paramName: T; | paramName: T; | ||||
| paramName2?: T; | paramName2?: T; | ||||
| // options?: T[] | string[]; | // options?: T[] | string[]; | ||||
| defaultValue?: string; | |||||
| preFilledValue?: string; | |||||
| filterObj?: T; | filterObj?: T; | ||||
| handleSelectionChange?: (selectedOptions: T[]) => void; | handleSelectionChange?: (selectedOptions: T[]) => void; | ||||
| } | } | ||||
| @@ -136,18 +138,31 @@ function SearchBox<T extends string>({ | |||||
| if (c.type === "dateRange") { | if (c.type === "dateRange") { | ||||
| tempCriteria = { | tempCriteria = { | ||||
| ...tempCriteria, | ...tempCriteria, | ||||
| [c.paramName]: "", | |||||
| [c.paramName]: c.defaultValue ?? "", | |||||
| [`${c.paramName}To`]: "", | [`${c.paramName}To`]: "", | ||||
| }; | }; | ||||
| } | } | ||||
| return tempCriteria; | return tempCriteria; | ||||
| }, | }, | ||||
| {} as Record<T | `${T}To`, string>, | {} as Record<T | `${T}To`, string>, | ||||
| ), | ), | ||||
| [criteria], | [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 [isReset, setIsReset] = useState(false); | ||||
| const makeInputChangeHandler = useCallback( | const makeInputChangeHandler = useCallback( | ||||
| @@ -43,5 +43,6 @@ | |||||
| "Back": "返回", | "Back": "返回", | ||||
| "Batch Release": "批量放單", | "Batch Release": "批量放單", | ||||
| "Batch release completed successfully.": "已完成批量放單", | "Batch release completed successfully.": "已完成批量放單", | ||||
| "Edit Delivery Order Detail": "編輯交貨單詳情" | |||||
| "Edit Delivery Order Detail": "編輯交貨單詳情", | |||||
| "DO released successfully! Pick orders created.": "交貨單放單成功!提料單已建立。" | |||||
| } | } | ||||
| @@ -192,7 +192,7 @@ | |||||
| "Finished Good Order": "成品出倉", | "Finished Good Order": "成品出倉", | ||||
| "Assign and Release": "分派並放單", | "Assign and Release": "分派並放單", | ||||
| "Original Available Qty": "原可用數", | "Original Available Qty": "原可用數", | ||||
| "Remaining Available Qty": "剩餘", | |||||
| "Remaining Available Qty": "剩餘可用數", | |||||
| "Please submit pick order.": "請提交提料單。", | "Please submit pick order.": "請提交提料單。", | ||||
| "Please finish QR code scan and pick order.": "請完成 QR 碼掃描和提料。", | "Please finish QR code scan and pick order.": "請完成 QR 碼掃描和提料。", | ||||
| "Please finish QR code scanand pick order.": "請完成 QR 碼掃描和提料。", | "Please finish QR code scanand pick order.": "請完成 QR 碼掃描和提料。", | ||||
| @@ -273,6 +273,17 @@ | |||||
| "Print Draft":"列印草稿", | "Print Draft":"列印草稿", | ||||
| "Print Pick Order and DN Label":"列印提料單和送貨單標貼", | "Print Pick Order and DN Label":"列印提料單和送貨單標貼", | ||||
| "Print Pick Order":"列印提料單", | "Print Pick Order":"列印提料單", | ||||
| "Print DN Label":"列印送貨單標貼", | |||||
| "If you confirm, the system will:":"如果您確認,系統將:", | |||||
| "QR code verified.":"QR 碼驗證成功。", | |||||
| "Order Finished":"訂單完成", | |||||
| "Submitted Status":"提交狀態", | |||||
| "Pick Execution Record":"提料執行記錄", | |||||
| "Delivery No.":"送貨單編號", | |||||
| "Total":"總數", | |||||
| "completed DO pick orders":"已完成送貨單提料單", | |||||
| "No completed DO pick orders found":"沒有已完成送貨單提料單", | |||||
| "Print DN Label":"列印送貨單標貼", | "Print DN Label":"列印送貨單標貼", | ||||
| "Enter the number of cartons: ": "請輸入總箱數", | "Enter the number of cartons: ": "請輸入總箱數", | ||||
| "Number of cartons": "箱數" | "Number of cartons": "箱數" | ||||
| @@ -44,7 +44,7 @@ | |||||
| "price": "訂單貨值", | "price": "訂單貨值", | ||||
| "processedQty": "已上架數量", | "processedQty": "已上架數量", | ||||
| "expiryDate": "到期日", | "expiryDate": "到期日", | ||||
| "acceptedQty": "是次來貨數量", | |||||
| "acceptedQty": "本批收貨數量", | |||||
| "putawayQty": "上架數量", | "putawayQty": "上架數量", | ||||
| "acceptQty": "揀收數量", | "acceptQty": "揀收數量", | ||||
| "printQty": "列印數量", | "printQty": "列印數量", | ||||
| @@ -91,7 +91,7 @@ | |||||
| "to be processed": "待處理", | "to be processed": "待處理", | ||||
| "supervisor": "管理層", | "supervisor": "管理層", | ||||
| "Stock In Detail": "入庫詳情", | "Stock In Detail": "入庫詳情", | ||||
| "productLotNo": "貨品批號", | |||||
| "productLotNo": "來貨批號", | |||||
| "receiptDate": "收貨日期", | "receiptDate": "收貨日期", | ||||
| "acceptedWeight": "接受重量", | "acceptedWeight": "接受重量", | ||||
| "productionDate": "生產日期", | "productionDate": "生產日期", | ||||
| @@ -100,7 +100,7 @@ | |||||
| "Select warehouse": "選擇倉庫", | "Select warehouse": "選擇倉庫", | ||||
| "Putaway Detail": "上架詳情", | "Putaway Detail": "上架詳情", | ||||
| "Delivery Detail": "來貨詳情", | "Delivery Detail": "來貨詳情", | ||||
| "LotNo": "批號", | |||||
| "stockLotNo": "入倉批號", | |||||
| "Po Code": "採購訂單編號", | "Po Code": "採購訂單編號", | ||||
| "No Warehouse": "沒有倉庫", | "No Warehouse": "沒有倉庫", | ||||
| "Please scan warehouse qr code.": "請掃描倉庫 QR 碼。", | "Please scan warehouse qr code.": "請掃描倉庫 QR 碼。", | ||||