diff options
Diffstat (limited to 'lib/rfqs-tech/validations.ts')
| -rw-r--r-- | lib/rfqs-tech/validations.ts | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/lib/rfqs-tech/validations.ts b/lib/rfqs-tech/validations.ts new file mode 100644 index 00000000..82b0934e --- /dev/null +++ b/lib/rfqs-tech/validations.ts @@ -0,0 +1,284 @@ +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, VendorResponseCBEView, VendorRfqViewBase, VendorTbeView } from "@/db/schema/rfq"; +import { vendors } from "@/db/schema/vendors"; + +// ======================= +// 1) SearchParams (목록 필터링/정렬) +// ======================= +export const searchParamsCache = createSearchParamsCache({ + // 1) 공통 플래그 + flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]), + + // 2) 페이지네이션 + page: parseAsInteger.withDefault(1), + perPage: parseAsInteger.withDefault(10), + + // 3) 정렬 (Rfq 테이블) + // getSortingStateParser<Rfq>() → Rfq 테이블의 컬럼명에 맞춘 유효성 검사 + sort: getSortingStateParser<RfqsView>().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(""), + +}); + +export type GetRfqsSchema = Awaited<ReturnType<typeof searchParamsCache.parse>>; + + +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>() → Rfq 테이블의 컬럼명에 맞춘 유효성 검사 + sort: getSortingStateParser<VendorRfqViewBase>().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<ReturnType<typeof searchParamsMatchedVCache.parse>>; + +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>() → Rfq 테이블의 컬럼명에 맞춘 유효성 검사 + sort: getSortingStateParser<VendorTbeView>().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(""), + + // 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<ReturnType<typeof searchParamsTBECache.parse>>; + +// ======================= +// 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 (선택적) + dueDate: z.date(), + status: z.enum(["DRAFT", "PUBLISHED", "EVALUATION", "AWARDED"]), + createdBy: z.number(), +}); + +export type CreateRfqSchema = z.infer<typeof createRfqSchema>; + +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(), +}); + +export type CreateRfqItemSchema = z.infer<typeof createRfqItemSchema>; + +// ======================= +// 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(), + dueDate: z.preprocess( + // null이나 빈 문자열을 undefined로 변환 + (val) => (val === null || val === '') ? undefined : val, + z.date().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<typeof updateRfqSchema>; + +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<Rfq>().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<ReturnType<...parse>>` 형태로 + * Next.js 13 서버 액션이나 클라이언트에서 사용 가능 + */ +export type GetRfqsForVendorsSchema = Awaited<ReturnType<typeof searchParamsRfqsForVendorsCache.parse>> + +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<typeof updateRfqVendorSchema> + + +export const searchParamsCBECache = createSearchParamsCache({ + // 1) 공통 플래그 + flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]), + + // 2) 페이지네이션 + page: parseAsInteger.withDefault(1), + perPage: parseAsInteger.withDefault(10), + + // 3) 정렬 (VendorResponseCBEView 테이블) + // getSortingStateParser<VendorResponseCBEView>() → CBE 테이블의 컬럼명에 맞춤 + sort: getSortingStateParser<VendorResponseCBEView>().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(""), + + // 응답 상태 + 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<ReturnType<typeof searchParamsCBECache.parse>>; + + +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<typeof createCbeEvaluationSchema>
\ No newline at end of file |
