summaryrefslogtreecommitdiff
path: root/lib/gtc-contract/gtc-clauses/validations.ts
blob: edbcf612c1b56f3edbaef44737272cb38764c1b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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>