import { NextRequest, NextResponse } from 'next/server' import { getDb } from '@/lib/db' import { initDatabase } from '@/lib/db-schema' import { getCurrentUser } from '@/lib/auth' import { hasPermission } from '@/lib/permissions' import { parseExcelTickets } from '@/lib/excel' function validateTicketNo(ticketNo: string): string | null { if (!/^\d{14}$/.test(ticketNo)) { return '工单号必须为 14 位纯数字' } const y = parseInt(ticketNo.slice(0, 4)) const m = parseInt(ticketNo.slice(4, 6)) const d = parseInt(ticketNo.slice(6, 8)) const dt = new Date(y, m - 1, d) if (dt.getFullYear() !== y || dt.getMonth() !== m - 1 || dt.getDate() !== d) { return '工单号前 8 位必须为合法日期(YYYYMMDD)' } return null } export async function POST(request: NextRequest) { try { initDatabase() const user = await getCurrentUser() if (!user) return NextResponse.json({ error: '未登录' }, { status: 401 }) if (!hasPermission(user, 'tickets:write')) return NextResponse.json({ error: '权限不足' }, { status: 403 }) const formData = await request.formData() const file = formData.get('file') as File | null const overwrite = formData.get('overwrite') === 'true' if (!file) return NextResponse.json({ error: '请上传文件' }, { status: 400 }) const buffer = Buffer.from(await file.arrayBuffer()) const parsed = parseExcelTickets(buffer) if (parsed.length === 0) return NextResponse.json({ error: '文件中没有有效工单数据' }, { status: 400 }) const db = getDb() // 校验工单号 const validationErrors: string[] = [] const validTickets: typeof parsed = [] for (let i = 0; i < parsed.length; i++) { const t = parsed[i] if (!t.ticket_no) { validationErrors.push(`第 ${i + 2} 行: 缺少工单号`) continue } const err = validateTicketNo(t.ticket_no) if (err) { validationErrors.push(`第 ${i + 2} 行: ${err}(${t.ticket_no})`) continue } validTickets.push(t) } if (validTickets.length === 0) { return NextResponse.json({ success: false, error: '所有工单号校验失败', validationErrors, }, { status: 400 }) } // 检查重复 const existingIds = new Set( (db.prepare('SELECT id FROM tickets').all() as { id: number }[]).map(r => r.id) ) const conflicts = validTickets.filter(t => existingIds.has(parseInt(t.ticket_no!))) if (conflicts.length > 0 && !overwrite) { return NextResponse.json({ success: false, conflicts: conflicts.map(t => t.ticket_no), message: `${conflicts.length} 个工单号已存在,是否覆盖?`, requireOverwrite: true, validationErrors: validationErrors.length > 0 ? validationErrors : undefined, }, { status: 409 }) } // 执行导入 const insertStmt = db.prepare(` INSERT OR REPLACE INTO tickets (id, device_ip, device_sn, device_name, content, assign_time, fault_category, fault_subcategory, responsibility, current_status, counted_in_sla, created_by, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `) const imported: string[] = [] const errors: string[] = [] const transaction = db.transaction(() => { for (const t of validTickets) { try { const ticketId = parseInt(t.ticket_no!) insertStmt.run( ticketId, t.device_ip || null, t.device_sn || null, t.device_name || null, t.content || null, t.assign_time || new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().slice(0, 19), t.fault_category || null, t.fault_subcategory || null, t.responsibility || null, t.current_status || 'open', t.counted_in_sla ?? 1, user.id, user.id, ) imported.push(t.ticket_no!) } catch (e) { errors.push(`第 ${validTickets.indexOf(t) + 2} 行: ${e instanceof Error ? e.message : '导入失败'}`) } } }) transaction() return NextResponse.json({ success: true, imported: imported.length, overwritten: conflicts.length, errors: errors.length > 0 ? errors : undefined, validationErrors: validationErrors.length > 0 ? validationErrors : undefined, }) } catch (e) { const msg = e instanceof Error ? e.message : '导入失败' return NextResponse.json({ error: msg }, { status: 500 }) } }