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.ts260
1 files changed, 260 insertions, 0 deletions
diff --git a/lib/tech-vendors/validations.ts b/lib/tech-vendors/validations.ts
new file mode 100644
index 00000000..8bba3103
--- /dev/null
+++ b/lib/tech-vendors/validations.ts
@@ -0,0 +1,260 @@
+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", "IN_REVIEW", "REJECTED"]),
+
+ // 협력업체명 검색
+ vendorName: parseAsString.withDefault(""),
+
+ // 국가 검색
+ country: parseAsString.withDefault(""),
+
+ // 예) 코드 검색
+ vendorCode: parseAsString.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(),
+ 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().url("유효하지 않은 URL입니다. https:// 혹은 http:// 로 시작하는 주소를 입력해주세요.").max(255).optional(),
+
+ files: z.any().optional(),
+ status: z.enum(techVendors.status.enumValues).default("PENDING_REVIEW"),
+ techVendorType: z.enum(VENDOR_TYPES).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(),
+ corporateRegistrationNumber: z.union([z.string().max(100), 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) 업체일 경우 필수입니다.",
+ })
+ }
+ if (!data.corporateRegistrationNumber) {
+ ctx.addIssue({
+ code: "custom",
+ path: ["corporateRegistrationNumber"],
+ 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(),
+ 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(),
+ isPrimary: z.boolean().optional(),
+});
+
+// 아이템 생성 스키마
+export const createTechVendorItemSchema = z.object({
+ vendorId: z.number(),
+ itemCode: z.string().max(100, "Max length 100"),
+ itemName: z.string().min(1, "Item name is required").max(255, "Max length 255"),
+});
+
+// 아이템 업데이트 스키마
+export const updateTechVendorItemSchema = z.object({
+ itemName: z.string().optional(),
+ itemCode: z.string().max(100, "Max length 100"),
+});
+
+// 타입 내보내기
+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 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