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