3993 Commits

Author SHA1 Message Date
DDD1542 2348800e68 refactor(common-code): 마스터-디테일 재설계 — code_info(그룹) + code_detail(재귀 트리)
Build & Deploy to K8s / build-and-deploy (push) Successful in 9m22s
카테고리/캐스케이딩 시스템 (B/C/D) 전부 폐기:
- BE: mapper/Service/Controller 9세트 삭제 (cascading*, categoryTree, tableCategoryValue, categoryValueCascading, codeMerge)
- FE: 페이지 3 + API 8 + hooks 2 + 폐기 컴포넌트 6 삭제, 14곳 의존성 정리
- DB: 12 테이블 DROP, TABLE_TYPE_COLUMNS.CODE_CATEGORY → CODE_INFO rename

신설 commonCode 마스터-디테일:
- code_info: 1레벨 그룹 마스터
- code_detail: 2~∞ depth 재귀 트리 (parent_detail_id self-FK, depth 자동 계산)
- API: /api/common-codes/{info,detail}
- CodeCategoryFormModal/Panel → CodeInfoFormModal/Panel rename
- code_category 컬럼명 전부 code_info 로 치환 (mapper/Java/FE)
- 옛 commonCode API URL (/categories/...) → getCodeOptions 어댑터 + /detail?code_info=... 전환

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:50:50 +09:00
hjjeong d61777ab5f fix(admin): 외부커넥션 mapper varchar 캐스팅 + 외부커넥션/배치관리 UI 정돈
- mapper/externalDbConnection.xml: WHERE ID = #{id} 5곳 + ID != #{exclude_id} 1곳에 ::varchar 캐스팅 추가
  (EXTERNAL_DB_CONNECTIONS.ID 가 V001 마이그레이션으로 VARCHAR 인데 long 바인딩되어 character varying = bigint 비교 불가로 500 발생하던 것을 해결)
