diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-21 07:54:26 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-21 07:54:26 +0000 |
| commit | 14f61e24947fb92dd71ec0a7196a6e815f8e66da (patch) | |
| tree | 317c501d64662d05914330628f867467fba78132 /lib/tech-vendors/validations.ts | |
| parent | 194bd4bd7e6144d5c09c5e3f5476d254234dce72 (diff) | |
(최겸)기술영업 RFQ 담당자 초대, 요구사항 반영
Diffstat (limited to 'lib/tech-vendors/validations.ts')
| -rw-r--r-- | lib/tech-vendors/validations.ts | 719 |
1 files changed, 398 insertions, 321 deletions
diff --git a/lib/tech-vendors/validations.ts b/lib/tech-vendors/validations.ts index 0c850c1f..618ad22e 100644 --- a/lib/tech-vendors/validations.ts +++ b/lib/tech-vendors/validations.ts @@ -1,321 +1,398 @@ -import { - createSearchParamsCache, - parseAsArrayOf, - parseAsInteger, - parseAsString, - parseAsStringEnum, -} from "nuqs/server" -import * as z from "zod" - -import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers" -import { techVendors, TechVendor, TechVendorContact, TechVendorItemsView, VENDOR_TYPES } from "@/db/schema/techVendors"; - -export const searchParamsCache = createSearchParamsCache({ - // 공통 플래그 - flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault( - [] - ), - - // 페이징 - page: parseAsInteger.withDefault(1), - perPage: parseAsInteger.withDefault(10), - - // 정렬 (techVendors 테이블에 맞춰 TechVendor 타입 지정) - sort: getSortingStateParser<TechVendor>().withDefault([ - { id: "createdAt", desc: true }, // createdAt 기준 내림차순 - ]), - - // 고급 필터 - filters: getFiltersStateParser().withDefault([]), - joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), - - // 검색 키워드 - search: parseAsString.withDefault(""), - - // ----------------------------------------------------------------- - // 기술영업 협력업체에 특화된 검색 필드 - // ----------------------------------------------------------------- - // 상태 (ACTIVE, INACTIVE, BLACKLISTED 등) 중에서 선택 - status: parseAsStringEnum(["ACTIVE", "INACTIVE", "BLACKLISTED", "PENDING_REVIEW"]), - - // 협력업체명 검색 - vendorName: parseAsString.withDefault(""), - - // 국가 검색 - country: parseAsString.withDefault(""), - - // 예) 코드 검색 - vendorCode: parseAsString.withDefault(""), - - // 벤더 타입 필터링 (다중 선택 가능) - vendorType: parseAsStringEnum(["ship", "top", "hull"]), - - // workTypes 필터링 (다중 선택 가능) - workTypes: parseAsArrayOf(parseAsStringEnum([ - // 조선 workTypes - "기장", "전장", "선실", "배관", "철의", - // 해양TOP workTypes - "TM", "TS", "TE", "TP", - // 해양HULL workTypes - "HA", "HE", "HH", "HM", "NC" - ])).withDefault([]), - - // 필요하다면 이메일 검색 / 웹사이트 검색 등 추가 가능 - email: parseAsString.withDefault(""), - website: parseAsString.withDefault(""), -}); - -export const searchParamsContactCache = createSearchParamsCache({ - // 공통 플래그 - flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault( - [] - ), - - // 페이징 - page: parseAsInteger.withDefault(1), - perPage: parseAsInteger.withDefault(10), - - // 정렬 - sort: getSortingStateParser<TechVendorContact>().withDefault([ - { id: "createdAt", desc: true }, // createdAt 기준 내림차순 - ]), - - // 고급 필터 - filters: getFiltersStateParser().withDefault([]), - joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), - - // 검색 키워드 - search: parseAsString.withDefault(""), - - // 특정 필드 검색 - contactName: parseAsString.withDefault(""), - contactPosition: parseAsString.withDefault(""), - contactEmail: parseAsString.withDefault(""), - contactPhone: parseAsString.withDefault(""), -}); - -export const searchParamsItemCache = createSearchParamsCache({ - // 공통 플래그 - flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault( - [] - ), - - // 페이징 - page: parseAsInteger.withDefault(1), - perPage: parseAsInteger.withDefault(10), - - // 정렬 - sort: getSortingStateParser<TechVendorItemsView>().withDefault([ - { id: "createdAt", desc: true }, // createdAt 기준 내림차순 - ]), - - // 고급 필터 - filters: getFiltersStateParser().withDefault([]), - joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), - - // 검색 키워드 - search: parseAsString.withDefault(""), - - // 특정 필드 검색 - itemName: parseAsString.withDefault(""), - itemCode: parseAsString.withDefault(""), -}); - -// 기술영업 벤더 기본 정보 업데이트 스키마 -export const updateTechVendorSchema = z.object({ - vendorName: z.string().min(1, "업체명은 필수 입력사항입니다"), - vendorCode: z.string().optional(), - address: z.string().optional(), - country: z.string().optional(), - phone: z.string().optional(), - email: z.string().email("유효한 이메일 주소를 입력해주세요").optional(), - website: z.string().url("유효한 URL을 입력해주세요").optional(), - techVendorType: z.union([ - z.array(z.enum(VENDOR_TYPES)).min(1, "최소 하나의 벤더 타입을 선택해주세요"), - z.string().min(1, "벤더 타입을 선택해주세요") - ]).optional(), - status: z.enum(techVendors.status.enumValues).optional(), - userId: z.number().optional(), - comment: z.string().optional(), -}); - -// 연락처 스키마 -const contactSchema = z.object({ - id: z.number().optional(), - contactName: z - .string() - .min(1, "Contact name is required") - .max(255, "Max length 255"), - contactPosition: z.string().max(100).optional(), - contactEmail: z.string().email("Invalid email").max(255), - contactPhone: z.string().max(50).optional(), - isPrimary: z.boolean().default(false).optional() -}); - -// 기술영업 벤더 생성 스키마 -export const createTechVendorSchema = z - .object({ - vendorName: z - .string() - .min(1, "Vendor name is required") - .max(255, "Max length 255"), - - email: z.string().email("Invalid email").max(255), - // 나머지 optional - vendorCode: z.string().max(100, "Max length 100").optional(), - address: z.string().optional(), - country: z.string() - .min(1, "국가 선택은 필수입니다.") - .max(100, "Max length 100"), - phone: z.string().max(50, "Max length 50").optional(), - website: z.string().max(255).optional(), - - files: z.any().optional(), - status: z.enum(techVendors.status.enumValues).default("ACTIVE"), - techVendorType: z.union([ - z.array(z.enum(VENDOR_TYPES)).min(1, "최소 하나의 벤더 타입을 선택해주세요"), - z.string().min(1, "벤더 타입을 선택해주세요") - ]).default(["조선"]), - - representativeName: z.union([z.string().max(255), z.literal("")]).optional(), - representativeBirth: z.union([z.string().max(20), z.literal("")]).optional(), - representativeEmail: z.union([z.string().email("Invalid email").max(255), z.literal("")]).optional(), - representativePhone: z.union([z.string().max(50), z.literal("")]).optional(), - taxId: z.string().min(1, { message: "사업자등록번호를 입력해주세요" }), - - items: z.string().min(1, { message: "공급품목을 입력해주세요" }), - - contacts: z - .array(contactSchema) - .nonempty("At least one contact is required.") - }) - .superRefine((data, ctx) => { - if (data.country === "KR") { - // 1) 대표자 정보가 누락되면 각각 에러 발생 - if (!data.representativeName) { - ctx.addIssue({ - code: "custom", - path: ["representativeName"], - message: "대표자 이름은 한국(KR) 업체일 경우 필수입니다.", - }) - } - if (!data.representativeBirth) { - ctx.addIssue({ - code: "custom", - path: ["representativeBirth"], - message: "대표자 생년월일은 한국(KR) 업체일 경우 필수입니다.", - }) - } - if (!data.representativeEmail) { - ctx.addIssue({ - code: "custom", - path: ["representativeEmail"], - message: "대표자 이메일은 한국(KR) 업체일 경우 필수입니다.", - }) - } - if (!data.representativePhone) { - ctx.addIssue({ - code: "custom", - path: ["representativePhone"], - message: "대표자 전화번호는 한국(KR) 업체일 경우 필수입니다.", - }) - } - - } - }); - -// 연락처 생성 스키마 -export const createTechVendorContactSchema = z.object({ - vendorId: z.number(), - contactName: z.string() - .min(1, "Contact name is required") - .max(255, "Max length 255"), - contactPosition: z.string().max(100, "Max length 100"), - contactEmail: z.string().email(), - contactPhone: z.string().max(50, "Max length 50").optional(), - country: z.string().max(100, "Max length 100").optional(), - isPrimary: z.boolean(), -}); - -// 연락처 업데이트 스키마 -export const updateTechVendorContactSchema = z.object({ - contactName: z.string() - .min(1, "Contact name is required") - .max(255, "Max length 255"), - contactPosition: z.string().max(100, "Max length 100").optional(), - contactEmail: z.string().email().optional(), - contactPhone: z.string().max(50, "Max length 50").optional(), - country: z.string().max(100, "Max length 100").optional(), - isPrimary: z.boolean().optional(), -}); - -// 아이템 생성 스키마 -export const createTechVendorItemSchema = z.object({ - vendorId: z.number(), - itemCode: z.string().max(100, "Max length 100"), - itemList: z.string().min(1, "Item list is required").max(255, "Max length 255"), -}); - -// 아이템 업데이트 스키마 -export const updateTechVendorItemSchema = z.object({ - itemList: z.string().optional(), - itemCode: z.string().max(100, "Max length 100"), -}); - -export const searchParamsRfqHistoryCache = createSearchParamsCache({ - // 공통 플래그 - flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault( - [] - ), - - // 페이징 - page: parseAsInteger.withDefault(1), - perPage: parseAsInteger.withDefault(10), - - // 정렬 (RFQ 히스토리에 맞춰) - sort: getSortingStateParser<{ - id: number; - rfqCode: string | null; - description: string | null; - projectCode: string | null; - projectName: string | null; - projectType: string | null; // 프로젝트 타입 추가 - status: string; - totalAmount: string | null; - currency: string | null; - dueDate: Date | null; - createdAt: Date; - quotationCode: string | null; - submittedAt: Date | null; - }>().withDefault([ - { id: "createdAt", desc: true }, - ]), - - // 고급 필터 - filters: getFiltersStateParser().withDefault([]), - joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), - - // 검색 키워드 - search: parseAsString.withDefault(""), - - // RFQ 히스토리 특화 필드 - rfqCode: parseAsString.withDefault(""), - description: parseAsString.withDefault(""), - projectCode: parseAsString.withDefault(""), - projectName: parseAsString.withDefault(""), - projectType: parseAsStringEnum(["SHIP", "TOP", "HULL"]), // 프로젝트 타입 필터 추가 - status: parseAsStringEnum(["DRAFT", "PUBLISHED", "EVALUATION", "AWARDED"]), -}); - -// 타입 내보내기 -export type GetTechVendorsSchema = Awaited<ReturnType<typeof searchParamsCache.parse>> -export type GetTechVendorContactsSchema = Awaited<ReturnType<typeof searchParamsContactCache.parse>> -export type GetTechVendorItemsSchema = Awaited<ReturnType<typeof searchParamsItemCache.parse>> -export type GetTechVendorRfqHistorySchema = Awaited<ReturnType<typeof searchParamsRfqHistoryCache.parse>> - -export type UpdateTechVendorSchema = z.infer<typeof updateTechVendorSchema> -export type CreateTechVendorSchema = z.infer<typeof createTechVendorSchema> -export type CreateTechVendorContactSchema = z.infer<typeof createTechVendorContactSchema> -export type UpdateTechVendorContactSchema = z.infer<typeof updateTechVendorContactSchema> -export type CreateTechVendorItemSchema = z.infer<typeof createTechVendorItemSchema> -export type UpdateTechVendorItemSchema = z.infer<typeof updateTechVendorItemSchema>
\ No newline at end of file +import {
+ createSearchParamsCache,
+ parseAsArrayOf,
+ parseAsInteger,
+ parseAsString,
+ parseAsStringEnum,
+} from "nuqs/server"
+import * as z from "zod"
+
+import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers"
+import { techVendors, TechVendor, TechVendorContact, TechVendorItemsView, VENDOR_TYPES } from "@/db/schema/techVendors";
+
+// TechVendorPossibleItem 타입 정의
+export interface TechVendorPossibleItem {
+ id: number;
+ vendorId: number;
+ vendorCode: string | null;
+ vendorEmail: string | null;
+ itemCode: string;
+ workType: string | null;
+ shipTypes: string | null;
+ itemList: string | null;
+ subItemList: string | null;
+ createdAt: Date;
+ updatedAt: Date;
+ // 조인된 정보
+ techVendorType?: "조선" | "해양TOP" | "해양HULL";
+}
+
+export const searchParamsCache = createSearchParamsCache({
+ // 공통 플래그
+ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault(
+ []
+ ),
+
+ // 페이징
+ page: parseAsInteger.withDefault(1),
+ perPage: parseAsInteger.withDefault(10),
+
+ // 정렬 (techVendors 테이블에 맞춰 TechVendor 타입 지정)
+ sort: getSortingStateParser<TechVendor>().withDefault([
+ { id: "createdAt", desc: true }, // createdAt 기준 내림차순
+ ]),
+
+ // 고급 필터
+ filters: getFiltersStateParser().withDefault([]),
+ joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
+
+ // 검색 키워드
+ search: parseAsString.withDefault(""),
+
+ // -----------------------------------------------------------------
+ // 기술영업 협력업체에 특화된 검색 필드
+ // -----------------------------------------------------------------
+ // 상태 (ACTIVE, INACTIVE, BLACKLISTED 등) 중에서 선택
+ status: parseAsStringEnum(["ACTIVE", "INACTIVE", "BLACKLISTED", "PENDING_REVIEW"]),
+
+ // 협력업체명 검색
+ vendorName: parseAsString.withDefault(""),
+
+ // 국가 검색
+ country: parseAsString.withDefault(""),
+
+ // 예) 코드 검색
+ vendorCode: parseAsString.withDefault(""),
+
+ // 벤더 타입 필터링 (다중 선택 가능)
+ vendorType: parseAsStringEnum(["ship", "top", "hull"]),
+
+ // workTypes 필터링 (다중 선택 가능)
+ workTypes: parseAsArrayOf(parseAsStringEnum([
+ // 조선 workTypes
+ "기장", "전장", "선실", "배관", "철의", "선체",
+ // 해양TOP workTypes
+ "TM", "TS", "TE", "TP",
+ // 해양HULL workTypes
+ "HA", "HE", "HH", "HM", "NC", "HO", "HP"
+ ])).withDefault([]),
+
+ // 필요하다면 이메일 검색 / 웹사이트 검색 등 추가 가능
+ email: parseAsString.withDefault(""),
+ website: parseAsString.withDefault(""),
+});
+
+export const searchParamsContactCache = createSearchParamsCache({
+ // 공통 플래그
+ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault(
+ []
+ ),
+
+ // 페이징
+ page: parseAsInteger.withDefault(1),
+ perPage: parseAsInteger.withDefault(10),
+
+ // 정렬
+ sort: getSortingStateParser<TechVendorContact>().withDefault([
+ { id: "createdAt", desc: true }, // createdAt 기준 내림차순
+ ]),
+
+ // 고급 필터
+ filters: getFiltersStateParser().withDefault([]),
+ joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
+
+ // 검색 키워드
+ search: parseAsString.withDefault(""),
+
+ // 특정 필드 검색
+ contactName: parseAsString.withDefault(""),
+ contactPosition: parseAsString.withDefault(""),
+ contactEmail: parseAsString.withDefault(""),
+ contactPhone: parseAsString.withDefault(""),
+});
+
+export const searchParamsItemCache = createSearchParamsCache({
+ // 공통 플래그
+ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault(
+ []
+ ),
+
+ // 페이징
+ page: parseAsInteger.withDefault(1),
+ perPage: parseAsInteger.withDefault(10),
+
+ // 정렬
+ sort: getSortingStateParser<TechVendorItemsView>().withDefault([
+ { id: "createdAt", desc: true }, // createdAt 기준 내림차순
+ ]),
+
+ // 고급 필터
+ filters: getFiltersStateParser().withDefault([]),
+ joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
+
+ // 검색 키워드
+ search: parseAsString.withDefault(""),
+
+ // 특정 필드 검색
+ itemName: parseAsString.withDefault(""),
+ itemCode: parseAsString.withDefault(""),
+});
+
+export const searchParamsPossibleItemsCache = createSearchParamsCache({
+ // 공통 플래그
+ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault(
+ []
+ ),
+
+ // 페이징
+ page: parseAsInteger.withDefault(1),
+ perPage: parseAsInteger.withDefault(10),
+
+ // 정렬
+ sort: getSortingStateParser<TechVendorPossibleItem>().withDefault([
+ { id: "createdAt", desc: true },
+ ]),
+
+ // 고급 필터
+ filters: getFiltersStateParser().withDefault([]),
+ joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
+
+ // 검색 키워드
+ search: parseAsString.withDefault(""),
+
+ // 개별 필터 필드들
+ itemCode: parseAsString.withDefault(""),
+ workType: parseAsString.withDefault(""),
+ itemList: parseAsString.withDefault(""),
+ shipTypes: parseAsString.withDefault(""),
+ subItemList: parseAsString.withDefault(""),
+});
+
+// 기술영업 벤더 기본 정보 업데이트 스키마
+export const updateTechVendorSchema = z.object({
+ vendorName: z.string().min(1, "업체명은 필수 입력사항입니다"),
+ vendorCode: z.string().optional(),
+ address: z.string().optional(),
+ country: z.string().optional(),
+ countryEng: z.string().optional(),
+ countryFab: z.string().optional(),
+ phone: z.string().optional(),
+ email: z.string().email("유효한 이메일 주소를 입력해주세요").optional(),
+ website: z.string().optional(),
+ techVendorType: z.union([
+ z.array(z.enum(VENDOR_TYPES)).min(1, "최소 하나의 벤더 타입을 선택해주세요"),
+ z.string().min(1, "벤더 타입을 선택해주세요")
+ ]).optional(),
+ status: z.enum(techVendors.status.enumValues).optional(),
+ // 에이전트 정보
+ agentName: z.string().optional(),
+ agentEmail: z.string().email("유효한 이메일 주소를 입력해주세요").optional().or(z.literal("")),
+ agentPhone: z.string().optional(),
+ // 대표자 정보
+ representativeName: z.string().optional(),
+ representativeEmail: z.string().email("유효한 이메일 주소를 입력해주세요").optional().or(z.literal("")),
+ representativePhone: z.string().optional(),
+ representativeBirth: z.string().optional(),
+ userId: z.number().optional(),
+ comment: z.string().optional(),
+});
+
+// 연락처 스키마
+const contactSchema = z.object({
+ id: z.number().optional(),
+ contactName: z
+ .string()
+ .min(1, "Contact name is required")
+ .max(255, "Max length 255"),
+ contactPosition: z.string().max(100).optional(),
+ contactEmail: z.string().email("Invalid email").max(255),
+ contactCountry: z.string().max(100).optional(),
+ contactPhone: z.string().max(50).optional(),
+ isPrimary: z.boolean().default(false).optional()
+});
+
+// 기술영업 벤더 생성 스키마
+export const createTechVendorSchema = z
+ .object({
+ vendorName: z
+ .string()
+ .min(1, "Vendor name is required")
+ .max(255, "Max length 255"),
+
+ email: z.string().email("Invalid email").max(255),
+ // 나머지 optional
+ vendorCode: z.string().max(100, "Max length 100").optional(),
+ address: z.string().optional(),
+ country: z.string()
+ .min(1, "국가 선택은 필수입니다.")
+ .max(100, "Max length 100"),
+ phone: z.string().max(50, "Max length 50").optional(),
+ website: z.string().max(255).optional(),
+
+ files: z.any().optional(),
+ status: z.enum(techVendors.status.enumValues).default("ACTIVE"),
+ techVendorType: z.union([
+ z.array(z.enum(VENDOR_TYPES)).min(1, "최소 하나의 벤더 타입을 선택해주세요"),
+ z.string().min(1, "벤더 타입을 선택해주세요")
+ ]).default(["조선"]),
+
+ representativeName: z.union([z.string().max(255), z.literal("")]).optional(),
+ representativeBirth: z.union([z.string().max(20), z.literal("")]).optional(),
+ representativeEmail: z.union([z.string().email("Invalid email").max(255), z.literal("")]).optional(),
+ representativePhone: z.union([z.string().max(50), z.literal("")]).optional(),
+ taxId: z.string().min(1, { message: "사업자등록번호를 입력해주세요" }),
+
+ items: z.string().min(1, { message: "공급품목을 입력해주세요" }),
+
+ contacts: z
+ .array(contactSchema)
+ .nonempty("At least one contact is required.")
+ })
+ .superRefine((data, ctx) => {
+ if (data.country === "KR") {
+ // 1) 대표자 정보가 누락되면 각각 에러 발생
+ if (!data.representativeName) {
+ ctx.addIssue({
+ code: "custom",
+ path: ["representativeName"],
+ message: "대표자 이름은 한국(KR) 업체일 경우 필수입니다.",
+ })
+ }
+ if (!data.representativeBirth) {
+ ctx.addIssue({
+ code: "custom",
+ path: ["representativeBirth"],
+ message: "대표자 생년월일은 한국(KR) 업체일 경우 필수입니다.",
+ })
+ }
+ if (!data.representativeEmail) {
+ ctx.addIssue({
+ code: "custom",
+ path: ["representativeEmail"],
+ message: "대표자 이메일은 한국(KR) 업체일 경우 필수입니다.",
+ })
+ }
+ if (!data.representativePhone) {
+ ctx.addIssue({
+ code: "custom",
+ path: ["representativePhone"],
+ message: "대표자 전화번호는 한국(KR) 업체일 경우 필수입니다.",
+ })
+ }
+
+ }
+ });
+
+// 연락처 생성 스키마
+export const createTechVendorContactSchema = z.object({
+ vendorId: z.number(),
+ contactName: z.string()
+ .min(1, "Contact name is required")
+ .max(255, "Max length 255"),
+ contactPosition: z.string().max(100, "Max length 100"),
+ contactEmail: z.string().email(),
+ contactPhone: z.string().max(50, "Max length 50").optional(),
+ contactCountry: z.string().max(100, "Max length 100").optional(),
+ isPrimary: z.boolean(),
+});
+
+// 연락처 업데이트 스키마
+export const updateTechVendorContactSchema = z.object({
+ contactName: z.string()
+ .min(1, "Contact name is required")
+ .max(255, "Max length 255"),
+ contactPosition: z.string().max(100, "Max length 100").optional(),
+ contactEmail: z.string().email().optional(),
+ contactPhone: z.string().max(50, "Max length 50").optional(),
+ contactCountry: z.string().max(100, "Max length 100").optional(),
+ isPrimary: z.boolean().optional(),
+});
+
+// 아이템 생성 스키마
+export const createTechVendorItemSchema = z.object({
+ vendorId: z.number(),
+ itemCode: z.string().max(100, "Max length 100"),
+ itemList: z.string().min(1, "Item list is required").max(255, "Max length 255"),
+});
+
+// 아이템 업데이트 스키마
+export const updateTechVendorItemSchema = z.object({
+ itemList: z.string().optional(),
+ itemCode: z.string().max(100, "Max length 100"),
+});
+
+// Possible Items 생성 스키마
+export const createTechVendorPossibleItemSchema = z.object({
+ vendorId: z.number(),
+ itemCode: z.string().min(1, "아이템 코드는 필수입니다"),
+ workType: z.string().optional(),
+ shipTypes: z.string().optional(),
+ itemList: z.string().optional(),
+ subItemList: z.string().optional(),
+});
+
+// Possible Items 업데이트 스키마
+export const updateTechVendorPossibleItemSchema = createTechVendorPossibleItemSchema.extend({
+ id: z.number(),
+});
+
+export const searchParamsRfqHistoryCache = createSearchParamsCache({
+ // 공통 플래그
+ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault(
+ []
+ ),
+
+ // 페이징
+ page: parseAsInteger.withDefault(1),
+ perPage: parseAsInteger.withDefault(10),
+
+ // 정렬 (RFQ 히스토리에 맞춰)
+ sort: getSortingStateParser<{
+ id: number;
+ rfqCode: string | null;
+ description: string | null;
+ projectCode: string | null;
+ projectName: string | null;
+ projectType: string | null; // 프로젝트 타입 추가
+ status: string;
+ totalAmount: string | null;
+ currency: string | null;
+ dueDate: Date | null;
+ createdAt: Date;
+ quotationCode: string | null;
+ submittedAt: Date | null;
+ }>().withDefault([
+ { id: "createdAt", desc: true },
+ ]),
+
+ // 고급 필터
+ filters: getFiltersStateParser().withDefault([]),
+ joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
+
+ // 검색 키워드
+ search: parseAsString.withDefault(""),
+
+ // RFQ 히스토리 특화 필드
+ rfqCode: parseAsString.withDefault(""),
+ description: parseAsString.withDefault(""),
+ projectCode: parseAsString.withDefault(""),
+ projectName: parseAsString.withDefault(""),
+ projectType: parseAsStringEnum(["SHIP", "TOP", "HULL"]), // 프로젝트 타입 필터 추가
+ status: parseAsStringEnum(["DRAFT", "PUBLISHED", "EVALUATION", "AWARDED"]),
+});
+
+// 타입 내보내기
+export type GetTechVendorsSchema = Awaited<ReturnType<typeof searchParamsCache.parse>>
+export type GetTechVendorContactsSchema = Awaited<ReturnType<typeof searchParamsContactCache.parse>>
+export type GetTechVendorItemsSchema = Awaited<ReturnType<typeof searchParamsItemCache.parse>>
+export type GetTechVendorPossibleItemsSchema = Awaited<ReturnType<typeof searchParamsPossibleItemsCache.parse>>
+export type GetTechVendorRfqHistorySchema = Awaited<ReturnType<typeof searchParamsRfqHistoryCache.parse>>
+
+export type UpdateTechVendorSchema = z.infer<typeof updateTechVendorSchema>
+export type CreateTechVendorSchema = z.infer<typeof createTechVendorSchema>
+export type CreateTechVendorContactSchema = z.infer<typeof createTechVendorContactSchema>
+export type UpdateTechVendorContactSchema = z.infer<typeof updateTechVendorContactSchema>
+export type CreateTechVendorItemSchema = z.infer<typeof createTechVendorItemSchema>
+export type UpdateTechVendorItemSchema = z.infer<typeof updateTechVendorItemSchema>
+export type CreateTechVendorPossibleItemSchema = z.infer<typeof createTechVendorPossibleItemSchema>
+export type UpdateTechVendorPossibleItemSchema = z.infer<typeof updateTechVendorPossibleItemSchema>
\ No newline at end of file |
