Commit Graph

3957 Commits

Author SHA1 Message Date
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
hjjeong f31a7f852f feat(batch): Phase 2 — 프런트 ConditionalEditor + 조건 변환 매핑 UI
- batch.ts: ConditionalRule / ConditionalConfig 타입 추가,
  BatchMapping 에 mapping_type ('direct'|'fixed'|'conditional') + mapping_config 필드
- ConditionalEditor.tsx: 평가 필드 선택 + when/then 룰 add/remove + default 입력 컴포넌트.
  emptyConditionalConfig / normalizeConditionalConfig 헬퍼 동봉. vexplor_rps 1:1 포팅
- batchmngList/edit/[id]/page.tsx:
  · MappingItem.sourceType 에 'conditional' 추가 + conditionalConfig 필드
  · 소스타입 Select 에 "조건 변환" 옵션
  · Load: mapping_type=conditional 인 매핑은 mapping_config JSON 파싱 후 복원
  · Save: sourceType=conditional 매핑은 mapping_config 객체와 함께 전송

저장된 룰: {"rules":[{"when":"1","then":"Y"}],"default":"?"} 형태.
Phase 1 의 BatchService 직렬화 경로로 JSONB 에 저장된다.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:25:08 +09:00
hjjeong 2675c82904 feat(batch): Phase 1 — BATCH_MAPPINGS.MAPPING_CONFIG JSONB 컬럼 + JSON 직렬화
- V021 Flyway: ALTER TABLE BATCH_MAPPINGS ADD COLUMN MAPPING_CONFIG JSONB
- StartupSchemaMigrator: 같은 ALTER 를 idempotent 항목으로 추가 (모든 활성 테넌트 DB 부팅 시 동기화)
- batch.xml: getBatchMappingsByConfigId SELECT 에 MAPPING_CONFIG::TEXT cast,
  insertBatchMapping VALUES 에 #{mapping_config,jdbcType=OTHER}::jsonb
- BatchService: ObjectMapper 주입, parseJsonField/stringifyJsonField 유틸,
  syncMappings 는 INSERT 전 직렬화, attachMappings 는 SELECT 후 Map 으로 역직렬화
- RUN_087_MIGRATION.md: 운영용 마이그레이션 runbook (사전 점검/사후 검증/롤백)

conditional 매핑(when/then/default) 룰을 행 단위 저장하는 컬럼.
direct/fixed 는 NULL. Phase 2~3 에서 프런트/엔진이 이 컬럼을 읽고 쓴다.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:24:56 +09:00
hjjeong dce665caea feat(batch): Phase 0 — batch_mappings CRUD path + 실행 로그/관리 SQL 정리
- BatchService: insertBatch/updateBatch 가 body.mappings 받아 replace-all 동기화,
  getBatchInfo 가 batch_mappings 리스트 attach (지금까지는 silently drop)
- batch.xml: getBatchMappingsByConfigId / insertBatchMapping / deleteBatchMappingsByConfigId 신규
- batchExecutionLog.xml / batchManagement.xml: batch_config_id 에 ::varchar 캐스팅,
  오타 'batch_execution_log' → 'batch_execution_logs' 정정
- batchmngList/page.tsx: 같은 batch ID 가 회사 간 중복될 때 React key 충돌 방지
- notes: vexplor_rps → INVYONE 배치 파이프라인 이식 분석 노트

vexplor_rps → INVYONE 파이프라인 이식의 Phase 0.
구체 분해는 notes/hjjeong/2026-05-12-batch-pipeline-current-state.md 참조.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:24:36 +09:00
johngreen 5c0dca004b Merge fix(반응형): 페이지네이션 잘림 픽스
Build & Deploy to K8s / build-and-deploy (push) Failing after 31m59s
2026-05-13 09:41:44 +09:00
johngreen acbd61e25a fix(반응형): 페이지네이션 잘림 — viewport 하드코딩 → flex-1 min-h-0
기존: scrollContainer 모드의 max-h-[calc(100vh-280px)] 가 viewport 기준 하드코딩이라 페이지 헤더+툴바 합산이 280px 보다 클 때 카드 영역이 viewport 끝까지 차지 → 페이지네이션이 부모 overflow-hidden 에 잘림.

