diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-15 10:36:26 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-15 10:36:26 +0000 |
| commit | 534266f90a5ca846767dc2a990c77f1112a33d9c (patch) | |
| tree | 62803374475726c2d2bdb0aece29b20599ed718a /app | |
| parent | 0ffba85c79a7693d887e6fe0cd991e42faea8f36 (diff) | |
(임수민) serp 구현
Diffstat (limited to 'app')
| -rw-r--r-- | app/[lng]/serp/layout.tsx | 5 | ||||
| -rw-r--r-- | app/[lng]/serp/page.tsx | 69 | ||||
| -rw-r--r-- | app/api/s-erp-import/rpa/route.ts | 135 |
3 files changed, 209 insertions, 0 deletions
diff --git a/app/[lng]/serp/layout.tsx b/app/[lng]/serp/layout.tsx new file mode 100644 index 00000000..9f048854 --- /dev/null +++ b/app/[lng]/serp/layout.tsx @@ -0,0 +1,5 @@ +export default function SapImportLayout({ children }: { children: React.ReactNode }) {
+ return <>{children}</>
+}
+
+
diff --git a/app/[lng]/serp/page.tsx b/app/[lng]/serp/page.tsx new file mode 100644 index 00000000..0c9e0d3f --- /dev/null +++ b/app/[lng]/serp/page.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'
+import { ImportButton } from '@/components/s-erp-import/import-card'
+import { getSapTableCounts } from '@/lib/s-erp-import/actions'
+
+export const dynamic = 'force-dynamic'
+
+const TABLES = [
+ 'TB_SAP_EquipInfo',
+ 'TB_SAP_Order',
+ 'TB_SAP_OrderConfirm',
+ 'TB_SAP_OrderNotice',
+ 'TB_SAP_OrderBreakdown',
+ 'TB_SAP_MainternanceBOM',
+ 'TB_SAP_MaterialRepair',
+ 'TB_SAP_MaterialInfo',
+ 'TB_SAP_MaterialStock',
+ 'TB_SAP_MaterialRelease',
+ 'TB_SAP_MaterialReceiving',
+ 'TB_SAP_MaterialPurchase',
+]
+
+export default async function Page() {
+ const counts = await getSapTableCounts(TABLES)
+
+ return (
+ <div className="container py-8" data-testid="s-erp-import-page">
+ <h1 className="text-2xl font-bold mb-2" data-testid="page-title">S-ERP 데이터 임포트</h1>
+ <p className='text-sm mb-6' data-testid="page-description">버튼을 클릭하여 엑셀 파일을 업로드 해주세요.</p>
+
+ {/* RPA 가이드 정보 */}
+ <div
+ className="mb-6 p-4 bg-gray-50 border rounded"
+ data-testid="rpa-guide"
+ style={{ display: 'none' }} // RPA가 필요할 때만 표시
+ >
+ <h3 className="font-semibold mb-2" data-testid="rpa-guide-title">RPA 사용 가이드</h3>
+ <div className="text-sm space-y-1" data-testid="rpa-guide-content">
+ <div data-testid="rpa-selector-info">
+ <strong>셀렉터:</strong> [data-testid="upload-button-TABLE_NAME"]
+ </div>
+ <div data-testid="rpa-file-input-info">
+ <strong>파일 입력:</strong> [data-testid="file-input-TABLE_NAME"]
+ </div>
+ <div data-testid="rpa-status-info">
+ <strong>상태 확인:</strong> [data-testid="status-message-TABLE_NAME"]
+ </div>
+ <div data-testid="rpa-table-list-info">
+ <strong>테이블 목록:</strong> [data-testid="table-list"]
+ </div>
+ </div>
+ </div>
+
+ <div className="flex flex-col gap-6" data-testid="table-list">
+ {TABLES.map((name, index) => (
+ <div
+ key={name}
+ data-testid={`table-row-${name}`}
+ data-table-name={name}
+ data-table-index={index}
+ >
+ <ImportButton tableName={name} count={counts[name] ?? 0} />
+ </div>
+ ))}
+ </div>
+ </div>
+ )
+}
+
+
diff --git a/app/api/s-erp-import/rpa/route.ts b/app/api/s-erp-import/rpa/route.ts new file mode 100644 index 00000000..749de90f --- /dev/null +++ b/app/api/s-erp-import/rpa/route.ts @@ -0,0 +1,135 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getSapTableCounts, importExcel, exportTemplate } from '@/lib/s-erp-import/actions' + +// RPA용 API 엔드포인트 +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const action = searchParams.get('action') + const tableName = searchParams.get('tableName') + + switch (action) { + case 'get-counts': + // 모든 테이블의 데이터 건수 조회 + const TABLES = [ + 'TB_SAP_EquipInfo', + 'TB_SAP_Order', + 'TB_SAP_OrderConfirm', + 'TB_SAP_OrderNotice', + 'TB_SAP_OrderBreakdown', + 'TB_SAP_MainternanceBOM', + 'TB_SAP_MaterialRepair', + 'TB_SAP_MaterialInfo', + 'TB_SAP_MaterialStock', + 'TB_SAP_MaterialRelease', + 'TB_SAP_MaterialReceiving', + 'TB_SAP_MaterialPurchase', + ] + const counts = await getSapTableCounts(TABLES) + return NextResponse.json({ + success: true, + data: counts, + timestamp: new Date().toISOString() + }) + + case 'get-template': + // 템플릿 다운로드 + if (!tableName) { + return NextResponse.json({ + success: false, + message: 'tableName is required' + }, { status: 400 }) + } + + const templateBuffer = await exportTemplate(tableName) + return new NextResponse(templateBuffer, { + headers: { + 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'Content-Disposition': `attachment; filename="${tableName}_template.xlsx"` + } + }) + + case 'get-table-info': + // 테이블 정보 조회 + const tableInfo = { + 'TB_SAP_EquipInfo': { title: '장비 정보', category: '장비 관리', description: 'SAP 장비 마스터 데이터' }, + 'TB_SAP_Order': { title: '작업 주문', category: '작업 관리', description: '유지보수 작업 주문 정보' }, + 'TB_SAP_OrderConfirm': { title: '주문 확인', category: '작업 관리', description: '작업 주문 확인 및 완료 정보' }, + 'TB_SAP_OrderNotice': { title: '주문 통지', category: '작업 관리', description: '작업 주문 통지 및 알림 정보' }, + 'TB_SAP_OrderBreakdown': { title: '주문 고장', category: '고장 관리', description: '장비 고장 및 수리 정보' }, + 'TB_SAP_MainternanceBOM': { title: '유지보수 BOM', category: '부품 관리', description: '유지보수용 부품 목록' }, + 'TB_SAP_MaterialRepair': { title: '자재 수리', category: '자재 관리', description: '자재 수리 및 교체 정보' }, + 'TB_SAP_MaterialInfo': { title: '자재 정보', category: '자재 관리', description: '자재 마스터 데이터' }, + 'TB_SAP_MaterialStock': { title: '자재 재고', category: '자재 관리', description: '자재 재고 현황' }, + 'TB_SAP_MaterialRelease': { title: '자재 출고', category: '자재 관리', description: '자재 출고 내역' }, + 'TB_SAP_MaterialReceiving': { title: '자재 입고', category: '자재 관리', description: '자재 입고 내역' }, + 'TB_SAP_MaterialPurchase': { title: '자재 구매', category: '자재 관리', description: '자재 구매 요청 정보' } + } + + if (tableName) { + return NextResponse.json({ + success: true, + data: tableInfo[tableName as keyof typeof tableInfo] || null + }) + } else { + return NextResponse.json({ + success: true, + data: tableInfo + }) + } + + default: + return NextResponse.json({ + success: false, + message: 'Invalid action. Supported actions: get-counts, get-template, get-table-info' + }, { status: 400 }) + } + } catch (error: any) { + console.error('RPA API Error:', error) + return NextResponse.json({ + success: false, + message: error.message || 'Internal server error', + timestamp: new Date().toISOString() + }, { status: 500 }) + } +} + +// RPA용 파일 업로드 엔드포인트 +export async function POST(request: NextRequest) { + try { + const formData = await request.formData() + const tableName = formData.get('tableName') as string + const file = formData.get('file') as File + + if (!tableName || !file) { + return NextResponse.json({ + success: false, + message: 'tableName and file are required' + }, { status: 400 }) + } + + // 파일 업로드 처리 + const result = await importExcel(tableName, file) + + return NextResponse.json({ + success: result.success, + data: { + tableName, + fileName: file.name, + fileSize: file.size, + inserted: result.inserted || 0, + message: result.message, + unknownHeaders: (result as any).unknownHeaders, + errors: (result as any).errors + }, + timestamp: new Date().toISOString() + }) + } catch (error: any) { + console.error('RPA Upload Error:', error) + return NextResponse.json({ + success: false, + message: error.message || 'Upload failed', + timestamp: new Date().toISOString() + }, { status: 500 }) + } +} |
