From c228a89c2834ee63b209bad608837c39643f350e Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 28 Jul 2025 11:44:16 +0000 Subject: (대표님) 의존성 docx 추가, basicContract API, gtc(계약일반조건), 벤더평가 esg 평가데이터 내보내기 개선, S-EDP 피드백 대응(CLS_ID, ITEM NO 등) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/gtc-contract/gtc-clauses/validations.ts | 124 ++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 lib/gtc-contract/gtc-clauses/validations.ts (limited to 'lib/gtc-contract/gtc-clauses/validations.ts') diff --git a/lib/gtc-contract/gtc-clauses/validations.ts b/lib/gtc-contract/gtc-clauses/validations.ts new file mode 100644 index 00000000..edbcf612 --- /dev/null +++ b/lib/gtc-contract/gtc-clauses/validations.ts @@ -0,0 +1,124 @@ +import { type GtcClause } from "@/db/schema/gtc" +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(20), + sort: getSortingStateParser().withDefault([ + { id: "sortOrder", desc: false }, + ]), + // 검색 필터들 + category: parseAsString.withDefault(""), + depth: parseAsInteger.withDefault(0), + parentId: parseAsInteger.withDefault(0), + // advanced filter + filters: getFiltersStateParser().withDefault([]), + joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), + search: parseAsString.withDefault(""), +}) + +const clauseImageSchema = z.object({ + id: z.string(), + url: z.string().url(), + fileName: z.string(), + size: z.number().positive(), +}) + +export const createGtcClauseSchema = z.object({ + documentId: z.number(), + parentId: z.number().nullable().optional(), + itemNumber: z.string().min(1, "채번을 입력해주세요"), + category: z.string().optional(), + subtitle: z.string().min(1, "소제목을 입력해주세요"), + images: z.array(clauseImageSchema).optional(), // ✅ 이미지 배열 추가 + + content: z.string().optional(), // 그룹핑용 조항은 내용이 없을 수 있음 + sortOrder: z.number().default(0), + editReason: z.string().optional(), +}).superRefine(async (data, ctx) => { + // 채번 형식 검증 (숫자, 문자 모두 허용하되 특수문자 제한) + const itemNumberRegex = /^[a-zA-Z0-9._-]+$/ + if (!itemNumberRegex.test(data.itemNumber)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "채번은 영문, 숫자, 점(.), 하이픈(-), 언더스코어(_)만 사용 가능합니다", + path: ["itemNumber"], + }) + } +}) + +export const updateGtcClauseSchema = z.object({ + itemNumber: z.string().min(1, "채번을 입력해주세요").optional(), + category: z.string().optional(), + subtitle: z.string().min(1, "소제목을 입력해주세요").optional(), + content: z.string().optional(), // 내용도 nullable + sortOrder: z.number().optional(), + images: z.array(clauseImageSchema).optional(), // ✅ 이미지 배열 추가 + isActive: z.boolean().optional(), + editReason: z.string().optional(), +}).superRefine(async (data, ctx) => { + // 채번 형식 검증 + if (data.itemNumber) { + const itemNumberRegex = /^[a-zA-Z0-9._-]+$/ + if (!itemNumberRegex.test(data.itemNumber)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "채번은 영문, 숫자, 점(.), 하이픈(-), 언더스코어(_)만 사용 가능합니다", + path: ["itemNumber"], + }) + } + } +}) + +export const reorderGtcClausesSchema = z.object({ + clauses: z.array(z.object({ + id: z.number(), + sortOrder: z.number(), + parentId: z.number().nullable(), + depth: z.number(), + fullPath: z.string().optional(), + })), + editReason: z.string().optional(), +}) + +export const bulkUpdateGtcClausesSchema = z.object({ + clauseIds: z.array(z.number()).min(1, "수정할 조항을 선택해주세요"), + updates: z.object({ + category: z.string().optional(), + isActive: z.boolean().optional(), + }), + editReason: z.string().min(1, "편집 사유를 입력해주세요"), +}) + +export const generateVariableNamesSchema = z.object({ + documentId: z.number(), + prefix: z.string().default("CLAUSE"), + includeVendorCode: z.boolean().default(false), + vendorCode: z.string().optional(), +}).superRefine(async (data, ctx) => { + if (data.includeVendorCode && !data.vendorCode) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "벤더 코드 포함 시 벤더 코드를 입력해주세요", + path: ["vendorCode"], + }) + } +}) + +export type GetGtcClausesSchema = Awaited> +export type CreateGtcClauseSchema = z.infer +export type UpdateGtcClauseSchema = z.infer +export type ReorderGtcClausesSchema = z.infer +export type BulkUpdateGtcClausesSchema = z.infer +export type GenerateVariableNamesSchema = z.infer \ No newline at end of file -- cgit v1.2.3