diff options
| author | joonhoekim <26rote@gmail.com> | 2025-11-12 17:49:57 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-11-12 17:49:57 +0900 |
| commit | f4b1a770184e8647e0b3042a52a1bcc6f9cf00ce (patch) | |
| tree | d76700d8f52fa893afa816cf60bd3620f3032fe7 /lib/docu-list-rule | |
| parent | d3ff18a2320eeb400dc5d18588490c775bff4820 (diff) | |
(김준회) SWP: 파일 업로드시 Stage 검증 추가, DOC_CLASS 관리 단순화 (코드 제거), DOC_CLASS 추가시 검증(A-Z0-9) 처리
Diffstat (limited to 'lib/docu-list-rule')
3 files changed, 103 insertions, 93 deletions
diff --git a/lib/docu-list-rule/document-class/service.ts b/lib/docu-list-rule/document-class/service.ts index d92f3a95..9d3ff23a 100644 --- a/lib/docu-list-rule/document-class/service.ts +++ b/lib/docu-list-rule/document-class/service.ts @@ -149,51 +149,16 @@ export async function createDocumentClassCodeGroup(input: { description?: string }) { try { - // Value 자동 변환: "A", "AB", "A Class", "A CLASS" 등을 "A Class", "AB Class" 형태로 변환 - const formatValue = (input: string): string => { - // 공백 제거 및 대소문자 정규화 - const cleaned = input.trim().toLowerCase() - - // "class"가 포함되어 있으면 제거 - const withoutClass = cleaned.replace(/\s*class\s*/g, '') - - // 알파벳과 숫자만 추출 - const letters = withoutClass.replace(/[^a-z0-9]/g, '') - - if (letters.length === 0) { - return input.trim() // 변환할 수 없으면 원본 반환 - } - - // 첫 글자를 대문자로 변환하고 "Class" 추가 - return letters.charAt(0).toUpperCase() + letters.slice(1) + " Class" - } - - const formattedValue = formatValue(input.value) - - // 해당 프로젝트의 자동으로 code 생성 (예: "DOC_CLASS_001", "DOC_CLASS_002" 등) - const existingClasses = await db - .select({ code: documentClasses.code }) - .from(documentClasses) - .where(eq(documentClasses.projectId, input.projectId)) // projectId로 변경 - .orderBy(desc(documentClasses.code)) - - let newCode = "DOC_CLASS_001" - if (existingClasses.length > 0) { - const lastClass = existingClasses[0] - if (lastClass.code) { - const lastNumber = parseInt(lastClass.code.replace("DOC_CLASS_", "")) || 0 - newCode = `DOC_CLASS_${String(lastNumber + 1).padStart(3, '0')}` - } - } + // Value는 1자리 대문자 알파벳 그대로 저장 (API DOC_CLASS 전송용) + const formattedValue = input.value.trim().toUpperCase() const [newDocumentClass] = await db .insert(documentClasses) .values({ - projectId: input.projectId, // projectId로 변경 - code: newCode, - value: formattedValue, + projectId: input.projectId, + value: formattedValue, // "A", "B", "C" 등 1자리 description: input.description || "", - codeGroupId: null, // Code Group 연결 제거 + codeGroupId: null, isActive: true, }) .returning({ id: documentClasses.id }) @@ -222,31 +187,13 @@ export async function updateDocumentClassCodeGroup(input: { description?: string }) { try { - // Value 자동 변환: "A", "AB", "A Class", "A CLASS" 등을 "A Class", "AB Class" 형태로 변환 - const formatValue = (value: string): string => { - // 공백 제거 및 대소문자 정규화 - const cleaned = value.trim().toLowerCase() - - // "class"가 포함되어 있으면 제거 - const withoutClass = cleaned.replace(/\s*class\s*/g, '') - - // 알파벳과 숫자만 추출 - const letters = withoutClass.replace(/[^a-z0-9]/g, '') - - if (letters.length === 0) { - return value.trim() // 변환할 수 없으면 원본 반환 - } - - // 첫 글자를 대문자로 변환하고 "Class" 추가 - return letters.charAt(0).toUpperCase() + letters.slice(1) + " Class" - } - - const formattedValue = formatValue(input.value) + // Value는 1자리 대문자 알파벳 그대로 저장 (API DOC_CLASS 전송용) + const formattedValue = input.value.trim().toUpperCase() const [updatedDocumentClass] = await db .update(documentClasses) .set({ - value: formattedValue, + value: formattedValue, // "A", "B", "C" 등 1자리 description: input.description || "", updatedAt: new Date(), }) @@ -630,4 +577,67 @@ export async function getProjectKindScheduleSetting(projectCode: string): Promis console.error('Error fetching schedule settings:', error) return [] } +} + +/** + * 프로젝트의 Document Class와 해당 Stage 옵션 매핑 조회 + * @param projectCode 프로젝트 코드 (예: "SN2190") + * @returns Document Class별 허용 Stage 목록 맵 + */ +export async function getProjectDocumentClassStages(projectCode: string): Promise<Record<string, string[]>> { + try { + // 1. 프로젝트 ID 조회 + const project = await db + .select({ id: projects.id }) + .from(projects) + .where(eq(projects.code, projectCode)) + .limit(1); + + if (!project.length) { + console.warn(`[getProjectDocumentClassStages] 프로젝트를 찾을 수 없습니다: ${projectCode}`); + return {}; + } + + const projectId = project[0].id; + + // 2. 프로젝트의 모든 Document Class와 옵션 조회 + const documentClassesWithOptions = await db + .select({ + docClassValue: documentClasses.value, + optionCode: documentClassOptions.optionCode, + }) + .from(documentClasses) + .leftJoin( + documentClassOptions, + eq(documentClasses.id, documentClassOptions.documentClassId) + ) + .where( + and( + eq(documentClasses.projectId, projectId), + eq(documentClasses.isActive, true), + eq(documentClassOptions.isActive, true) + ) + ) + .orderBy(documentClasses.value, documentClassOptions.sdq); + + // 3. Document Class별로 Stage 목록 그룹핑 + const stageMap: Record<string, string[]> = {}; + + for (const row of documentClassesWithOptions) { + if (!row.docClassValue || !row.optionCode) continue; + + if (!stageMap[row.docClassValue]) { + stageMap[row.docClassValue] = []; + } + + stageMap[row.docClassValue].push(row.optionCode); + } + + console.log(`[getProjectDocumentClassStages] ${projectCode}: ${Object.keys(stageMap).length}개 Document Class, 총 ${Object.values(stageMap).flat().length}개 Stage 옵션`); + + return stageMap; + } catch (error) { + console.error('[getProjectDocumentClassStages] 오류:', error); + return {}; + } }
\ No newline at end of file diff --git a/lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx b/lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx index e2cfc39e..6e8ac686 100644 --- a/lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx +++ b/lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx @@ -32,7 +32,10 @@ import { createDocumentClassCodeGroup } from "@/lib/docu-list-rule/document-clas import { useParams } from "next/navigation" const createDocumentClassSchema = z.object({ - value: z.string().min(1, "Value는 필수입니다."), + value: z.string() + .min(1, "Value는 필수입니다.") + .max(1, "Value는 1자리만 입력 가능합니다. (예: A, B, 0, 1)") + .regex(/^[A-Z0-9]$/, "대문자 알파벳 또는 숫자 1자리만 입력 가능합니다. (예: A, B, 0, 1)"), description: z.string().optional(), }) @@ -117,8 +120,17 @@ export function DocumentClassAddDialog({ <FormItem> <FormLabel>Value *</FormLabel> <FormControl> - <Input {...field} placeholder="예: A Class" /> + <Input + {...field} + placeholder="예: A" + maxLength={1} + className="uppercase" + onChange={(e) => field.onChange(e.target.value.toUpperCase())} + /> </FormControl> + <div className="text-xs text-muted-foreground mt-1"> + 💡 대문자 알파벳 또는 숫자 1자리 (A, B, 0, 1 등) - API DOC_CLASS로 전송됩니다 + </div> <FormMessage /> </FormItem> )} @@ -131,8 +143,11 @@ export function DocumentClassAddDialog({ <FormItem> <FormLabel>Description</FormLabel> <FormControl> - <Input {...field} placeholder="예: A Class Description (선택사항)" /> + <Input {...field} placeholder="예: General Documents (선택사항)" /> </FormControl> + <div className="text-xs text-muted-foreground mt-1"> + 선택사항: Document Class에 대한 추가 설명 + </div> <FormMessage /> </FormItem> )} diff --git a/lib/docu-list-rule/document-class/table/document-class-table-columns.tsx b/lib/docu-list-rule/document-class/table/document-class-table-columns.tsx index 8c391def..9d8d91e0 100644 --- a/lib/docu-list-rule/document-class/table/document-class-table-columns.tsx +++ b/lib/docu-list-rule/document-class/table/document-class-table-columns.tsx @@ -107,43 +107,28 @@ export function getColumns({ setRowAction, onDetail }: GetColumnsProps): ColumnD // ---------------------------------------------------------------- const dataColumns: ColumnDef<typeof documentClasses.$inferSelect>[] = [ { - accessorKey: "code", - enableResizing: true, - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="코드" /> - ), - meta: { - excelHeader: "코드", - type: "text", - }, - cell: ({ row }) => row.getValue("code") ?? "", - minSize: 80 - }, - { accessorKey: "value", enableResizing: true, header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="값" /> + <DataTableColumnHeaderSimple column={column} title="클래스" /> ), meta: { - excelHeader: "값", + excelHeader: "클래스", type: "text", }, - cell: ({ row }) => row.getValue("value") ?? "", - minSize: 80 - }, - { - accessorKey: "description", - enableResizing: true, - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="설명" /> - ), - meta: { - excelHeader: "설명", - type: "text", + cell: ({ row }) => { + const value = row.getValue("value") as string + const description = row.getValue("description") as string + return ( + <div className="flex items-center gap-2"> + <span className="font-mono font-bold text-lg">{value}</span> + {description && ( + <span className="text-muted-foreground text-sm">- {description}</span> + )} + </div> + ) }, - cell: ({ row }) => row.getValue("description") ?? "", - minSize: 80 + minSize: 250 }, { |
