summaryrefslogtreecommitdiff
path: root/lib/gtc-contract/gtc-clauses/validations.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gtc-contract/gtc-clauses/validations.ts')
-rw-r--r--lib/gtc-contract/gtc-clauses/validations.ts124
1 files changed, 124 insertions, 0 deletions
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<GtcClause>().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<ReturnType<typeof searchParamsCache.parse>>
+export type CreateGtcClauseSchema = z.infer<typeof createGtcClauseSchema>
+export type UpdateGtcClauseSchema = z.infer<typeof updateGtcClauseSchema>
+export type ReorderGtcClausesSchema = z.infer<typeof reorderGtcClausesSchema>
+export type BulkUpdateGtcClausesSchema = z.infer<typeof bulkUpdateGtcClausesSchema>
+export type GenerateVariableNamesSchema = z.infer<typeof generateVariableNamesSchema> \ No newline at end of file