summaryrefslogtreecommitdiff
path: root/lib/rfqs-tech/validations.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rfqs-tech/validations.ts')
-rw-r--r--lib/rfqs-tech/validations.ts284
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