- exconList: 페이지 overflow-hidden + Tabs/TabsContent 가 flex 컨테이너, ResponsiveDataView scrollContainer 활성화로 테이블 안에서만 sticky header + 자체 스크롤
- exconList/RestApiConnectionList: text-3xl→text-lg/text-sm→text-xs/h-10→h-8 등 컴팩트 폰트로 통일 (배치관리/플로우관리와 톤 매칭)
- RestApiConnectionList: Table divClassName 으로 wrapper 자체에 스크롤 위임 + sticky TableHeader 적용
- ResponsiveDataView: compact 모드일 때 폰트/셀패딩/카드 폰트도 함께 축소, scrollContainer 모드에서 @3xl:block 이 flex 를 덮어쓰던 우선순위 충돌 해결, sticky header 알파 제거
- batchmngList: Pagination 컴포넌트 적용 (RPS batchmngList 참고, 페이지당 10/20/50/100 선택), 컨테이너를 h-full min-h-0 overflow-hidden + 리스트만 자체 스크롤로 변경

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:38:23 +09:00
johngreen d5f9814865 Merge remote-tracking branch 'origin/main' into johngreen
Build & Deploy to K8s / build-and-deploy (push) Successful in 9m23s
2026-05-15 12:22:45 +09:00
johngreen 824a3100ce security(멀티테넌시): 관리 plane vs 테넌트 plane 격리 + 부서관리 후속
이번 PR 은 invyone 멀티테넌시 SaaS 의 "관리 plane vs 테넌트 plane" 격리를
4 영역(PR #A~D) 에서 강화하고, 별도로 진행 중이던 부서관리 후속 작업을 포함한다.

# 보안 (plane 격리)

PR #A — controller/CompanyManagementController 인증 누락 패치
  /api/company-management/* 가 JWT/role/host 체크 없이 외부에서 누구나 회사 삭제
  + 디스크 통계 호출 가능했던 critical 누수 막음. SuperAdminGuard.enforce() 적용.

PR #C — cross-tenant 컨트롤러 호스트 격리 + 감사 로그
  CrossTenantContext.requireManagementHost() 헬퍼 추가, 5 컨트롤러
  (CrossTenantContext/Controller/UserController/RoleController/DeptController) 모두
  테넌트 호스트에서 호출 시 403. CompanyAuditLogService 에 cross-tenant write 4종
  (USER_CREATE/DELETE, PW_RESET, ROLE_UPDATE) audit action 추가.
  SuperAdminGuard.isTenantHost 가시성 public static 으로 승격.

PR #B — 프론트 솔루션 전용 admin 페이지 가드
  admin/* 페이지 전수 분류 결과 솔루션 전용 3건 식별:
  subdomainList / companyList / audit-log. 각 페이지에 isManagementHost
  useEffect 가드 + redirect 추가. 사이드바도 같이 숨김.

PR #D — MENU_INFO.IS_SOLUTION_ONLY 컬럼 + DB-driven 메뉴 필터
  V023 마이그레이션으로 컬럼 추가 + 솔루션 메뉴 3개 마킹.
  admin.xml selectUserMenuList 에 호스트 기반 필터 추가, AdminController.getUserMenus
  가 Host 헤더로 is_management_host 결정. 프론트 MANAGEMENT_ONLY_MENU_URLS
  하드코딩 set 폐기 (DB 가 대신함). 페이지 자체 가드는 defense in depth 로 유지.
  StartupSchemaMigrator 에 V023 등록되어 모든 테넌트 DB 부팅 시 자동 적용.

# 부서관리 후속 (이전 PR #18/#19 follow-up)

DepartmentController/Service + frontend deptMngList/department.ts 의 추가 작업분.
이번 격리 작업과 무관하지만 같이 정리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 10:59:15 +09:00
DDD1542 387a5c2bd7 Merge remote-tracking branch 'origin/main' into gbpark-node
Build & Deploy to K8s / build-and-deploy (push) Successful in 11m34s
2026-05-14 17:42:17 +09:00
DDD1542 3883031c0b feat(studio): Phase G — KPI stats / chart / cardList / groupedTable + canonical container tabs
INV Studio 데이터 뷰 시리즈. 솔루션 개발 단계라 backward-compat alias 없이 깔끔하게.

Backend:
- TableManagementController + Service: /aggregate, /aggregate-group, /select-rows endpoint 추가
  sanitize + hasColumn 검증 + buildAggregateWhere 공유 헬퍼

Frontend canonical view components (신규):
- stats: DB-first KPI editor (CPSegment 메타 chip, 컬럼 dropdown, 디자인 모드 debounce 350ms preview)
- chart: recharts (bar / horizontalBar / line / donut)
- card-list: title/subtitles/metrics 카드 카탈로그 (list / grid 레이아웃)
- grouped-table: 클라이언트 측 groupBy + 그룹 헤더 row

Canonical container (Phase G.2 / G.2.5 / G.2.6):
- containerType='tabs' 활성 탭만 mount, ChildSlot 으로 자식 렌더
- ScreenDesigner.handleComponentDrop 가 canonical container tabs 도 인식
- 우측 V2PropertiesPanel 4-way 분기: tab child / panel child / selected / empty
  nested path update + saveToHistory, delete handler 동기화

Shared utilities:
- useDbColumns hook (모듈 캐시), ColumnPicker (CPSelect 기반)
- OptionFilterRow 자연어 카드 형식 (컬럼 dropdown / 조건 select / 값 입력)
- _shared/use-table-rows.ts (cardList + groupedTable 공용 fetch)
- IconPicker: 한글 키워드 80+ alias, 휠 스크롤 fix, 360px 상한, 결과 80→300

stats DB-first UX (Phase G.4.x):
- DB / 정적 모드 이분법 제거 — 항상 dataSource 시작
- collapsed: 라벨 input + KpiMetaSegment chip (테이블 · 집계 · 컬럼 · 필터수)
- expanded: 데이터 / 필터 / 외형 / 고급 flat CP rows
- useSlideToggle hook 으로 펼침/닫힘 양방향 애니메이션
- 변화량 (delta) 수동 입력 UI 제거 — 향후 DB 자동 계산 영역
- 카드 fetch state 명시: loading / error / 대기 중 / 테이블 미설정

기타:
- ScreenDesigner.tsx → InvyoneStudio.tsx rename (활성 빌더 파일)
- 모든 hardcoded #6c5ce7 fallback 제거, hsl(var(--primary)) 토큰만 사용 (light/dark/테마 자동 적응)
- StatsDefinition default_config 도 DB-first placeholder (value: 0 박지 않음)

Docs:
- notes/gbpark/2026-05-14-studio-data-view-roadmap.md (G.0 ~ G.4.2 진행 기록)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 17:41:50 +09:00
johngreen 2f52d9587e Merge pull request 'fix(부서관리): 보안 + 운영 데이터 버그 8건 (PR #18/#19 후속)' (#20) from johngreen into main
Build & Deploy to K8s / build-and-deploy (push) Failing after 10m45s
2026-05-14 08:26:16 +00:00
johngreen 4f13d2e440 fix(부서관리): 보안 + 운영 데이터 버그 8건 (PR #18/#19 후속)
- security: syncManagers 가 user_id 의 회사 격리·실존 검증 (cross-tenant injection 차단)
- bug: base_date 필터에 START_DATE IS NULL 조건 추가 — 옛날 데이터가 기준일 켜자마자 사라지는 문제 해결
- bug: PUT partial update — 매니저 키 없으면 sync skip (단일 필드 수정 시 매니저 보존)
- bug: 페이지 로드 시 단일 컬럼 backfill — PR #19 이전 데이터가 chip UI 에서 사라지는 문제 해결
- validation: Controller base_date YYYY-MM-DD regex (잘못된 형식 시 400)
- validation: 프론트 handleSave start_date > end_date 체크
- robustness: parseManagersJson 64KB max + log.warn (catch silent swallow 제거)
- ops: StartupSchemaMigrator 실패 테넌트 DB 명단 부팅 종료 시 log.error 집계
2026-05-14 17:25:31 +09:00
johngreen 1613fae8fb Merge pull request 'feat(부서관리): 다중 관리자 + 조직장 (DEPT_MANAGERS)' (#19) from johngreen into main
Build & Deploy to K8s / build-and-deploy (push) Successful in 8m54s
2026-05-14 06:20:59 +00:00
johngreen c350ebe86a feat(부서관리): 다중 결재/부서 관리자 + 조직장 (DEPT_MANAGERS 매핑 테이블)
- 마이그레이션 V022/RUN_088: DEPT_MANAGERS 신규 (role: approval/dept/org_leader, PK 3-tuple, FK CASCADE)
- StartupSchemaMigrator 에 V022 idempotent CREATE 추가 → 테넌트 DB 자동 동기화
- mapper.xml: SELECT 에 3 json_agg ::TEXT 컬럼 추가, insertDeptManagers + deleteDeptManagersByDeptAndRole 신규
- service: parseManagersJson + syncManagers (delete-all + insert-all, 최대 10명, 역할 한글 메시지)
- frontend: types 3 필드, DeptDetailDraft 확장, ManagerChipsField (chip+UserSearchModal 재사용), 조직장 Row 신규

기존 DEPT_INFO.APPROVAL_MANAGER / DEPT_MANAGER 단일 컬럼은 호환을 위해 유지.
2026-05-14 15:19:50 +09:00
johngreen 5335dc78b0 Merge pull request 'feat(부서관리): 기준일 필터 + 시작일/종료일 UI 노출' (#18) from johngreen into main
Build & Deploy to K8s / build-and-deploy (push) Successful in 8m57s
2026-05-14 05:16:12 +00:00
johngreen ecad2915ce feat(부서관리): 기준일 필터 + 시작일/종료일 UI 노출
- 백엔드: selectDepartments 에 base_date <if> 블록 추가 (start_date <= base_date AND (end_date IS NULL OR end_date >= base_date))
- 서비스/컨트롤러에 3-arg overload 와 @RequestParam("base_date") 추가
- 프론트: 사용기간 RadioGroup + 시작일/종료일 Row 의 {false &&} hide 제거
- loadDepartments 가 periodMode === "date" 일 때 baseDate 를 API 에 전달
2026-05-14 14:12:59 +09:00
johngreen 0552425f47 Merge pull request 'feat(테이블타입): 헤더 표시명/설명 inline click-to-edit (Google Docs 패턴)' (#17) from johngreen into main
Build & Deploy to K8s / build-and-deploy (push) Successful in 7m1s
2026-05-14 01:53:05 +00:00
johngreen ca241c017d feat(테이블타입): 헤더 표시명/설명 inline click-to-edit (Google Docs 패턴)
기존 "헤딩 + 옆에 input box 2개 + 저장 버튼" 구조의 UX 문제:
- 같은 값을 헤딩과 input 양쪽에서 중복 표시
- 항상 폼처럼 보여 컬럼 그리드 시선을 뺏음
- 라벨만 바꾸려는 의도가 "전체 설정 저장" 에 묶여 흐려짐

변경: 헤딩 텍스트 자체를 클릭하면 그 자리에서 input 으로 변신
(Google Docs 문서 제목 / Notion 패턴).
- blur 또는 Enter → PUT /label 즉시 저장
- Esc → 취소
- hover 시 muted/60 배경, cursor: text, title tooltip 으로 affordance
- 설명이 비어 있으면 "+ 설명 추가" 힌트 표시
- 표시명은 비울 수 없게 가드 (toast.error)

"전체 설정 저장" → "컬럼 설정 저장" 으로 책임 분리:
- 헤더 라벨/설명: inline 즉시 저장
- 컬럼 input_type/web_type/detail_settings 등 일괄: 버튼

키보드 접근성:
- Tab 으로 헤딩에 focus → Enter/Space 로 편집 모드
- Esc 로 취소, Enter 로 커밋 (blur 트리거)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:52:29 +09:00
johngreen ec679ac640 Merge pull request 'fix(테이블타입): constraints SQL ARRAY_AGG → text 캐스트로 일원화' (#16) from johngreen into main
Build & Deploy to K8s / build-and-deploy (push) Successful in 8m45s
2026-05-14 00:18:53 +00:00
johngreen 1e1b3e103c fix(테이블타입): constraints SQL ARRAY_AGG → text 캐스트로 일원화
이전 PR (#15) 의 Java parseColumnArray 분기 추가가 실제 운영 환경에서
빈 배열을 그대로 반환 — MyBatis ↔ PostgreSQL JDBC 의 array 타입 변환이
java.sql.Array 가 아닌 다른 경로로 도착하는 듯.

방식 변경: SQL 단에서 ARRAY_AGG(...)::text 캐스트 → PostgreSQL 가
"{email,phone}" String 으로 반환. parseColumnArray 의 기존 String 분기
(중괄호 제거 + 쉼표 split) 가 자연스럽게 처리.

장점:
- JDBC 드라이버 / MyBatis 변환 동작에 의존하지 않음
- parseColumnArray 코드 단순 복원 (List/String 2분기)
- 한 줄 SQL 변경으로 PK/IDX 두 쿼리 모두 해결

검증:
- gradle compileJava BUILD SUCCESSFUL
- solution.invyone.com 에서 customer_mng PK columns / email IDX columns
  비어있지 않음 확인 예정

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 09:18:26 +09:00
johngreen 35d5a00b20 Merge pull request 'fix(테이블타입): constraints API PgArray 디코딩 + primary_key key 매칭' (#15) from johngreen into main
Build & Deploy to K8s / build-and-deploy (push) Successful in 8m29s
2026-05-13 23:33:17 +00:00
johngreen 0365b743f5 fix(테이블타입): constraints API PgArray 디코딩 + primary_key key 매칭
테이블 타입 관리 페이지에서 PK/IDX 토글 후 UI 상태가 갱신되지 않던 버그.

1. Backend: TableManagementService.parseColumnArray
   - PostgreSQL ARRAY_AGG 가 java.sql.Array (PgArray) 로 오는데
     List/String 만 처리해서 항상 List.of() 반환 → columns 빈 배열
   - 조치: java.sql.Array 분기 추가, arr.getArray() → Object[] → List<String>

2. Frontend: loadConstraints
   - 백엔드는 result.put("primary_key", ...) 로 snake_case 반환
   - 프론트가 data.primaryKey 로 camelCase 로 읽어 undefined → 항상 빈 PK
   - 조치: data.primary_key 로 통일

이전 PR (#14) 의 IDX payload 수정과 합쳐, PK/IDX 토글이 API 호출/DB 적용/
UI 반영까지 한 사이클로 동작.

검증:
- gradle compileJava BUILD SUCCESSFUL
- solution.invyone.com 에서 IDX 토글 양방향 확인 예정

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:32:48 +09:00
johngreen ff3d4c2cc5 Merge pull request 'fix(테이블타입): IDX/label API payload key snake_case 통일' (#14) from johngreen into main
Build & Deploy to K8s / build-and-deploy (push) Successful in 7m1s
2026-05-13 23:20:22 +00:00
johngreen 44f5b134a5 fix(테이블타입): IDX/label API payload key snake_case 로 통일
테이블 타입 관리 페이지에서 IDX 토글 / 테이블 라벨 저장이 400 에러로
조용히 실패하던 버그. 백엔드는 body.get("column_name") / get("index_type")
/ get("display_name") 등 snake_case 로 읽는데 프론트가 camelCase 로 보내고
있었음 (CLAUDE.md Map key snake_case 컨벤션 위반).

- POST /table-management/tables/:t/indexes
  { columnName, indexType, action } → { column_name, index_type, action }
- PUT /table-management/tables/:t/label
  { displayName } → { display_name }

PK 는 다이얼로그 확인 흐름, NN/UQ 는 key 가 맞아 영향 없음.
SUPER_ADMIN 으로 테스트 시 IDX 만 안 되던 증상 일치.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:18:09 +09:00
johngreen ff4033b927 Merge pull request #13 - fix+security: bug hunt 6 + 인가/SQL 2
Build & Deploy to K8s / build-and-deploy (push) Successful in 8m46s
johngreen -> main: 테이블 타입 관리 bug fix 6 + 보안 hardening 2
2026-05-13 09:08:11 +00:00
johngreen efea906ead security(테이블타입): TableManagementController 인가 + createLogTable SQL injection 강화
bug hunt security-reviewer 발견 2건 보안 fix:

1. TableManagementController 인가 누락 (OWASP A01 Broken Access Control)
   - 15개 write/DDL endpoint 가 admin role 검증 없이 JWT 만 있으면 호출 가능
   - 일반 사용자가 PK 재설정/index 변경/컬럼 수정 가능했음
   - 조치:
     - DepartmentController 의 isAdmin/isSuperAdmin helper 패턴 복사
     - SUPER_ADMIN 전용 (DDL 5건): primary-key, indexes, nullable, unique, log
     - admin (COMPANY_ADMIN+) (10건): updateColumnSettings, addTableData, editTableData, deleteTableData, multi-save 등
     - read 19건은 그대로 (일반 사용자 접근 유지, company_code 격리만)

2. createLogTable SQL injection (OWASP A03 Injection)
   - information_schema.data_type 을 raw concat 으로 DDL 생성
   - 조치:
     - ALLOWED_LOG_COLUMN_TYPES Set 으로 화이트리스트 (varchar/text/integer/numeric/boolean/date/timestamp/jsonb 등 21개)
     - sanitize 빈 식별자 차단 + 원본 테이블에 없는 컬럼 skip
     - colDefs empty 시 IllegalArgumentException
     - 알 수 없는 type 은 text 로 안전 대체

검증:
- gradle compileJava BUILD SUCCESSFUL
- mapper XML 0건 변경 (READ 경로 보호)
- 별도 미해결 보안 이슈 (k8s secrets git 노출, CORS 와일드카드 등) 는 본 PR scope 외

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:07:40 +09:00
johngreen 420b92bc7b fix(테이블타입): bug hunt 발견 6건 수정 (B1~B6)
review 4명 (debugger + security-reviewer + architect + code-reviewer) 발견:

- B1 [CRITICAL]: DdlService.convertToInputType 에 file/image/numbering case 추가
  - 사용자가 파일/이미지/채번 선택해도 silent text 저장되던 버그
  - 박창현 image 2 의 8개 중 3개가 운영에서 작동 안 함
- B2 [MAJOR]: TableManagementService.updateColumnSettings null check
  - settings 에 input_type 키만 있고 값 null 일 때 500 에러 방지
- B3 [MAJOR]: TableSettingModal.tsx 'direct' default → 'text'
  - 운영의 bom.status 같은 컬럼이 UI 에 'direct' 표시되던 원인 제거
- B4 [MINOR/UX]: TypeOverviewStrip 에 Legacy 합산 칩 추가
  - V0 의 legacy 1,209 row (category 888 외) 가 strip 에서 보이도록 amber 칩 + 도넛 호
- B5 [DRY]: USER_SELECTABLE_INPUT_TYPES 공통 상수 추출
  - TableManagementService:30 + DdlService:43 중복 → InputTypeConstants 신설
- B6 [type safety]: context flag enum 화
  - 'user-insert'/'user-update-type'/'user-update-other'/'system-normalize' string → InputTypeContext enum
  - typo silent fail 차단

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:07:19 +09:00
hjjeong 0328f618b9 Merge pull request 'style(rolesList): 다른 메뉴 톤에 맞춰 사이즈/글씨 축소' (#12) from hjjeong into main
Build & Deploy to K8s / build-and-deploy (push) Successful in 8m35s
Reviewed-on: #12
2026-05-13 08:23:51 +00:00
hjjeong f53307a72e Merge remote-tracking branch 'origin/main' into hjjeong 2026-05-13 17:20:59 +09:00
hjjeong cbf94dc90f feat(batch): TO DB 자동 선택 (internal) + Select 컴포넌트 controlled 화
새 배치 (REST API → DB) 진입 시 매번 사용자가 "데이터베이스 커넥션 선택" 셀렉트에서
"내부 DB" 를 직접 골라야 했음. 대부분의 배치가 internal 적재라 디폴트 채움이 자연스러움.

1) TO DB 자동 선택
   useEffect 로 batchType === "restapi-to-db" + connections 로드 + toConnection 비어있음
   조건 만족 시 handleToConnectionChange("internal") 자동 호출. 사용자가 외부 DB 로 변경하면
   toConnection != null 이 되어 더 이상 자동 동작 안 함.

2) Select controlled 화
   DB 커넥션/테이블 Select 가 value prop 없는 uncontrolled 상태였음.
   setToConnection/setToTable state 가 바뀌어도 Select UI 가 placeholder 그대로 →
   programmatic 자동 선택이 시각적으로 반영 안 됨.
   → value prop 추가:
     - DB 커넥션: toConnection.type === "internal" ? "internal" : String(toConnection.id)
     - 테이블: toTable

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:18:09 +09:00
johngreen aeddd7dc2a Merge pull request #11 - perf(CI): standalone output
Build & Deploy to K8s / build-and-deploy (push) Successful in 6m41s
johngreen -> main: frontend Dockerfile standalone 활용. 빌드 21m -> ~9m
2026-05-13 07:45:22 +00:00
johngreen 5fdd1c67b1 perf(CI): frontend build runner stage 를 Next.js standalone output 활용으로 전환
배경:
- frontend build 의 가장 큰 시간 소비 = runner stage 의 COPY node_modules (12분 16초)
- 전체 21분 34초 중 57%
- next.config.mjs 의 output: "standalone" 가 prod 빌드에서 이미 활성 상태였으나, Dockerfile 의 runner stage 가 .next 통째 + node_modules 통째를 COPY 하느라 standalone 결과물 미활용

조치:
- runner stage 재작성:
  - .next 전체 → .next/standalone (server.js + 실제 사용 node_modules)
  - .next/static 별도 COPY (standalone 가 자동 포함 안 함)
  - public 별도 COPY (standalone 가 자동 포함 안 함)
  - node_modules 통째 COPY 제거 (standalone 가 알아서 포함)
  - package.json COPY 제거 (server.js 직접 실행)
  - CMD: npm start → node server.js

검증:
- frontend 에 dynamic require/import 0건 (정적 import 만) → standalone 의존성 추적 정확
- prisma 가 package.json 에 있으나 코드 import 0건 → 자연 제외, 추가 설정 불필요

예상 효과:
- 빌드 시간 21m 34s → 약 9분 (12분 단축, 57% 감소)
- 이미지 크기 약 1GB → 약 300MB (70% 감소)
- pull 시간 단축
- runtime memory footprint 감소

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 16:44:36 +09:00
hjjeong 54a8f97f78 fix(batch): 미리보기 → 매핑 카드 표시 흐름 정상화 + 매핑 카드 컴팩트화
배치 생성 흐름 검증 중 발견된 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) <noreply@anthropic.com>
2026-05-13 16:32:41 +09:00
johngreen 0199d1624b Merge pull request #10 - fix(k8s): ConfigMap sslmode=disable
Build & Deploy to K8s / build-and-deploy (push) Successful in 26m24s
johngreen → main: ConfigMap SPRING_DATASOURCE_URL 에 sslmode=disable 영구 추가
2026-05-13 07:14:55 +00:00
johngreen b3f955d97d fix(k8s): ConfigMap SPRING_DATASOURCE_URL 에 sslmode=disable 추가
PR #9 deploy 단계 실패의 후속 처리.

배경:
- 운영 cluster 의 backend-spring deployment 에 누군가 직접 sslmode=disable 박은 value override 가 있었음 (env[2] SPRING_DATASOURCE_URL value + valueFrom 동시 존재 → k8s reject)
- PR #9 머지 후 수동으로 cluster 의 ConfigMap 에 sslmode=disable patch + deployment 재생성 + 새 image rollout 완료
- 다만 git 의 k8s/configmap.yaml 은 sslmode 없는 상태 → 다음 머지 시 workflow 가 ConfigMap 을 git 값으로 덮어쓰면 sslmode 다시 빠짐 → backend pod 가 SSL 시도 후 DB 연결 실패 가능

조치:
- k8s/configmap.yaml line 9 의 SPRING_DATASOURCE_URL 에 ?sslmode=disable 영구 추가
- 운영 postgres 가 SSL 강제 안 하는 환경이라 disable 가 안전
- cluster 와 git 의 state 일치

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 16:14:28 +09:00
johngreen ae899a3589 Merge pull request #9 — refactor(테이블타입): 3-layer 분리 + CI OOM fix
Build & Deploy to K8s / build-and-deploy (push) Failing after 24m30s
johngreen → main: 테이블 타입 관리 3-layer 분리 (DB 12 유지 / UI 8 한정 / widget variant) + backend 백스톱 + CI OOM fix
2026-05-13 06:32:16 +00:00
johngreen 43b0455364 fix(CI): frontend build OOM 방지 — NODE_OPTIONS=--max-old-space-size=4096
main 의 최근 8연속 build 실패 (run 113~120) 원인이 docker build 단계의 OOM Killed.

진단:
- wace 호스트 32GB / available 24GB 충분, 시스템 OOM 기록 없음
- act_runner systemd MemoryMax=infinity, config container.options=null (제한 없음)
- 그러나 Next.js build V8 heap spike (5-8GB+) 가 다른 30개 동시 가동 서비스 (k3s/mailu/nextcloud/mattermost 등) 와 충돌
- Committed_AS 21.6 GB / Limit 24.8 GB 한계 — page touch 시 oom-killer 가 build process kill
- 결과: 28분 빌드 후 Killed → 8연속 main 배포 실패

조치:
- builder stage 에 NODE_OPTIONS=--max-old-space-size=4096 명시 → V8 heap 4GB 로 cap
- build process 가 알아서 절제 → OOM killer 트리거 안 됨

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 15:24:25 +09:00
hjjeong b752de23a1 fix(batch): previewRestApiData 에 convertCamelToSnake 누락 보강 (400 원인)
데이터 불러오고 매핑하기 클릭 시 400 발생.
원인: 프론트는 camelCase (apiUrl/endpoint/method/apiKey/dataArrayPath/paramType/...)
로 body 를 보내는데 백엔드는 snake_case (api_url/endpoint/method/api_key/...) 키로 읽음.
다른 service 진입점 (updateBatchConfig / executeBatchConfig 등) 은 convertCamelToSnake
를 호출해서 자동 변환하는데 previewRestApiData 만 빠져있어 isBlank(apiUrl)=true 가
되며 IllegalArgumentException → 400.

수정: previewRestApiData 진입부에 convertCamelToSnake(body) 한 줄 추가.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 15:11:33 +09:00
johngreen 574319811c refactor(테이블타입): backend INSERT/UPDATE 8개 validate 백스톱
- TableManagementService.normalizeInputType(value, context) 오버로드 — user-insert/user-update-type 만 8개 검증, user-update-other/system-normalize 는 skip
- TableManagementService.updateColumnSettings: payload 의 input_type 키 존재 여부로 context 분기 (input_type 자체 변경 vs 다른 속성 변경)
- DdlService.addColumn / saveColumnMetadata: convertToInputType 결과를 USER_SELECTABLE_INPUT_TYPES (8개) 와 대조, 외이면 IllegalArgumentException

mapper XML 5곳 (categoryTree / entityJoin / tableCategoryValue / screenManagement / tableManagement / entityReference) 무변경 — READ 경로 12개 그대로.

spec: .omc/specs/deep-dive-table-type-storage-ui-separation.md (v3.2 §6.3)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:43:41 +09:00
johngreen 8f92fb2368 refactor(테이블타입): 3-layer 분리 — DB 12개 유지, UI 8개 한정, widget variant
- input-type-mapping.ts: BaseInputType 10개 → UserSelectableInputType 8개 (박창현 image 2). vexplor_rps INPUT_TYPE_DETAIL_TYPES 포팅, select/checkbox/radio variant 를 code base 로 흡수
- input-types.ts: USER_SELECTABLE_INPUT_TYPE_ORDER/LABELS re-export (InputType 12개는 그대로)
- getDetailType.ts (신규): getWidgetVariants / getDefaultWidgetVariant helper
- 드롭다운 호출처 7개 8개 제한: ColumnDetailPanel, AddColumnModal, ColumnDefinitionTable, tableMngList/page.tsx, TableSettingModal, TypeOverviewStrip, types.ts
- ColumnDetailPanel: Legacy row 드롭다운 disabled + v5-glow-sm Alert 배너
- backward shim: BaseInputType / BASE_INPUT_TYPE_OPTIONS / getBaseInputType 등 V2/Properties/DetailSettingsPanel 호환

운영 DB 96.6% 가 이미 8개 안 (V0, 35,316 row). DB zero touch, mapper 5곳 보호.

spec: .omc/specs/deep-dive-table-type-storage-ui-separation.md (v3.2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:43:26 +09:00
hjjeong 6fcb101f59 style(batch): API 파라미터 설정을 collapsible 로 변경 — 기본 접힘
이전: 페이지 진입 시 항상 펼쳐져 큰 공간 차지 → 매핑 영역이 화면 밖으로 밀림
신규: <details> + summary 로 기본 접힘. 클릭 시만 펼침. 토글 아이콘 함께.

특정 사용자/조건으로 API 조회할 때만 쓰는 옵션이라 기본 접힘이 자연스러움.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:34:26 +09:00
hjjeong 47eed68072 fix(external-rest-api): WHERE ID = #{id} 에 ::varchar 캐스팅 추가
외부 커넥션 관리에서 연결 테스트 시 500 발생.
원인: 운영 DB 의 external_rest_api_connections.id 가 V001 legacy 마이그레이션으로
character varying 인데 mapper 의 WHERE ID = #{id} 가 controller 의 int id 를
그대로 받아 PgJDBC 가 보내는 BIND 가 정수.
PostgreSQL 이 "operator does not exist: character varying = integer" 거부 →
SQLException → ApiResponse 500 ("서버 내부 오류").

수정: 4곳 모두 #{id}::varchar 캐스팅 추가.
  - getExternalRestApiConnectionInfo (SELECT)
  - updateExternalRestApiConnection (UPDATE)
  - deleteExternalRestApiConnection (DELETE)
  - updateExternalRestApiConnectionTestResult (UPDATE)

배치 작업의 batch_config_id 패턴과 동일. 같은 V001 영향을 받은 다른 mapper
(externalDbConnection.xml / externalCallConfig.xml / booking.xml / delivery.xml /
multiConnection.xml / taxInvoice.xml 등) 도 같은 수정이 필요할 가능성 — 별도 작업으로 분리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:25:17 +09:00
hjjeong d8f606ab00 style(batch): 기본 정보를 모드 토글과 한 행으로 통합 — 하단 매핑 영역 확보
사용자 보고: 기본 정보 카드가 별도 영역을 차지해서 한 화면에 매핑 영역(중요) 까지
스크롤 없이 안 들어옴. vexplor_rps 스크린샷처럼 상단을 한 줄로 컴팩트하게.

변경:
- 배치 타입 토글 grid + 기본 정보 카드를 단일 grid 로 통합
- xl(1280px+): grid-cols-[minmax(28rem,1.4fr)_1fr_1fr_1.5fr]
  → 모드토글그룹 + 배치명 + 스케줄 + 설명 한 줄
- xl 미만: grid-cols-1 stack
- 모드 토글 카드 컴팩트화: p-3 / h-9 아이콘 / text-[10px] description
- 설명: Textarea → Input (단일 행) 로 축소. 긴 설명은 어차피 메모 용도라 한 줄이면 충분.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:20:00 +09:00
hjjeong e8f517ed18 fix(batch): batch-management-new 도 풀폭 적용 — 이전 풀폭 커밋에서 누락된 파일
0bba1836 에서 batchmngList/page.tsx 와 edit/[id]/page.tsx 두 곳만 max-w 제거했고
batch-management-new 의 root mx-auto max-w-5xl 가 남아있어 가운데 1024px 컬럼으로
본문이 박혀있었음. 좌우 패널이 sparse 하게 보이는 원인.

mx-auto max-w-5xl → w-full

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:16:32 +09:00
hjjeong d02bc38f6c style(batch): FROM 카드 행 그룹화 + 컴팩트 폰트로 sparse 레이아웃 정리
이전: API 서버 URL / 인증 토큰 / 엔드포인트 / 메서드 / 데이터 배열 경로가 각각 단독 행
신규: 의미 단위로 그룹화 + 컴팩트화

- API 서버 URL (3fr) + HTTP 메서드 (1fr) 한 행
- 엔드포인트 + 데이터 배열 경로 50:50 한 행
- 인증 토큰: 라디오 → 세그먼티드 토글 버튼 그룹 + 입력을 한 행에 압축
- Label text-xs / Input h-9 text-sm 로 컴팩트 통일

vexplor_rps batch-management-new (L1417 부근) 의 컴팩트 레이아웃 패턴 참고.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:01:40 +09:00
hjjeong 0c9e22a679 feat(batch): 등록 REST API 연결 자동 호출 + 응답 필드 추출
vexplor_rps batch-management-new 의 applyRegisteredRestApi 핵심 흐름을 INVYONE 에
이식. 등록된 REST API 연결 선택 시:
  1. 폼(URL/엔드포인트/메서드/Body/인증 토큰) 자동 채움
  2. ExternalRestApiConnectionAPI.testConnectionById 로 자동 API 호출
  3. 응답 안에서 배열 자동 탐색 (depth ≤ 4)
  4. fromApiFields / fromApiData 채움 → 매핑 드롭다운에 필드 즉시 노출

UI: FROM 카드 최상단에 "🔗 등록된 연결" 셀렉터 추가.
로딩 중에는 셀렉터 라벨 옆에 스피너, 에러 시 destructive 톤 메시지.

vexplor_rps 와 다르게 제외한 부분:
  - Amaranth/Wehago 회사 전용 프리셋 (AMARANTH_TARGET_PRESETS) — 6종 ERP 동기화
    배치 전용 하드코딩이라 INVYONE 일반 사용자에겐 의미 없음. Phase 6 후속에서
    검토할 별도 영역.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 13:54:11 +09:00
hjjeong 570b3267ab feat(batch): batch-management-new 에 conditional 매핑 추가 (Phase 2 일관성)
vexplor_rps 와 비교 시 batch-management-new 에 조건 변환 모드가 빠져있어
batchmngList/edit/[id] 와 같은 패턴으로 정렬.

- MappingItem.sourceType 에 'conditional' 추가 + conditionalConfig 필드
- ConditionalConfig / ConditionalEditor / emptyConditionalConfig import
- Select 옵션에 "조건 변환" 추가
- conditional 일 때 ConditionalEditor 렌더
- validMappings 필터: conditional 은 평가 필드(apiField) + 룰 또는 default 필요
- apiMappings 변환: mapping_type='conditional' + mapping_config 객체 전송

note: 이 페이지는 신규 생성 전용이라 load(편집 복원) 로직은 없음.
편집은 batchmngList/edit/[id]/page.tsx 가 처리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:29:21 +09:00
hjjeong 0bba1836fb fix(batch): 빈 화면 원인이던 openTab 키명 정정 + 본문 풀폭 적용
문제 1: 배치 수정/생성 탭이 빈 화면
- Tab type 정의는 admin_url (snake_case) 인데 호출자가 adminUrl (camelCase)
  로 전달 → TabPageRenderer 매칭 실패 → null 렌더 → 빈 화면
- 콘솔 경고: "[TabPageRenderer] 렌더링 불가 — 매칭 조건 없음" (TabContent.tsx:268)
- 11곳 (4파일) 의 adminUrl → admin_url 정정

문제 2: 리스트/편집 본문이 좁은 가운데 컬럼에 박혀있음
- batchmngList/page.tsx: max-w-[720px] → w-full
- batchmngList/edit/[id]/page.tsx: max-w-[640px] → w-full

해당 파일:
- batchmngList/page.tsx
- batchmngList/edit/[id]/page.tsx
- batchmngList/create/page.tsx
- batch-management-new/page.tsx

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:15:51 +09:00
hjjeong f70719aecb fix(batch): batch_execution_logs 의 VARCHAR 숫자 컬럼에 명시적 String 전달
운영 DB 검증 결과 batch_execution_logs.duration_ms / total_records /
success_records / failed_records 가 모두 character varying 으로 정의됨
(V001 legacy 마이그레이션 흔적). PgJDBC 가 Long/Integer 를 VARCHAR 컬럼에
자동 변환하지 못할 위험이 있어 명시적으로 String.valueOf 로 변환 후 전달.

mapper 의 COALESCE default 가 '0' (문자열) 인 점과도 일관.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:56:40 +09:00
hjjeong 3ab7deb196 test(batch): Phase 3 — MappingTransformerTest (18 cases)
vexplor_rps 알고리즘 1:1 이식이 정상 동작하는지 검증.

검증 항목:
- evaluateConditional: 단순 매칭 / null cfg / 빈 rules
- parseConditionalConfig: Map / String(JSON) / null / 손상 JSON 모두 안전
- getValueByPath: 단순 키 / 중첩 경로 / 없는 경로 / null obj
- partitionFixed: fixed / non-fixed 분리
- transformRow: direct / conditional("1"→"Y") / conditional default 폴백 /
  fixed 적용 / 점 표기법 ("user.profile.name") / company_code 자동주입 조건

18 tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:53:12 +09:00
hjjeong d592547242 fix(batch): writeTo @Transactional 제거 + end_time Timestamp 객체 전달
검증 전 선제 보강 2건.

1) BatchExecutor.writeTo 의 @Transactional 제거
   - 같은 클래스 내부 호출(execute → writeTo)이라 Spring AOP 가 우회되어 어차피 안 걸림
   - batch 의 정상 동작은 row 단위 독립 commit (일부 실패해도 다른 row 는 살아야 함).
     vexplor_rps 도 동일 패턴

2) BatchManagementService.executeBatchConfig 의 end_time 을 Timestamp 객체로 직접 전달
   - 기존: Timestamp.toString() 으로 변환 후 #{end_time}::timestamp 캐스트
   - 신규: Timestamp 객체 그대로 → PostgreSQL JDBC 가 timestamp 자동 변환,
     mapper 의 ::timestamp 캐스트는 noop

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:50:49 +09:00
hjjeong 6f8461a533 feat(batch): Phase 5 — executeBatchConfig 가 BatchExecutor 호출 + batch_execution_logs 기록
기존 stub(0 만 리턴) 였던 BatchManagementService.executeBatchConfig 를 실제 ETL 실행 + 로그 기록 흐름으로 교체.

흐름:
  1. RUNNING 상태로 batch_execution_logs INSERT (도중 비정상 종료 추적용)
  2. BatchExecutor.execute(batchConfig) 호출
     - 정상: failedRecords > 0 면 PARTIAL/FAILED, 아니면 SUCCESS
     - 예외: FAILED 로 마킹, error_message 기록
  3. 로그 UPDATE — execution_status, end_time, duration_ms, total/success/failed_records, error_message
  4. controller 응답에 execution_status + error_message 동봉

server_name 은 InetAddress.getLocalHost(), process_id 는 ProcessHandle.current().pid().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:31:44 +09:00
hjjeong 17172cf9b3 feat(batch): Phase 4 — BatchExecutor ETL 본체 (FROM → Transform → TO)
vexplor_rps batchSchedulerService.executeBatchMappings 의 1:1 이식.

흐름:
  1. mappings 를 fixed/non-fixed 로 partition
  2. non-fixed 를 (from_connection_type, from_connection_id, from_table_name) 으로 그룹화
  3. 그룹별 FROM 읽기 → MappingTransformer.transformRow → TO 저장
  4. (totalRecords, successRecords, failedRecords) 집계

FROM 소스:
  - internal     : sqlSession.getConnection() 의 동적 SELECT (식별자 화이트리스트 escape)
  - external_db  : ExternalDbConnectionService.executeQuery (SELECT-only)
  - restapi      : ExternalRestApiConnectionService.fetchData (등록된 연결 + dataArrayPath)

TO 대상:
  - internal     : INSERT / UPSERT(save_mode + conflict_key, ON CONFLICT DO UPDATE/NOTHING)
                   updated_date 컬럼 있으면 자동으로 NOW() 갱신
  - restapi      : 행 단위 POST/PUT/DELETE — testConnection 으로 호출
  - external_db  : 미지원 (보안 정책: ExternalDbConnectionService 가 SELECT-only)

vexplor_rps 대비 단순화 항목 (필요 시 후속 Phase 로 분리):
  - to_api_body 템플릿 기반 일괄 전송
  - URL_PATH_PARAM 컬럼 처리
  - auth_tokens 자동 조회 (inline-mode REST API: from_connection_id 없이 from_api_url 직접 호출)
  - row_filter_config

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:30:27 +09:00
hjjeong f9a9c67891 feat(batch): Phase 3 — MappingTransformer lookup 엔진
vexplor_rps batchSchedulerService L540~617 의 변환 로직 1:1 이식.
의존성 없는 정적 유틸 — BatchExecutor 가 FROM 읽기 결과를 TO 형태로 변환할 때 사용.

- transformRow: mapping_type 별 분기 (direct/conditional/fixed),
  멀티테넌시용 company_code 자동 주입, 점 표기법 path 평가
- evaluateConditional: ConditionalConfig.rules 의 when/then lookup + default 폴백.
  단순 문자열 동등 비교 (SpEL/JEXL 표현식 평가 안 함)
- getValueByPath: "response.access_token" 같은 중첩 키 지원
- parseConditionalConfig: JSONB 가 Map/String/null 셋 다 가능 — 안전 normalize
- partitionFixed: non-fixed / fixed 매핑 분리 헬퍼

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:26:28 +09:00