diff options
Diffstat (limited to 'lib/bidding/validation.ts')
| -rw-r--r-- | lib/bidding/validation.ts | 170 |
1 files changed, 130 insertions, 40 deletions
diff --git a/lib/bidding/validation.ts b/lib/bidding/validation.ts index 8476be1c..5cf296e1 100644 --- a/lib/bidding/validation.ts +++ b/lib/bidding/validation.ts @@ -1,4 +1,4 @@ -import { BiddingListView, biddings } from "@/db/schema" +import { BiddingListItem, biddings } from "@/db/schema" import { createSearchParamsCache, parseAsArrayOf, @@ -14,7 +14,7 @@ export const searchParamsCache = createSearchParamsCache({ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]), page: parseAsInteger.withDefault(1), perPage: parseAsInteger.withDefault(10), - sort: getSortingStateParser<BiddingListView>().withDefault([ + sort: getSortingStateParser<BiddingListItem>().withDefault([ { id: "createdAt", desc: true }, ]), @@ -23,6 +23,7 @@ export const searchParamsCache = createSearchParamsCache({ status: parseAsArrayOf(z.enum(biddings.status.enumValues)).withDefault([]), biddingType: parseAsArrayOf(z.enum(biddings.biddingType.enumValues)).withDefault([]), contractType: parseAsArrayOf(z.enum(biddings.contractType.enumValues)).withDefault([]), + purchasingOrganization: parseAsString.withDefault(""), managerName: parseAsString.withDefault(""), // 날짜 필터 @@ -51,19 +52,24 @@ export const createBiddingSchema = z.object({ // ❌ 제거: biddingNumber (자동 생성) // ❌ 제거: preQuoteDate (나중에 자동 기록) // ❌ 제거: biddingRegistrationDate (시스템에서 자동 기록) - + revision: z.number().int().min(0).default(0), - - // ✅ 프로젝트 정보 (새로 추가) - projectId: z.number().min(1, "프로젝트를 선택해주세요"), // 필수 + + // ✅ 프로젝트 정보 (새로 추가) - 임시로 optional로 변경 + projectId: z.number().optional(), // 임시로 필수 해제 projectName: z.string().optional(), // ProjectSelector에서 자동 설정 - + // ✅ 필수 필드들 - itemName: z.string().min(1, "품목명은 필수입니다"), + itemName: z.string().optional(), // 임시로 필수 해제 title: z.string().min(1, "입찰명은 필수입니다"), description: z.string().optional(), content: z.string().optional(), - + + // 입찰공고 정보 + noticeType: z.enum(['standard', 'facility', 'unit_price'], { + required_error: "입찰공고 타입을 선택해주세요" + }), + // ✅ 계약 정보 (필수) contractType: z.enum(biddings.contractType.enumValues, { required_error: "계약구분을 선택해주세요" @@ -75,44 +81,90 @@ export const createBiddingSchema = z.object({ awardCount: z.enum(biddings.awardCount.enumValues, { required_error: "낙찰수를 선택해주세요" }), + + // ✅ 가격 정보 (조회용으로 readonly 처리) + budget: z.string().optional(), // 예산 (조회용) + finalBidPrice: z.string().optional(), // 실적가 (조회용) + targetPrice: z.string().optional(), // 내정가 (조회용) + + // PR 정보 (조회용) + prNumber: z.string().optional(), + + // 계약기간 contractStartDate: z.string().optional(), contractEndDate: z.string().optional(), - + // ✅ 일정 (제출기간 필수) - submissionStartDate: z.string().min(1, "제출시작일시는 필수입니다"), - submissionEndDate: z.string().min(1, "제출마감일시는 필수입니다"), + submissionStartDate: z.string().optional(), + + submissionEndDate: z.string().optional(), + 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"), - - // 상태 및 담당자 + + // 상태 (조회용) status: z.enum(biddings.status.enumValues).default("bidding_generated"), isPublic: z.boolean().default(false), isUrgent: z.boolean().default(false), + + // 구매조직 + purchasingOrganization: z.string().optional(), + + // 담당자 정보 (개선된 구조) + bidPicId: z.number().int().positive().optional(), + bidPicName: z.string().min(1, "입찰담당자는 필수입니다"), + bidPicCode: z.string().min(1, "입찰담당자 코드는 필수입니다"), + supplyPicId: z.number().int().positive().optional(), + supplyPicName: z.string().optional(), + supplyPicCode: z.string().optional(), + + // 기존 담당자 정보 (점진적 마이그레이션을 위해 유지) managerName: z.string().optional(), managerEmail: z.string().email().optional().or(z.literal("")), managerPhone: z.string().optional(), - + + // 구매요청자 (현재 사용자) + requesterName: z.string().optional(), + // 메타 remarks: z.string().optional(), - // 입찰 조건 (선택사항이지만, 설정할 경우 필수 항목들이 있음) + // 첨부파일 (두 가지 타입으로 구분) + attachments: z.array(z.object({ + id: z.string(), + fileName: z.string(), + fileSize: z.number().optional(), + filePath: z.string(), + uploadedAt: z.string().optional(), + type: z.enum(['shi', 'vendor']).default('shi'), // SHI용 또는 협력업체용 + })).default([]), + vendorAttachments: z.array(z.object({ + id: z.string(), + fileName: z.string(), + fileSize: z.number().optional(), + filePath: z.string(), + uploadedAt: z.string().optional(), + type: z.enum(['shi', 'vendor']).default('vendor'), // SHI용 또는 협력업체용 + })).default([]), + + // 입찰 조건 (통합된 구조) biddingConditions: z.object({ - paymentTerms: z.string().min(1, "지급조건은 필수입니다"), - taxConditions: z.string().min(1, "세금조건은 필수입니다"), - incoterms: z.string().min(1, "운송조건은 필수입니다"), - contractDeliveryDate: z.string().min(1, "계약납품일은 필수입니다"), - shippingPort: z.string().optional(), - destinationPort: z.string().optional(), - isPriceAdjustmentApplicable: z.boolean().default(false), + paymentTerms: z.string().min(1, "SHI 지급조건은 필수입니다"), // SHI 지급조건 + taxConditions: z.string().min(1, "SHI 매입부가가치세는 필수입니다"), // SHI 매입부가가치세 + incoterms: z.string().min(1, "SHI 인도조건은 필수입니다"), // SHI 인도조건 + incotermsOption: z.string().optional(), // SHI 인도조건2 + contractDeliveryDate: z.string().optional(), + shippingPort: z.string().optional(), // SHI 선적지 + destinationPort: z.string().optional(), // SHI 하역지 + isPriceAdjustmentApplicable: z.boolean().default(false), // 하도급법적용여부 sparePartOptions: z.string().optional(), - }).optional(), + }), }).refine((data) => { // 제출 기간 검증: 시작일이 마감일보다 이전이어야 함 if (data.submissionStartDate && data.submissionEndDate) { @@ -138,40 +190,78 @@ export const createBiddingSchema = z.object({ 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(), - + + // 입찰공고 정보 + noticeType: z.enum(['standard', 'facility', 'unit_price']).optional(), + contractType: z.enum(biddings.contractType.enumValues).optional(), biddingType: z.enum(biddings.biddingType.enumValues).optional(), + biddingTypeCustom: z.string().optional(), awardCount: z.enum(biddings.awardCount.enumValues).optional(), + + // 가격 정보 (조회용) + budget: z.string().optional(), + finalBidPrice: z.string().optional(), + targetPrice: z.string().optional(), + + // PR 정보 (조회용) + prNumber: z.string().optional(), + + // 계약기간 contractStartDate: z.string().optional(), contractEndDate: 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(), isUrgent: z.boolean().optional(), + + // 구매조직 + purchasingOrganization: z.string().optional(), + + // 담당자 정보 (개선된 구조) + bidPicId: z.number().int().positive().optional(), + bidPicName: z.string().min(1, "입찰담당자는 필수입니다").optional(), + bidPicCode: z.string().min(1, "입찰담당자 코드는 필수입니다").optional(), + supplyPicId: z.number().int().positive().optional(), + supplyPicName: z.string().optional(), + supplyPicCode: z.string().optional(), + + // 기존 담당자 정보 (점진적 마이그레이션을 위해 유지) managerName: z.string().optional(), managerEmail: z.string().email().optional().or(z.literal("")), managerPhone: z.string().optional(), - + + // 구매요청자 (현재 사용자) + requesterName: z.string().optional(), + + // 입찰 조건 + biddingConditions: z.object({ + paymentTerms: z.string().min(1, "SHI 지급조건은 필수입니다").optional(), + taxConditions: z.string().min(1, "SHI 매입부가가치세는 필수입니다").optional(), + incoterms: z.string().min(1, "SHI 인도조건은 필수입니다").optional(), + incotermsOption: z.string().optional(), + contractDeliveryDate: z.string().optional(), + shippingPort: z.string().optional(), + destinationPort: z.string().optional(), + isPriceAdjustmentApplicable: z.boolean().default(false), + sparePartOptions: z.string().optional(), + }), + remarks: z.string().optional(), }) @@ -202,7 +292,7 @@ export const createBiddingSchema = z.object({ taxConditions: z.string().optional(), deliveryDate: z.string().optional(), awardRatio: z.number().min(0).max(100, '발주비율은 0-100 사이여야 합니다').optional(), - status: z.enum(['pending', 'submitted', 'selected', 'rejected']).default('pending'), + invitationStatus: z.enum(['pending', 'pre_quote_sent', 'pre_quote_accepted', 'pre_quote_declined', 'pre_quote_submitted', 'bidding_sent', 'bidding_accepted', 'bidding_declined', 'bidding_cancelled', 'bidding_submitted']).default('pending'), }) // 협력업체 정보 업데이트 스키마 @@ -219,7 +309,7 @@ export const createBiddingSchema = z.object({ taxConditions: z.string().optional(), deliveryDate: z.string().optional(), awardRatio: z.number().min(0).max(100, '발주비율은 0-100 사이여야 합니다').optional(), - status: z.enum(['pending', 'submitted', 'selected', 'rejected']).optional(), + invitationStatus: z.enum(['pending', 'pre_quote_sent', 'pre_quote_accepted', 'pre_quote_declined', 'pre_quote_submitted', 'bidding_sent', 'bidding_accepted', 'bidding_declined', 'bidding_cancelled', 'bidding_submitted']).optional(), }) // 낙찰 선택 스키마 |
