summaryrefslogtreecommitdiff
path: root/lib/bidding/validation.ts
diff options
context:
space:
mode:
authorTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2025-11-10 11:25:19 +0900
committerTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2025-11-10 11:25:19 +0900
commita5501ad1d1cb836d2b2f84e9b0f06049e22c901e (patch)
tree667ed8c5d6ec35b109190e9f976d66ae54def4ce /lib/bidding/validation.ts
parentb0fe980376fcf1a19ff4b90851ca8b01f378fdc0 (diff)
parentf8a38907911d940cb2e8e6c9aa49488d05b2b578 (diff)
Merge remote-tracking branch 'origin/dujinkim' into master_homemaster
Diffstat (limited to 'lib/bidding/validation.ts')
-rw-r--r--lib/bidding/validation.ts170
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(),
})
// 낙찰 선택 스키마