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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
|
// @/lib/esg-check-list/excel-actions.ts
"use server"
import db from "@/db/db"
import {
esgEvaluations,
esgEvaluationItems,
esgAnswerOptions
} from "@/db/schema"
import { eq, inArray } from "drizzle-orm"
import type { ParsedExcelData } from "./excel-utils"
export interface ImportOptions {
skipDuplicates?: boolean // 중복 시 스킵
updateExisting?: boolean // 기존 데이터 업데이트
}
export interface ImportResult {
success: boolean
message: string
details: {
evaluationsCreated: number
evaluationsUpdated: number
evaluationsSkipped: number
itemsCreated: number
optionsCreated: number
errors: string[]
}
}
export async function importEsgDataFromExcel(
data: ParsedExcelData,
options: ImportOptions = {}
): Promise<ImportResult> {
const result: ImportResult = {
success: false,
message: '',
details: {
evaluationsCreated: 0,
evaluationsUpdated: 0,
evaluationsSkipped: 0,
itemsCreated: 0,
optionsCreated: 0,
errors: []
}
}
try {
await db.transaction(async (tx) => {
// 1. 기존 평가표 확인
const existingSerials = data.evaluations.map(e => e.serialNumber)
const existingEvaluations = await tx
.select()
.from(esgEvaluations)
.where(inArray(esgEvaluations.serialNumber, existingSerials))
const existingSerialNumbers = existingEvaluations.map(e => e.serialNumber)
// 2. 평가표 처리
for (const evaluation of data.evaluations) {
const exists = existingSerialNumbers.includes(evaluation.serialNumber)
if (exists) {
if (options.skipDuplicates) {
result.details.evaluationsSkipped++
continue
} else if (options.updateExisting) {
// 기존 데이터 업데이트
await tx
.update(esgEvaluations)
.set({
category: evaluation.category,
inspectionItem: evaluation.inspectionItem,
updatedAt: new Date(),
})
.where(eq(esgEvaluations.serialNumber, evaluation.serialNumber))
// 기존 평가항목과 답변옵션 삭제
const existingEvaluation = existingEvaluations.find(e => e.serialNumber === evaluation.serialNumber)
if (existingEvaluation) {
await tx
.delete(esgEvaluationItems)
.where(eq(esgEvaluationItems.esgEvaluationId, existingEvaluation.id))
// 답변옵션은 CASCADE DELETE로 자동 삭제됨
}
result.details.evaluationsUpdated++
} else {
result.details.errors.push(`시리얼번호 '${evaluation.serialNumber}'가 이미 존재합니다.`)
continue
}
} else {
// 새 평가표 생성
await tx
.insert(esgEvaluations)
.values({
serialNumber: evaluation.serialNumber,
category: evaluation.category,
inspectionItem: evaluation.inspectionItem,
})
result.details.evaluationsCreated++
}
}
// 3. 최신 평가표 목록 다시 조회 (새로 생성되거나 업데이트된 것들 포함)
const currentEvaluations = await tx
.select()
.from(esgEvaluations)
.where(inArray(esgEvaluations.serialNumber, existingSerials))
const evaluationMap = new Map(
currentEvaluations.map(e => [e.serialNumber, e.id])
)
// 4. 평가항목 처리
const itemsToInsert = []
for (const item of data.evaluationItems) {
const evaluationId = evaluationMap.get(item.serialNumber)
if (!evaluationId) {
result.details.errors.push(
`평가항목의 시리얼번호 '${item.serialNumber}'에 해당하는 평가표를 찾을 수 없습니다.`
)
continue
}
itemsToInsert.push({
esgEvaluationId: evaluationId,
evaluationItem: item.evaluationItem,
evaluationItemDescription: item.evaluationItemDescription,
orderIndex: item.orderIndex,
})
}
if (itemsToInsert.length > 0) {
const insertedItems = await tx
.insert(esgEvaluationItems)
.values(itemsToInsert)
.returning()
result.details.itemsCreated = insertedItems.length
// 5. 답변옵션 처리
const itemMap = new Map()
for (const insertedItem of insertedItems) {
const originalItem = itemsToInsert.find(
item => item.esgEvaluationId === insertedItem.esgEvaluationId &&
item.evaluationItem === insertedItem.evaluationItem
)
if (originalItem) {
const evaluation = currentEvaluations.find(e => e.id === originalItem.esgEvaluationId)
if (evaluation) {
const key = `${evaluation.serialNumber}:${originalItem.evaluationItem}`
itemMap.set(key, insertedItem.id)
}
}
}
const optionsToInsert = []
for (const option of data.answerOptions) {
const key = `${option.serialNumber}:${option.evaluationItem}`
const itemId = itemMap.get(key)
if (!itemId) {
result.details.errors.push(
`답변옵션의 평가항목 '${option.evaluationItem}'을 찾을 수 없습니다.`
)
continue
}
optionsToInsert.push({
esgEvaluationItemId: itemId,
answerText: option.answerText,
score: option.score.toString(),
orderIndex: option.orderIndex,
})
}
if (optionsToInsert.length > 0) {
const insertedOptions = await tx
.insert(esgAnswerOptions)
.values(optionsToInsert)
.returning()
result.details.optionsCreated = insertedOptions.length
}
}
})
// 결과 메시지 생성
const { details } = result
const totalProcessed = details.evaluationsCreated + details.evaluationsUpdated + details.evaluationsSkipped
if (details.errors.length === 0) {
result.success = true
result.message = `성공적으로 처리되었습니다. 평가표 ${totalProcessed}개 (생성: ${details.evaluationsCreated}, 업데이트: ${details.evaluationsUpdated}, 스킵: ${details.evaluationsSkipped}), 평가항목 ${details.itemsCreated}개, 답변옵션 ${details.optionsCreated}개`
} else if (details.evaluationsCreated > 0 || details.evaluationsUpdated > 0) {
result.success = true
result.message = `부분적으로 성공했습니다. ${details.errors.length}개의 오류가 있었습니다.`
} else {
result.success = false
result.message = `임포트에 실패했습니다. ${details.errors.length}개의 오류가 발생했습니다.`
}
return result
} catch (error) {
console.error('Excel import error:', error)
result.success = false
result.message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.'
result.details.errors.push(result.message)
return result
}
}
// 중복 확인 함수
export async function checkDuplicateSerials(serialNumbers: string[]) {
try {
const existing = await db
.select({ serialNumber: esgEvaluations.serialNumber })
.from(esgEvaluations)
.where(inArray(esgEvaluations.serialNumber, serialNumbers))
return existing.map(e => e.serialNumber)
} catch (error) {
console.error('Error checking duplicates:', error)
return []
}
}
|