신: outer wrapper 에 flex flex-col min-h-0 flex-1, 내부 테이블/카드에 min-h-0 flex-1 overflow-y-auto. 부모 flex-col 의 남는 공간만 차지 → 페이지네이션 등 형제는 자기 자연 height 유지.

로딩 스켈레톤 동일 패턴 적용.
2026-05-13 09:41:42 +09:00
johngreen dc77c07cc4 Merge refactor(반응형): container query 전환
Build & Deploy to K8s / build-and-deploy (push) Failing after 28m28s
2026-05-13 08:43:16 +09:00
johngreen 6d5ca2f23a refactor(반응형): ResponsiveDataView 를 container query 기반으로 전환
기존: viewport 기준 (lg:hidden, sm:grid-cols-2) — 사이드바 펼친 상태에서 콘텐츠 영역의 실제 width 와 무관하게 동작 → 좁은 영역에 2열 카드가 들어가 카드가 잘려보이는 문제

신: @container 기반 — 컴포넌트가 자기 부모 컨테이너 width 에 반응
- 컨테이너 < 32rem (512px): 카드 1열
- 32~48rem (512~768px): 카드 2열
- ≥ 48rem (768px): 데스크톱 테이블

Tailwind v4 의 first-class container query 활용 (별도 플러그인 불필요). 데스크톱 테이블의 viewport 기준 max-height 스크롤은 유지.

근거: 2026 베스트프랙티스 — page layout=media query, 컴포넌트=container query (LogRocket / NN-Group / Tailwind v4 가이드).
2026-05-13 08:43:13 +09:00
johngreen 1ba310236c Merge fix(반응형): 모바일 width 카드/이력 모달 스크롤
Build & Deploy to K8s / build-and-deploy (push) Failing after 29m24s
2026-05-13 08:09:01 +09:00
johngreen 0d5d1fe10d fix(반응형): 모바일 width 에서 카드 뷰 + 이력 모달 스크롤 누락
- ResponsiveDataView: 모바일 카드 뷰 (lg 미만) + 카드 스켈레톤에 scrollContainer 모드의 max-h + overflow-y-auto 적용. 데스크톱 테이블만 적용돼 있던 누락 픽스
- TableHistoryModal: timeline/detail 탭의 ScrollArea 고정 h-[500px] → 모바일 h-[300px] sm:h-[500px] 적응형. DialogContent max-h-[90vh] 와 충돌 방지

증상: 브라우저 width 좁아질 때 카드 그리드/이력 타임라인이 viewport 너머로 잘리고 스크롤 안 됨.
2026-05-13 08:08:58 +09:00
gbpark c3e04adb23 docs(notes): close input canonical cleanup 2026-05-13 02:50:30 +09:00
gbpark 7bd08dcf9d refactor(components): consolidate canonical input cleanup 2026-05-13 02:38:29 +09:00
johngreen 3dbc2107d8 Merge fix(비번초기화): 키 불일치 + 입력값 무시 픽스
Build & Deploy to K8s / build-and-deploy (push) Failing after 11m15s
2026-05-12 19:28:02 +09:00
johngreen 3eeb0764bf fix(비번초기화): 키 불일치 + 입력값 무시 픽스
- Frontend: body 키를 snake_case (user_id/new_password) 로 변환
- Controller: new_password 도 추출해서 service 에 전달
- Service: 2-arg 오버로드 추가, newPassword 입력값 사용 (blank 일 때만 Welcome1! fallback), userId null/blank 시 IllegalArgumentException

