From 54a8f97f782c0d2e5c904444873f719166496d25 Mon Sep 17 00:00:00 2001 From: hjjeong Date: Wed, 13 May 2026 16:32:41 +0900 Subject: [PATCH] =?UTF-8?q?fix(batch):=20=EB=AF=B8=EB=A6=AC=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=E2=86=92=20=EB=A7=A4=ED=95=91=20=EC=B9=B4=EB=93=9C?= =?UTF-8?q?=20=ED=91=9C=EC=8B=9C=20=ED=9D=90=EB=A6=84=20=EC=A0=95=EC=83=81?= =?UTF-8?q?=ED=99=94=20+=20=EB=A7=A4=ED=95=91=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8C=A9=ED=8A=B8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 배치 생성 흐름 검증 중 발견된 4가지 이슈 일괄 정정. 1) BatchManagementService.previewRestApiData — camelCase 키 명시 remap 직전 커밋(b752de23)에서 convertCamelToSnake() 호출 추가했지만 그 함수의 실제 구현이 batch_configs 전용 snake→snake remap 이라 사실상 no-op. 프론트의 apiUrl 등 camelCase 가 변환되지 않아 isBlank(api_url)=true → 400. → previewRestApiData 진입부에 직접 remap (apiUrl/apiKey/requestBody/dataArrayPath/ paramType/paramName/paramValue/paramSource/authServiceName 9개 키). 2) batchManagement.ts.previewRestApiData — 응답 totalCount 정규화 백엔드는 total_count (snake_case) 로 응답하는데 프론트는 result.totalCount 로 읽음. 토스트가 "2개 필드, undefined개 레코드" 로 표시됨. → 응답 normalize: total_count ?? totalCount ?? 0. 3) batch-management-new/page.tsx — root h-full overflow-y-auto 페이지 root 가 overflow 처리가 없어 FROM/TO 카드 아래의 매핑 카드가 탭 컨테이너 밖으로 잘려 사용자가 못 봄. → root div 에 h-full overflow-y-auto 추가. 4) RestApiToDbMappingCard — v5 컨벤션에 맞춘 컴팩트화 다른 메뉴들과 톤 통일. CardHeader 패딩 축소, 폰트 size 일괄 다운, 행 padding p-3 → p-2, Select/Input h-9 → h-7 text-xs, 순서 원형 h-6 → h-5, 카드 내부 height 360 → 300px, 매핑 추가 버튼/삭제 버튼 컴팩트. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../erp/service/BatchManagementService.java | 15 +++- .../admin/batch-management-new/page.tsx | 68 +++++++++---------- frontend/lib/api/batchManagement.ts | 12 +++- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/backend-spring/src/main/java/com/erp/service/BatchManagementService.java b/backend-spring/src/main/java/com/erp/service/BatchManagementService.java index fbb88f0e..2ee2786c 100644 --- a/backend-spring/src/main/java/com/erp/service/BatchManagementService.java +++ b/backend-spring/src/main/java/com/erp/service/BatchManagementService.java @@ -200,9 +200,18 @@ public class BatchManagementService extends BaseService { // ── REST API Preview / Save ─────────────────────────────────────────────── public Map previewRestApiData(Map body) { - // 프론트는 camelCase 로 보내고 백엔드는 snake_case 로 읽음 — 변환 필요 - // (updateBatchConfig / executeBatchConfig 와 동일 패턴. 누락되어 있던 것을 보강) - convertCamelToSnake(body); + // 프론트(batchManagement.ts)는 camelCase 로 키를 보내고 백엔드는 snake_case 로 읽음. + // 기존 convertCamelToSnake() 는 batch_configs 전용 remap 이라 여기엔 효과 없음. + // → previewRestApiData 전용으로 사용하는 키만 직접 remap. + remap(body, "apiUrl", "api_url"); + remap(body, "apiKey", "api_key"); + remap(body, "requestBody", "request_body"); + remap(body, "dataArrayPath", "data_array_path"); + remap(body, "paramType", "param_type"); + remap(body, "paramName", "param_name"); + remap(body, "paramValue", "param_value"); + remap(body, "paramSource", "param_source"); + remap(body, "authServiceName", "auth_service_name"); String apiUrl = str(body.get("api_url")); String endpoint = str(body.get("endpoint")); diff --git a/frontend/app/(main)/admin/batch-management-new/page.tsx b/frontend/app/(main)/admin/batch-management-new/page.tsx index 156d083c..7e5ded4b 100644 --- a/frontend/app/(main)/admin/batch-management-new/page.tsx +++ b/frontend/app/(main)/admin/batch-management-new/page.tsx @@ -721,7 +721,7 @@ export default function BatchManagementNewPage() { const goBack = () => openTab({ type: "admin", title: "배치 관리", admin_url: "/admin/automaticMng/batchmngList" }); return ( -
+
{/* 헤더 */}
@@ -1630,30 +1630,30 @@ const RestApiToDbMappingCard = memo(function RestApiToDbMappingCard({ return ( - - 컬럼 매핑 설정 - DB 컬럼에 API 필드 또는 고정값을 매핑합니다. + + 컬럼 매핑 설정 + DB 컬럼에 API 필드 또는 고정값을 매핑합니다. - -
+ +
{/* 왼쪽: 샘플 데이터 */}
-
-

샘플 데이터 (최대 3개)

+
+

샘플 데이터 (최대 3개)

{sampleJsonList.length > 0 ? ( -
-
+
+
{sampleJsonList.map((json, index) => ( -
-
{json}
+
+
{json}
))}
) : ( -
-

+

+

API 데이터 미리보기를 실행하면 샘플 데이터가 표시됩니다.

@@ -1662,39 +1662,39 @@ const RestApiToDbMappingCard = memo(function RestApiToDbMappingCard({ {/* 오른쪽: 매핑 영역 (스크롤) */}
-
-

매핑 설정

-
{mappingList.length === 0 ? ( -
-

매핑이 없습니다.

-
) : ( -
+
{mappingList.map((mapping, index) => ( -
+
{/* 순서 표시 */} -
+
{index + 1}
{/* DB 컬럼 선택 (좌측 - TO) */} -
+
@@ -1738,7 +1738,7 @@ const RestApiToDbMappingCard = memo(function RestApiToDbMappingCard({ }) } > - + @@ -1758,7 +1758,7 @@ const RestApiToDbMappingCard = memo(function RestApiToDbMappingCard({ updateMapping(mapping.id, { apiField: value === "none" ? "" : value }) } > - + @@ -1768,7 +1768,7 @@ const RestApiToDbMappingCard = memo(function RestApiToDbMappingCard({
{field} {firstSample && firstSample[field] !== undefined && ( - + (예: {String(firstSample[field]).substring(0, 15)} {String(firstSample[field]).length > 15 ? "..." : ""}) @@ -1784,7 +1784,7 @@ const RestApiToDbMappingCard = memo(function RestApiToDbMappingCard({ value={mapping.fixedValue} onChange={(e) => updateMapping(mapping.id, { fixedValue: e.target.value })} placeholder="고정값 입력" - className="h-9" + className="h-7 text-xs" /> )} {mapping.sourceType === "conditional" && ( @@ -1807,9 +1807,9 @@ const RestApiToDbMappingCard = memo(function RestApiToDbMappingCard({ variant="ghost" size="icon" onClick={() => removeMapping(mapping.id)} - className="text-muted-foreground hover:text-destructive h-8 w-8 shrink-0" + className="text-muted-foreground hover:text-destructive h-6 w-6 shrink-0" > - +
))} diff --git a/frontend/lib/api/batchManagement.ts b/frontend/lib/api/batchManagement.ts index ad880786..a0f666b0 100644 --- a/frontend/lib/api/batchManagement.ts +++ b/frontend/lib/api/batchManagement.ts @@ -164,7 +164,9 @@ class BatchManagementAPIClass { BatchApiResponse<{ fields: string[]; samples: any[]; - totalCount: number; + // 백엔드는 snake_case (total_count) 로 응답하므로 두 키 모두 옵션으로 받음 + total_count?: number; + totalCount?: number; }> >(`${this.BASE_PATH}/rest-api/preview`, requestData); @@ -172,7 +174,13 @@ class BatchManagementAPIClass { throw new Error(response.data.message || "REST API 미리보기에 실패했습니다."); } - return response.data.data || { fields: [], samples: [], totalCount: 0 }; + const raw = response.data.data; + return { + fields: raw?.fields ?? [], + samples: raw?.samples ?? [], + // 백엔드는 total_count 로 응답 → camelCase totalCount 로 normalize + totalCount: raw?.total_count ?? raw?.totalCount ?? 0, + }; } catch (error) { console.error("REST API 미리보기 오류:", error); throw error;