import { createSearchParamsCache, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum,parseAsBoolean } from "nuqs/server" import * as z from "zod" import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers" import { Rfq, rfqs, RfqsView, VendorCbeView, VendorResponseCBEView, VendorRfqViewBase, VendorTbeView } from "@/db/schema/rfq"; import { Vendor, vendors } from "@/db/schema/vendors"; export const RfqType = { PURCHASE_BUDGETARY: "PURCHASE_BUDGETARY", PURCHASE: "PURCHASE", BUDGETARY: "BUDGETARY" } as const; export type RfqType = typeof RfqType[keyof typeof RfqType]; // ======================= // 1) SearchParams (목록 필터링/정렬) // ======================= export const searchParamsCache = createSearchParamsCache({ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]), page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(10), sort: getSortingStateParser().withDefault([ { id: "createdAt", desc: true }, ]), // 간단 검색 필드 rfqCode: parseAsString.withDefault(""), projectCode: parseAsString.withDefault(""), projectName: parseAsString.withDefault(""), dueDate: parseAsString.withDefault(""), // 상태 - 여러 개일 수 있다고 가정 status: parseAsArrayOf(z.enum(rfqs.status.enumValues)).withDefault([]), // 고급 필터 filters: getFiltersStateParser().withDefault([]), joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), search: parseAsString.withDefault(""), rfqType: parseAsStringEnum(["PURCHASE", "BUDGETARY", "PURCHASE_BUDGETARY"]).withDefault("PURCHASE"), }); export type GetRfqsSchema = Awaited>; export const searchParamsMatchedVCache = createSearchParamsCache({ // 1) 공통 플래그 flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]), // 2) 페이지네이션 page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(10), // 3) 정렬 (Rfq 테이블) // getSortingStateParser() → Rfq 테이블의 컬럼명에 맞춘 유효성 검사 sort: getSortingStateParser().withDefault([ { id: "rfqVendorUpdated", desc: true }, ]), // 4) 간단 검색 필드 vendorName: parseAsString.withDefault(""), vendorCode: parseAsString.withDefault(""), country: parseAsString.withDefault(""), email: parseAsString.withDefault(""), website: parseAsString.withDefault(""), // 5) 상태 (배열) - Rfq["status"]는 "DRAFT"|"PUBLISHED"|"EVALUATION"|"AWARDED" // rfqs.status.enumValues 로 가져온 문자열 배열을 z.enum([...])로 처리 vendorStatus: parseAsArrayOf(z.enum(vendors.status.enumValues)).withDefault([]), // 6) 고급 필터 (nuqs - filterColumns) filters: getFiltersStateParser().withDefault([]), joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), // 7) 글로벌 검색어 search: parseAsString.withDefault(""), }) export type GetMatchedVendorsSchema = Awaited>; export const searchParamsTBECache = createSearchParamsCache({ // 1) 공통 플래그 flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]), // 2) 페이지네이션 page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(10), // 3) 정렬 (Rfq 테이블) // getSortingStateParser() → Rfq 테이블의 컬럼명에 맞춘 유효성 검사 sort: getSortingStateParser().withDefault([ { id: "tbeUpdated", desc: true }, ]), // 4) 간단 검색 필드 vendorName: parseAsString.withDefault(""), vendorCode: parseAsString.withDefault(""), country: parseAsString.withDefault(""), email: parseAsString.withDefault(""), website: parseAsString.withDefault(""), tbeResult: parseAsString.withDefault(""), tbeNote: parseAsString.withDefault(""), tbeUpdated: parseAsString.withDefault(""), rfqType: parseAsStringEnum(["PURCHASE", "BUDGETARY", "PURCHASE_BUDGETARY"]).withDefault("PURCHASE"), // 5) 상태 (배열) - Rfq["status"]는 "DRAFT"|"PUBLISHED"|"EVALUATION"|"AWARDED" // rfqs.status.enumValues 로 가져온 문자열 배열을 z.enum([...])로 처리 vendorStatus: parseAsArrayOf(z.enum(vendors.status.enumValues)).withDefault([]), // 6) 고급 필터 (nuqs - filterColumns) filters: getFiltersStateParser().withDefault([]), joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), // 7) 글로벌 검색어 search: parseAsString.withDefault(""), }) export type GetTBESchema = Awaited>; // ======================= // 2) Create RFQ Schema // ======================= export const createRfqSchema = z.object({ rfqCode: z.string().min(3, "RFQ 코드는 최소 3글자 이상이어야 합니다"), description: z.string().optional(), projectId: z.number().nullable().optional(), // 프로젝트 ID (선택적) bidProjectId: z.number().nullable().optional(), // 프로젝트 ID (선택적) parentRfqId: z.number().nullable().optional(), // 부모 RFQ ID (선택적) dueDate: z.date(), status: z.enum(["DRAFT", "PUBLISHED", "EVALUATION", "AWARDED"]), rfqType: z.enum([RfqType.PURCHASE, RfqType.BUDGETARY, RfqType.PURCHASE_BUDGETARY]).default(RfqType.PURCHASE), createdBy: z.number(), }); export type CreateRfqSchema = z.infer; export const createRfqItemSchema = z.object({ rfqId: z.number().int().min(1, "Invalid RFQ ID"), itemCode: z.string().min(1), itemName: z.string().optional(), description: z.string().optional(), quantity: z.number().min(1).optional(), uom: z.string().optional(), rfqType: z.string().default("PURCHASE"), // rfqType 필드 추가 }); export type CreateRfqItemSchema = z.infer; // ======================= // 3) Update RFQ Schema // (현재 코드엔 updateTaskSchema라고 되어 있는데, // RFQ 업데이트이므로 'updateRfqSchema'라 명명하는 게 자연스러움) // ======================= export const updateRfqSchema = z.object({ // PK id -> 실제로는 URL params로 받을 수도 있지만, // 여기서는 body에서 받는다고 가정 id: z.number().int().min(1, "Invalid ID"), // 업데이트 시 대부분 optional rfqCode: z.string().max(50).optional(), projectId: z.number().nullable().optional(), // null 값도 허용 description: z.string().optional(), parentRfqId: z.number().nullable().optional(), // 부모 RFQ ID (선택적) dueDate: z.preprocess( // null이나 빈 문자열을 undefined로 변환 (val) => (val === null || val === '') ? undefined : val, z.date().optional() ), rfqType: z.enum(["PURCHASE", "BUDGETARY", "PURCHASE_BUDGETARY"]).optional(), status: z.union([ z.enum(["DRAFT", "PUBLISHED", "EVALUATION", "AWARDED"]), z.string().refine( (val) => ["DRAFT", "PUBLISHED", "EVALUATION", "AWARDED"].includes(val), { message: "Invalid status value" } ) ]).optional(), createdBy: z.number().int().min(1).optional(), }); export type UpdateRfqSchema = z.infer; export const searchParamsRfqsForVendorsCache = createSearchParamsCache({ // 1) 공통 플래그 flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]), // 2) 페이지네이션 page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(10), // 3) 정렬 (rfqs 테이블) sort: getSortingStateParser().withDefault([ { id: "createdAt", desc: true }, ]), // 4) 간단 검색 필드 (예: rfqCode, projectName, projectCode 등) rfqCode: parseAsString.withDefault(""), projectCode: parseAsString.withDefault(""), projectName: parseAsString.withDefault(""), // 5) 상태 배열 (rfqs.status.enumValues: "DRAFT" | "PUBLISHED" | ...) status: parseAsArrayOf(z.enum(rfqs.status.enumValues)).withDefault([]), // 6) 고급 필터 (nuqs filterColumns) filters: getFiltersStateParser().withDefault([]), joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), // 7) 글로벌 검색어 search: parseAsString.withDefault(""), }) /** * 최종 타입 * `Awaited>` 형태로 * Next.js 13 서버 액션이나 클라이언트에서 사용 가능 */ export type GetRfqsForVendorsSchema = Awaited> export const updateRfqVendorSchema = z.object({ id: z.number().int().min(1, "Invalid ID"), // rfq_vendors.id status: z.enum(["INVITED","ACCEPTED","DECLINED","REVIEWING", "RESPONDED"]) }) export type UpdateRfqVendorSchema = z.infer export const searchParamsCBECache = createSearchParamsCache({ // 1) 공통 플래그 flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]), // 2) 페이지네이션 page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(10), // 3) 정렬 (VendorResponseCBEView 테이블) // getSortingStateParser() → CBE 테이블의 컬럼명에 맞춤 sort: getSortingStateParser().withDefault([ { id: "totalPrice", desc: true }, ]), // 4) 간단 검색 필드 - 기본 정보 vendorName: parseAsString.withDefault(""), vendorCode: parseAsString.withDefault(""), country: parseAsString.withDefault(""), email: parseAsString.withDefault(""), website: parseAsString.withDefault(""), // CBE 관련 필드 commercialResponseId: parseAsString.withDefault(""), totalPrice: parseAsString.withDefault(""), currency: parseAsString.withDefault(""), paymentTerms: parseAsString.withDefault(""), incoterms: parseAsString.withDefault(""), deliveryPeriod: parseAsString.withDefault(""), warrantyPeriod: parseAsString.withDefault(""), validityPeriod: parseAsString.withDefault(""), // RFQ 관련 필드 rfqType: parseAsStringEnum(["PURCHASE", "BUDGETARY", "PURCHASE_BUDGETARY"]).withDefault("PURCHASE"), // 응답 상태 responseStatus: parseAsStringEnum(["INVITED", "ACCEPTED", "DECLINED", "REVIEWING", "RESPONDED"]).withDefault("REVIEWING"), // 5) 상태 (배열) - vendor 상태 vendorStatus: parseAsArrayOf(z.enum(vendors.status.enumValues)).withDefault([]), // 6) 고급 필터 (nuqs - filterColumns) filters: getFiltersStateParser().withDefault([]), joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), // 7) 글로벌 검색어 search: parseAsString.withDefault(""), // 8) 첨부파일 관련 필터 hasAttachments: parseAsBoolean.withDefault(false), // 9) 날짜 범위 필터 respondedAtRange: parseAsString.withDefault(""), commercialUpdatedAtRange: parseAsString.withDefault(""), }) export type GetCBESchema = Awaited>; export const createCbeEvaluationSchema = z.object({ paymentTerms: z.string().min(1, "결제 조건을 입력하세요"), incoterms: z.string().min(1, "Incoterms를 입력하세요"), deliverySchedule: z.string().min(1, "배송 일정을 입력하세요"), notes: z.string().optional(), }) // 타입 추출 export type CreateCbeEvaluationSchema = z.infer