summaryrefslogtreecommitdiff
path: root/lib/bidding/validation.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/validation.ts')
-rw-r--r--lib/bidding/validation.ts157
1 files changed, 157 insertions, 0 deletions
diff --git a/lib/bidding/validation.ts b/lib/bidding/validation.ts
new file mode 100644
index 00000000..3d47aefe
--- /dev/null
+++ b/lib/bidding/validation.ts
@@ -0,0 +1,157 @@
+import { biddings, type Bidding } from "@/db/schema"
+import {
+ createSearchParamsCache,
+ parseAsArrayOf,
+ parseAsInteger,
+ parseAsString,
+ parseAsStringEnum,
+} from "nuqs/server"
+import * as z from "zod"
+
+import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers"
+
+export const searchParamsCache = createSearchParamsCache({
+ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]),
+ page: parseAsInteger.withDefault(1),
+ perPage: parseAsInteger.withDefault(10),
+ sort: getSortingStateParser<Bidding>().withDefault([
+ { id: "createdAt", desc: true },
+ ]),
+
+ // 기본 필터
+ biddingNumber: parseAsString.withDefault(""),
+ status: parseAsArrayOf(z.enum(biddings.status.enumValues)).withDefault([]),
+ biddingType: parseAsArrayOf(z.enum(biddings.biddingType.enumValues)).withDefault([]),
+ contractType: parseAsArrayOf(z.enum(biddings.contractType.enumValues)).withDefault([]),
+ managerName: parseAsString.withDefault(""),
+
+ // 날짜 필터
+ preQuoteDateFrom: parseAsString.withDefault(""),
+ preQuoteDateTo: parseAsString.withDefault(""),
+ submissionDateFrom: parseAsString.withDefault(""),
+ submissionDateTo: parseAsString.withDefault(""),
+ createdAtFrom: parseAsString.withDefault(""),
+ createdAtTo: parseAsString.withDefault(""),
+
+ // 가격 필터
+ budgetMin: parseAsString.withDefault(""),
+ budgetMax: parseAsString.withDefault(""),
+
+ // Boolean 필터
+ hasSpecificationMeeting: parseAsString.withDefault(""), // "true" | "false" | ""
+ hasPrDocument: parseAsString.withDefault(""), // "true" | "false" | ""
+
+ // 고급 필터
+ filters: getFiltersStateParser().withDefault([]),
+ joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
+ search: parseAsString.withDefault(""),
+})
+
+export const createBiddingSchema = z.object({
+ // ❌ 제거: biddingNumber (자동 생성)
+ // ❌ 제거: preQuoteDate (나중에 자동 기록)
+ // ❌ 제거: biddingRegistrationDate (시스템에서 자동 기록)
+
+ revision: z.number().int().min(0).default(0),
+
+ // ✅ 프로젝트 정보 (새로 추가)
+ projectId: z.number().min(1, "프로젝트를 선택해주세요"), // 필수
+ projectName: z.string().optional(), // ProjectSelector에서 자동 설정
+
+ // ✅ 필수 필드들
+ itemName: z.string().min(1, "품목명은 필수입니다"),
+ title: z.string().min(1, "입찰명은 필수입니다"),
+ description: z.string().optional(),
+ content: z.string().optional(),
+
+ // ✅ 계약 정보 (필수)
+ contractType: z.enum(biddings.contractType.enumValues, {
+ required_error: "계약구분을 선택해주세요"
+ }),
+ biddingType: z.enum(biddings.biddingType.enumValues, {
+ required_error: "입찰유형을 선택해주세요"
+ }),
+ awardCount: z.enum(biddings.awardCount.enumValues, {
+ required_error: "낙찰수를 선택해주세요"
+ }),
+ contractPeriod: z.string().min(1, "계약기간은 필수입니다"),
+
+ // ✅ 일정 (제출기간 필수)
+ submissionStartDate: z.string().min(1, "제출시작일시는 필수입니다"),
+ submissionEndDate: z.string().min(1, "제출마감일시는 필수입니다"),
+ evaluationDate: z.string().optional(),
+
+ // 회의 및 문서
+ hasSpecificationMeeting: z.boolean().default(false),
+ hasPrDocument: z.boolean().default(false),
+ prNumber: z.string().optional(),
+
+ // ✅ 가격 정보 (통화 필수)
+ currency: z.string().min(1, "통화를 선택해주세요").default("KRW"),
+ budget: z.string().optional(),
+ targetPrice: z.string().optional(),
+ finalBidPrice: z.string().optional(),
+
+ // 상태 및 담당자
+ status: z.enum(biddings.status.enumValues).default("bidding_generated"),
+ isPublic: z.boolean().default(false),
+ managerName: z.string().optional(),
+ managerEmail: z.string().email().optional().or(z.literal("")),
+ managerPhone: z.string().optional(),
+
+ // 메타
+ remarks: z.string().optional(),
+ }).refine((data) => {
+ // 제출 기간 검증: 시작일이 마감일보다 이전이어야 함
+ if (data.submissionStartDate && data.submissionEndDate) {
+ const startDate = new Date(data.submissionStartDate)
+ const endDate = new Date(data.submissionEndDate)
+ return startDate < endDate
+ }
+ return true
+ }, {
+ message: "제출시작일시가 제출마감일시보다 늦을 수 없습니다",
+ path: ["submissionEndDate"]
+ })
+
+ export const updateBiddingSchema = z.object({
+ biddingNumber: z.string().min(1, "입찰번호는 필수입니다").optional(),
+ revision: z.number().int().min(0).optional(),
+
+ projectId: z.number().min(1).optional(),
+ projectName: z.string().optional(),
+ itemName: z.string().min(1, "품목명은 필수입니다").optional(),
+ title: z.string().min(1, "입찰명은 필수입니다").optional(),
+ description: z.string().optional(),
+ content: z.string().optional(),
+
+ contractType: z.enum(biddings.contractType.enumValues).optional(),
+ biddingType: z.enum(biddings.biddingType.enumValues).optional(),
+ awardCount: z.enum(biddings.awardCount.enumValues).optional(),
+ contractPeriod: z.string().optional(),
+
+ submissionStartDate: z.string().optional(),
+ submissionEndDate: z.string().optional(),
+ evaluationDate: z.string().optional(),
+
+ hasSpecificationMeeting: z.boolean().optional(),
+ hasPrDocument: z.boolean().optional(),
+ prNumber: z.string().optional(),
+
+ currency: z.string().optional(),
+ budget: z.string().optional(),
+ targetPrice: z.string().optional(),
+ finalBidPrice: z.string().optional(),
+
+ status: z.enum(biddings.status.enumValues).optional(),
+ isPublic: z.boolean().optional(),
+ managerName: z.string().optional(),
+ managerEmail: z.string().email().optional().or(z.literal("")),
+ managerPhone: z.string().optional(),
+
+ remarks: z.string().optional(),
+ })
+
+ export type GetBiddingsSchema = Awaited<ReturnType<typeof searchParamsCache.parse>>
+ export type CreateBiddingSchema = z.infer<typeof createBiddingSchema>
+ export type UpdateBiddingSchema = z.infer<typeof updateBiddingSchema>