증상: 사용자관리에서 비밀번호 초기화 modal 입력 → backend 가 user_id=null 로 SQL 실행 (0행) + newPassword 무시 후 항상 Welcome1! 로 덮어쓰기.
2026-05-12 19:27:44 +09:00
DDD1542 4a8413000b Consolidate canonical input migration
Build & Deploy to K8s / build-and-deploy (push) Failing after 11m17s
Remove legacy v2 input/select and file/media runtimes, add canonical option/file loaders, and document Codex handoff.
2026-05-12 18:36:43 +09:00
johngreen 7706403caa Merge fix(대무자): insert date CAST
Build & Deploy to K8s / build-and-deploy (push) Failing after 11m19s
2026-05-12 18:23:51 +09:00
johngreen 3c24956efd fix(대무자): insert SQL 의 start_date/end_date 도 CAST AS DATE 추가
insertSubstitute 공백 이 char varying 으로 전달되어 DATE 컴럼 에 ERROR. checkOverlap, updateSubstitute 처럼 CAST(#{...} AS DATE) 로 일관되게 수정.
2026-05-12 18:23:49 +09:00
johngreen a7683d4d0e Merge fix(대무자): Select candidates data shape
Build & Deploy to K8s / build-and-deploy (push) Failing after 18m41s
2026-05-12 18:10:09 +09:00
johngreen c3e5d7fc1b fix(대무자): Select 후보 목록 - getUserList 응답 shape 호환
SubstituteSection 의 loadCandidates 가 res.data.list 를 가정했지만
getUserList(/api/admin/users) 응답은 { data: [...] } 형태 (data 가 list 자체).
결과로 모든 select 가 '지정 가능한 사용자가 없습니다' 로 표시됐음.

Array.isArray(res.data) 와 res.data.list 둘 다 fallback 으로 처리.
2026-05-12 18:10:07 +09:00
johngreen 33a245e4e8 Merge pull request 'fix(대무자): COMPANY_ADMIN + SQL + UI select' from johngreen into main
Build & Deploy to K8s / build-and-deploy (push) Failing after 3m24s
2026-05-12 17:02:44 +09:00
johngreen c4a62b7e35 fix(대무자): COMPANY_ADMIN 권한 허용 + 결재함 SQL 컬럼 오타 fix + UI 셀렉트 개선
운영 QA 에서 발견된 3가지 결함을 한 번에 수정.

1. SubstituteController.java:56 / SubstituteService.java:242 (requireAdmin)
   - role 비교에서 "COMPANY_ADMIN" 누락 → 운영 admin 이 대무자 지정 시 항상 403.
   - 운영 회사 admin 의 user_type 은 COMPANY_ADMIN 이 표준 (AdminAccountCreator 가 그렇게 생성).
   - "ADMIN" / "SUPER_ADMIN" 외 "COMPANY_ADMIN" 도 허용.

2. mapper/approval.xml (selectMyRequests, selectMyPendingLines)
   - ORDER BY / SELECT 의 R.CREATED_DATE 가 잘못된 컬럼명 (APPROVAL_REQUESTS 실제: created_at).
   - 결재함 /api/approval/my-pending, /api/approval/requests 가 항상 500.
   - 3군데 R.CREATED_DATE → R.CREATED_AT.

3. SubstituteSection.tsx
   - 대무자 ID 를 직접 타이핑하던 input 을 Select 로 교체.
   - getUserList 로 같은 회사 활성 사용자 목록 로드, 본인 + SUPER_ADMIN + 비활성 자동 제외.
   - 다이얼로그 열 때 한 번만 load (openDialog 시 loadCandidates).
   - 빈 결과/로딩 placeholder 처리.
2026-05-12 17:02:15 +09:00
hjjeong 081feff51f style(rolesList): 다른 메뉴 톤에 맞춰 사이즈/글씨 축소
본문 텍스트 text-sm → text-xs, 헤더 보조 텍스트 text-xs →
text-[11px], 카드 헤더 p-3 → p-2.5, 좌측 권한 목록 그리드
260px → 220px. Input/Select h-8 → h-7 (메인) / h-7 → h-6 (서브),
메뉴 트리 row py-2 → py-1.5, 트리 들여쓰기 level*20+12 →
level*16+10, chevron h-3.5 → h-3. 4분할 직원 카드 영역
clamp(220, 32vh, 320) → clamp(200, 28vh, 280).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:17:39 +09:00
DDD1542 90035dd5c6 feat(numbering): 채번 관리 페이지 통짜 작업대 디자인 적용
Build & Deploy to K8s / build-and-deploy (push) Failing after 11m4s
채번 = 독립 자원 (컬럼과 N:M 연결) 모델로 페이지 통째 리뉴얼.
카드 박스 폐기, 좌측 sidebar + 우측 통짜 main + ⌘K 명령 팔레트 구조.

- frontend/styles/v5-layout.css: v5-nrm-* 섹션 추가 (메뉴관리 v5-mm-* 동일 패턴)
- numberingRuleList/page.tsx: PageHead 안 쓰고 메뉴관리 스타일 단순 헤더 +
  사이드바 (검색·필터·섹션·list) + Hero·파이프라인·2-col split·sticky save bar +
  ⌘K 팔레트 (검색·프리셋·기존 채번)
- 기존 NumberingRuleDesigner/SequenceManagementPanel/CreateDialog 의 로직을
  page.tsx 내부 sub-component (PipelineBlock·PartInspector·UsageList·CommandPalette) 로 재구성
- mockup HTML 산출물 notes/gbpark/ 보관

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 12:06:28 +09:00
hjjeong ffa6799053 Merge pull request 'Merge branch 'main' into hjjeong' (#8) from hjjeong into main
Build & Deploy to K8s / build-and-deploy (push) Successful in 9m35s
Reviewed-on: #8
2026-05-12 02:48:19 +00:00
hjjeong 7315603f0f Merge branch 'main' into hjjeong
# Conflicts:
#	backend-spring/src/main/java/com/erp/migration/StartupSchemaMigrator.java
2026-05-12 11:42:17 +09:00
hjjeong 8bdc9a958f feat(favorites): 사용자별 메뉴 즐겨찾기
사이드바 leaf 메뉴 옆 별표로 토글하고, 사이드바 최상단 '즐겨찾기'
섹션에 등록된 메뉴를 모아 보여준다. 테넌트 DB 별로 격리.

- V020 + StartupSchemaMigrator: USER_MENU_FAVORITES (USER_ID,
  MENU_OBJID UNIQUE) 메타·테넌트 DB 동기 멱등 생성
- 백엔드: GET/POST /api/favorites/menus, DELETE
  /api/favorites/menus/{menuObjid} — FavoritesController/Service
  + mapper/favorites.xml (MENU_INFO JOIN)
- 프론트: favoritesAPI 클라이언트 + FavoritesContext (낙관적
  toggle/롤백) + (main)/layout 에 Provider 마운트
- AppLayout: leaf/sub-item 옆 Star 토글 (등록 시 primary 컬러,
  미등록 시 0.35 dimmed) + 사이드바 최상단 favorites 섹션. uiMenus
  의 leaf 평탄화 결과를 isFavorite 으로 필터링해 권한 필터/메뉴
  클릭 핸들러를 그대로 재사용

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 11:29:18 +09:00
johngreen 6561aad7ef Merge pull request 'feat(대무자): 대무자 관리 기능 — 결재 위임 + 컨텍스트 주입 + UI' (#7) from johngreen into main
Build & Deploy to K8s / build-and-deploy (push) Successful in 9m38s
2026-05-11 23:29:33 +00:00
johngreen af23fd0316 feat(대무자): StartupSchemaMigrator 에 RUN_086 자동 적용 등록
invyone 의 정상 마이그레이션 패턴(StartupSchemaMigrator)에 RUN_086 의 7개 idempotent
DDL/DML 등록. 다음 backend 부팅 시 메타 DB + 모든 활성 테넌트 DB 에 자동 적용됨.

- btree_gist 확장 (CREATE EXTENSION IF NOT EXISTS)
- USER_SUBSTITUTES 테이블 (PK, CHECK 2, EXCLUDE 제약, 인덱스 2)
- SYSTEM_AUDIT_LOG ALTER (PROCESSOR_ID/PROCESSOR_NAME ADD COLUMN IF NOT EXISTS)
- APPROVAL_PROXY_SETTINGS → USER_SUBSTITUTES idempotent 데이터 복사

호환성 보정:
- EXCLUDE 의 daterange lower 에 COALESCE(.., CURRENT_DATE) 사용 불가 (IMMUTABLE 만 허용)
  → daterange(START_DATE, END_DATE, '[]') 단순 형식. NULL lower = -infinity 자연 처리.
- APPROVAL_PROXY_SETTINGS 의 START_DATE/END_DATE 가 varchar → DATE cast 추가
- 원본 메타데이터(created/updated) 컬럼명 환경별 차이 회피 → 'migration_086' + NOW() 고정

실측: meta+3 tenants 모두 OK (test01/test02/siflex)
2026-05-12 08:20:59 +09:00
johngreen 6a9fc06f0e feat(대무자): 프론트엔드 UI — UserFormModal 대무자 섹션 + ProfileModal 조회 + 결재 뱃지
- frontend/lib/api/substitute.ts: 7개 API 함수 (Record<string, any> 컨벤션)
- components/admin/SubstituteSection.tsx (신규): 관리자용 대무자 지정 섹션
  · 활성/예정 대무 관계 테이블, 사전 겹침 검증
  · v5 토큰 (--v5-surface-solid, --v5-glow-sm) 사용, blur 금지
- components/admin/UserFormModal.tsx: 수정 모드일 때 SubstituteSection 노출
- components/layout/MySubstituteView.tsx (신규): ProfileModal 용 read-only 조회
  · 내 대무자 + 내가 대무 중인 사람 양방향, D-day 카운트다운
- components/layout/ProfileModal.tsx: MySubstituteView 삽입
- app/(main)/approval/page.tsx: 대기함 행에 "대무 ← {원본 결재자}" 뱃지
  · currentUser.user_id !== line.approver_id 비교 (별도 타입 필드 X)
2026-05-12 08:07:15 +09:00
johngreen c0bd420c66 feat(대무자): SYSTEM_AUDIT_LOG processor 분리 기록 + USER_INFO lookup
- auditLog.xml insertAuditLog INSERT 절에 PROCESSOR_ID/PROCESSOR_NAME 컬럼 추가
- auditLog.xml selectUserNameById — 처리자 이름 lookup
- AuditLogService.insertAuditLog:
  · processor_id null → user_id 로 자동 채움 (평시 = 동일)
  · processor_id != user_id 이고 processor_name null → USER_INFO 단건 조회 (대무 이벤트만)
- notes: 도메인 테이블 CREATED_BY/UPDATED_BY broad scan — actual processor(B) 통일 자동 만족
2026-05-12 08:06:55 +09:00
johngreen 6ab7c3e780 feat(대무자): 결재 시스템 어댑터 통합 — USER_SUBSTITUTES read + IN(effective_user_ids)
- approval.xml selectActiveProxyForLine: APPROVAL_PROXY_SETTINGS → USER_SUBSTITUTES 참조
  (BOOLEAN IS_ACTIVE, START_DATE NULL 허용)
- approval.xml 3곳 (selectRequests/countRequests/selectMyPendingLines):
  APPROVER_ID = #{user_id} → APPROVER_ID IN (effective_user_ids) foreach
- ApprovalService.ensureEffectiveUserIds helper — mybatis 빈 IN() 방지
- ApprovalController: getRequests/getMyPendingLines 에 @RequestAttribute(effective_user_ids) 추가
- ApprovalService.processApproval 마지막에 AuditLogService.insertAuditLog 호출 추가
  (user_id=A, processor_id=B 분리 기록)
2026-05-12 08:06:39 +09:00
johngreen 4a83bfc8e8 feat(대무자): SubstituteContextFilter — JWT 후단계 effective_user_ids 주입
- TenantConsistencyGuard 뒤, ForcePasswordChangeGuard 앞에 등록
- /api/** 요청에 effective_user_ids = [user_id, ...active_original_ids] attribute 세팅
- SUPER_ADMIN (company_code='*') 은 short-circuit
- DB 조회 실패 시 본 요청 차단 안 함 (가용성 우선, warn 로그)
2026-05-12 08:06:21 +09:00
johngreen 6a7d261d23 feat(대무자): SubstituteService/Controller/mapper — CRUD + 검증 + Filter 핫패스
- substitute.xml (namespace=substitute): selectSubstituteList/Cnt, selectMySubstitutes,
  selectActiveOriginalUserIds (Filter 핫패스), selectActiveProxyForLine (결재 어댑터),
  countOverlap, countUserInCompany, countSuperAdmin, insert/update/delete
- SubstituteService extends BaseService: 관리자 권한 검증, self-위임 차단,
  cross-company 검증, SUPER_ADMIN 을 proxy 로 지정 거부, 사전 겹침 검증
- SubstituteController: /api/substitutes CRUD + /mine (read-only) + /check-overlap
2026-05-12 08:06:03 +09:00
johngreen e4856dcae5 feat(대무자): RUN_086 DB 마이그레이션 — USER_SUBSTITUTES + SYSTEM_AUDIT_LOG processor 추적
- USER_SUBSTITUTES 테이블 (PK, CHECK 2개, EXCLUDE 제약, 인덱스 2개)
- btree_gist 확장 hard prerequisite
- SYSTEM_AUDIT_LOG.PROCESSOR_ID/PROCESSOR_NAME 컬럼 추가
- APPROVAL_PROXY_SETTINGS → USER_SUBSTITUTES idempotent 데이터 복사
- 사전/사후 검증 SQL + 롤백 섹션 포함
2026-05-12 08:05:47 +09:00
gbpark baffd6affb Merge origin/main into gbpark-node
Build & Deploy to K8s / build-and-deploy (push) Successful in 9m45s
부서관리 V1 슬림 스코프 + UX 리디자인, 25개 버그 일괄 수정, admin/부서관리
탭 라벨 fallback, Windows dev HMR 복원 흡수.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 21:51:34 +09:00
gbpark a5bbd1eb7c refactor(numbering-rule): NumberingRule → Input canonical 흡수 + 채번 관리 페이지 분리
- 옛 registry/numbering-rule, registry/v2-numbering-rule, V2NumberingRuleConfigPanel,
  NumberingRuleTemplate 폐기 — InvFieldConfigPanel + InputComponent 로 통합
- input 에 numbering-picker / select-pickers 추가, autonum 타입 흡수
- 채번 관리 전용 admin 페이지(systemMng/numberingRuleList) + CreateDialog +
  SequenceManagementPanel 신설
- backend NumberingRule controller/service/mapper 갱신 (시퀀스 관리 엔드포인트)
- input canonical 진행 노트 + 채번 관리 mockup 추가

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 21:42:13 +09:00
hjjeong 1bd0fd8b80 fix(rolesList): 권한관리 viewport 안에 맞도록 높이 조정
페이지 외부 컨테이너를 h-screen 으로 가두고, 상단 4분할 카드는
viewport 비례(clamp 220~320px), 하단 메뉴 트리는 남은 공간을
flex-1 + min-h-0 으로 채우되 max-h(clamp 280~430px) 로 상한 제한.

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