Merge remote-tracking branch 'origin/main' into gbpark-node
Build & Deploy to K8s / build-and-deploy (push) Successful in 11m34s

This commit is contained in:
DDD1542
2026-05-14 17:42:17 +09:00
78 changed files with 5968 additions and 754 deletions
@@ -0,0 +1,280 @@
# 배치/파이프라인 이식 작업 분석 (2026-05-12)
작성자: hjjeong
배경: 박창현 팀장님(`chpark`) 카톡 (2026-05-11)
> *"그 수집관리 쪽은 파이프라인 쪽 가져와야 하는데. 빠이프. 내가 파이프 커밋해둠."*
> *"DB 라던지 restapi 라던지 소스 데이터 가져온뒤에 우리 db에 적재할 때, 원본 소스의 값이 1 인데 우리 시스템은 Y 일경우 → 조건변환을 통해서 변경된 값으로 우리 db에 적재."*
---
## 한 줄 요약
**팀장님이 만들어두신 "파이프" 는 `/Users/jhj/vexplor_rps/` (별도 저장소) 에 있는 완성된 ETL 코드**. INVYONE 으로 옮기는 작업은 **Node.js → Spring 재작성** + 일부 Frontend 보강 + DB 스키마 한 컬럼 추가가 필요하다.
---
## 1. 도메인 매트릭스 — INVYONE 의 현재 상태
INVYONE 안에 비슷한 이름의 도메인이 셋이고, 셋 다 **데이터 이동 실행 로직이 비어있다**.
| 도메인 | 메뉴 | 페이지 | 상태 |
|---|---|---|---|
| 배치관리 (구) | `/admin/batch-management` | [page.tsx](../../frontend/app/(main)/admin/batch-management/page.tsx) 206줄 | 모달 기반 db-to-db 만 |
| 배치관리 (신) | `/admin/automaticMng/batchmngList` | [edit/[id]/page.tsx](../../frontend/app/(main)/admin/automaticMng/batchmngList/edit/[id]/page.tsx) 1850줄 | 매핑 UI 풍부 — **저장도 실행도 안 됨** |
| 수집관리 | `/admin/systemMng/collection-managementList` | [page.tsx](../../frontend/app/(main)/admin/systemMng/collection-managementList/page.tsx) | `executeCollection` 이 jobs 테이블에 빈 행 INSERT 후 `records_processed=0` 박고 종료 |
| 제어관리 (파이프라인) | `/admin/systemMng/dataflow/...` | [DataFlowDesigner.tsx](../../frontend/components/dataflow/DataFlowDesigner.tsx) | 노드 그래프 UI. flow_data JSONB 저장만. 실행 엔진 없음 |
→ 팀장님 카톡 의미: **수집관리 + 배치관리 양쪽에 파이프라인(변환/실행) 기능을 가져와야 한다**.
---
## 2. 진짜 파이프라인 코드 위치 — `/Users/jhj/vexplor_rps/`
별도 git 저장소. INVYONE 의 [`_pipeline_backup/`](../../_pipeline_backup/) 폴더와는 무관(그건 mcp-agent-orchestrator 실행 기록).
### Backend — `vexplor_rps/backend-node/` (TypeScript/Node.js)
| 파일 | 역할 |
|---|---|
| `src/services/batchSchedulerService.ts` | **ETL 본체**. `executeMapping()` 함수가 FROM 읽기 → 변환 → TO 저장 3단계 실행 |
| `src/services/batchManagementService.ts` | 외부 DB 커넥터 (PG/MySQL/Oracle/MSSQL 등) |
| `src/services/erpApiClient.ts` (226줄) | Wehago/Amaranth ERP REST API 호출, HMAC-SHA256 서명 |
| `src/services/erpBatchSeedService.ts` (429줄) | 6종 매칭 배치 자동 시드 |
| `src/services/erpPresetSeedService.ts` (158줄) | REST API 연결 사전 설정 |
| `src/services/erpSyncService.ts` (539줄) | 동기화 로직 |
| `src/services/erpTableMigration.ts` (172줄) | Idempotent 마이그레이션 |
#### `executeMapping()` 핵심 흐름 (`batchSchedulerService.ts`)
```
L291~500 FROM 읽기 — 연결별 테이블 그룹화 후 batch fetch
├ internal: PostgreSQL 직접 쿼리
├ external_db: DatabaseConnectorFactory 동적 커넥터
└ restapi: GET/POST 응답 + dataArrayPath 추출
L550~596 매핑 변환 — row.map() 안에서 mapping_type 분기
├ "direct": row[from] → to (그대로 복사)
├ "fixed": from_column_name 자체가 고정값
└ "conditional": when/then 매칭 + default ← 1→Y 변환
L619~ TO 저장
├ DB: INSERT 또는 UPSERT (save_mode 기반)
└ REST: POST/PUT/DELETE + Request Body 템플릿
```
### Frontend — `vexplor_rps/frontend/`
| 파일 | 역할 |
|---|---|
| `app/(main)/admin/batch-management-new/page.tsx` (1865줄) | 배치 에디터 UI 본체. 좌우 2패널 + 매핑 그리드 + ConditionalEditor |
| `lib/api/batch.ts`, `batchManagement.ts` | API 클라이언트 + 타입 정의 |
INVYONE 의 [`batchmngList/edit/[id]/page.tsx`](../../frontend/app/(main)/admin/automaticMng/batchmngList/edit/[id]/page.tsx) (1850줄) 가 vexplor_rps 의 그것과 **거의 동일 구조** — 팀장님이 INVYONE 으로 한 번 옮긴 흔적. 다만 `mapping_type='conditional'` 분기와 ConditionalEditor 가 빠져있다.
---
## 3. 조건 변환 자료구조
### 매핑 row 데이터 모델
```typescript
{
id: string,
batch_config_id: string,
from_connection_type: "internal" | "external_db" | "restapi",
from_table_name: string,
from_column_name: string,
to_connection_type: "internal" | "external_db" | "restapi",
to_table_name: string,
to_column_name: string,
mapping_order: number,
mapping_type: "direct" | "fixed" | "conditional", // ← 핵심
mapping_config: ConditionalConfig | null, // ← 신규 컬럼 필요
// ... API 매핑용 (from_api_url, to_api_body 등)
}
```
### `ConditionalConfig` (mapping_type='conditional' 일 때)
```typescript
interface ConditionalConfig {
rules: { when: string; then: string }[];
default: string;
}
```
### 평가 알고리즘 (단순 문자열 매칭)
```ts
function evaluateConditional(sourceVal: string, cfg: ConditionalConfig): string {
for (const rule of cfg.rules) {
if (rule.when === sourceVal) return rule.then;
}
return cfg.default;
}
```
표현식 평가(SpEL/JEXL) 안 씀. Java 로 옮길 때 `Map<String,String>` + `getOrDefault` 한 줄이면 끝.
### 예시 (팀장님 카톡: 1 → Y)
```json
{
"mapping_type": "conditional",
"mapping_config": {
"rules": [
{ "when": "1", "then": "Y" },
{ "when": "0", "then": "N" }
],
"default": "?"
}
}
```
---
## 4. chpark 결정적 커밋 목록 (vexplor_rps)
```
2026-05-12 945b65b8 시퀀스 관리 메뉴 + 테이블 타입관리 코멘트/검증 + 설계 문서
2026-05-11 a61643c2 Merge origin/main
2026-05-08 57509869 배치 편집 conditional 매핑 평가 필드 UX 개선 ⭐
2026-05-08 40070423 ECR · 고객 CS · 결재 + Amaranth + wace_plm 데이터 import
2026-05-07 638543b3 Merge feature/rps-rebrand-pipeline-design ⭐
2026-05-07 97b333dd Amaranth(Wehago) ERP REST API 연계 + 배치 시스템 강화 ⭐
2026-04-30 9a8196a3 RPS 브랜딩 · COMPANY_16 단독 운영 · Pipeline 디자인 채용
```
### 결정적 커밋 분석
| 해시 | 일자 | 내용 |
|---|---|---|
| **97b333dd** | 5/7 | 배치 시스템 본체 강화 (conditional mapping, row_filter, UPSERT) + ERP 연계 |
| **638543b3** | 5/7 | `feature/rps-rebrand-pipeline-design` 브랜치 머지 (Pipeline 디자인 반영) |
| **57509869** | 5/8 | conditional 매핑의 "평가 필드" 필수 표기 UX 개선 (1파일 +27/13) |
**conditional 매핑 본체 구현은 97b333dd (5/7)**, 57509869 는 그 위에 UX 보완.
---
## 5. INVYONE 이식 작업
### 5-1. Backend (Spring 재작성)
| vexplor_rps (Node.js) | INVYONE (Spring) 대응 | 분량 |
|---|---|---|
| `batchSchedulerService.executeMapping()` | 신규 `BatchExecutor.java` (`@Service`, BaseService 상속) | ~2주 |
| 외부 DB 커넥터 | 이미 일부 있음 ([ExternalDbConnectionService](../../backend-spring/src/main/java/com/erp/service/ExternalDbConnectionService.java)) | ~3일 |
| REST API 호출 | 이미 있음 ([ExternalRestApiConnectionService](../../backend-spring/src/main/java/com/erp/service/ExternalRestApiConnectionService.java)) | 통합만 |
| `evaluateConditional()` | `MappingTransformer.java` (Map 기반 lookup) | ~반나절 |
| Quartz 스케줄링 | INVYONE 에 이미 Quartz 도입됨 (`V012__create_quartz_tables.sql`) | 통합만 |
| ERP 사전 시드 / HMAC | 선택적 (Phase 4) | 1주+ |
### 5-2. Frontend
| 작업 | 위치 | 분량 |
|---|---|---|
| `ConditionalEditor` 컴포넌트 신규 | `components/admin/batch/ConditionalEditor.tsx` (신규) | 1일 |
| 매핑 row 에 `sourceType='conditional'` 옵션 + `mapping_config` 필드 | [`batchmngList/edit/[id]/page.tsx`](../../frontend/app/(main)/admin/automaticMng/batchmngList/edit/[id]/page.tsx) | 1일 |
| API 타입 확장 | [`lib/api/batch.ts`](../../frontend/lib/api/batch.ts) | 0.5일 |
### 5-3. DB 스키마 변경
| 컬럼 | 필요 여부 |
|---|---|
| `batch_mappings.mapping_type` | **이미 있음** (현재 'direct' 값만 사용) |
| `batch_mappings.mapping_config JSONB` | **추가 필요** (conditional_config 저장) |
| `batch_mappings.row_filter JSONB` | (선택) row 단위 필터링 룰. vexplor_rps 에 있음 |
마이그레이션: Flyway V021 + StartupSchemaMigrator 양쪽에.
### 5-4. INVYONE 측 선결과제 (Phase 0)
vexplor_rps 의 이식 외에도 INVYONE 자체의 **매핑 path 가 비어있는 문제** 가 별도. 이 Phase 0 가 없으면 conditional 룰을 만들어도 저장 안 됨.
| 작업 | 비고 |
|---|---|
| `batch_mappings` INSERT/UPDATE/DELETE 매퍼 작성 | 현재 0건 |
| `getBatchInfo` 응답에 batch_mappings 포함 (LEFT JOIN 또는 별도 query) | 현재 BATCH_CONFIGS 만 SELECT |
| `BatchManagementService.updateBatchConfig` 가 body 의 mappings 받아 batch_mappings 동기화 | 현재 silently drop |
| `executeBatchConfig``BatchExecutor` 호출 | 현재 stub, 0건 응답 |
---
## 6. Phase 분해 (재추정)
| Phase | 작업 | 분량 |
|---|---|---|
| **0** | INVYONE 의 `batch_mappings` CRUD path 구현 + GET 응답 포함 + PUT 동기화 | 3일 |
| **1** | `mapping_config JSONB` 컬럼 추가 (Flyway V021 + StartupSchemaMigrator) | 0.5일 |
| **2** | Frontend `ConditionalEditor` + sourceType='conditional' 분기 + API 타입 확장 | 2일 |
| **3** | Backend `MappingTransformer` (lookup 엔진) | 0.5일 |
| **4** | Backend `BatchExecutor` 작성 — vexplor_rps `executeMapping` 알고리즘 1:1 이식 (FROM→Transform→TO 3단계, 내부/외부 DB/REST 모두 지원) | 2주 |
| **5** | `executeBatchConfig``BatchExecutor` 호출, 실행 로그 (`batch_execution_logs`) 기록 | 3일 |
| **6** | (선택) ERP 사전 시드, HMAC 서명, 6종 배치 자동 부팅 | 1주+ |
**합계** (Phase 0~5): 약 **3~4주** (1인 기준, 테스트·QA 제외). Phase 6 는 별도.
---
## 7. 팀장님께 확인하면 좋을 항목
1. **vexplor_rps 의 `batchSchedulerService.ts` 알고리즘을 1:1 이식하는 게 맞나** — 아니면 더 단순화/확장된 버전을 원하시는지
2. **conditional 외에 다른 mapping_type 도 필요한가** — row_filter, expression eval 등
3. **ERP 시드 (Phase 6) 도 이번 스프린트 범위인가** — 아니면 Phase 0~5 만
4. **수집관리(`collection-managementList`) 와 배치관리(`batchmngList`) 가 같은 코드 공유해도 되는지** — UI 두 군데에 둘 다 노출인지
5. **`mapping_config` 컬럼명 선호** — 다른 컨벤션이 있다면
---
## 부록 A. 분석 시 사용한 주요 파일
### INVYONE 측
- [`BatchController.java`](../../backend-spring/src/main/java/com/erp/controller/BatchController.java)
- [`BatchManagementController.java`](../../backend-spring/src/main/java/com/erp/controller/BatchManagementController.java)
- [`BatchService.java`](../../backend-spring/src/main/java/com/erp/service/BatchService.java)
- [`BatchManagementService.java`](../../backend-spring/src/main/java/com/erp/service/BatchManagementService.java)
- [`CollectionService.java`](../../backend-spring/src/main/java/com/erp/service/CollectionService.java)
- [`NodeFlowService.java`](../../backend-spring/src/main/java/com/erp/service/NodeFlowService.java)
- [`mapper/batch.xml`](../../backend-spring/src/main/resources/mapper/batch.xml)
- [`mapper/collection.xml`](../../backend-spring/src/main/resources/mapper/collection.xml)
- [`mapper/batchManagement.xml`](../../backend-spring/src/main/resources/mapper/batchManagement.xml)
- [`mapper/nodeFlow.xml`](../../backend-spring/src/main/resources/mapper/nodeFlow.xml)
- [`frontend/app/(main)/admin/automaticMng/batchmngList/`](../../frontend/app/(main)/admin/automaticMng/batchmngList/)
- [`frontend/app/(main)/admin/systemMng/collection-managementList/`](../../frontend/app/(main)/admin/systemMng/collection-managementList/)
- [`frontend/app/(main)/admin/systemMng/dataflow/`](../../frontend/app/(main)/admin/systemMng/dataflow/)
- [`frontend/components/dataflow/DataFlowDesigner.tsx`](../../frontend/components/dataflow/DataFlowDesigner.tsx)
### vexplor_rps 측 (별도 저장소)
- `/Users/jhj/vexplor_rps/backend-node/src/services/batchSchedulerService.ts`
- `/Users/jhj/vexplor_rps/backend-node/src/services/batchManagementService.ts`
- `/Users/jhj/vexplor_rps/backend-node/src/services/erp*.ts`
- `/Users/jhj/vexplor_rps/frontend/app/(main)/admin/batch-management-new/page.tsx`
### 운영 DB
- `batch_configs`, `batch_mappings` (스키마 ok / 운영 데이터 ok / backend 처리 없음), `batch_execution_logs`, `node_flow`, `data_collection_configs`
---
## 부록 B. INVYONE batch_mappings 운영 데이터 현황
```text
batch_config_id | count
----------------+------
5 | 5
10 | 4
18 | 4
20 | 2
21 | 4
28 | 1
30 | 1
31 | 2
32 | 2
37 | 4
```
`created_date` 가 2025-09 등으로 오래된 행 다수. 현 backend 코드는 이 데이터를 읽지도 쓰지도 못함 — 사실상 dead data. vexplor_rps 시절 또는 옛 Node.js 시절의 흔적으로 추정.
@@ -0,0 +1,87 @@
# 대무자(代務者) Phase 1 — 도메인 테이블 CREATED_BY/UPDATED_BY broad scan
작성일: 2026-05-11
작성자: johngreen
관련: `.omc/specs/deep-dive-user-substitute-management.md`, `.omc/plans/autopilot-impl.md` (T15)
## 목적
Phase 1 의 spec 결정 "일반 업무 도메인 테이블의 `created_by/updated_by` 는 actual processor(B) 로 통일" 이 현재 코드에서 자연 만족하는지 검증.
## scan 범위
`backend-spring/src/main/resources/mapper/*.xml``CREATED_BY` / `UPDATED_BY` 컬럼 사용처 전수.
## 결과 요약
| 항목 | 값 |
|---|---|
| 컬럼 사용 mapper 파일 | **33개** |
| 매치 occurrences | **206개** |
| 호출자(controller) 의 `user_id` 출처 | **모두 `@RequestAttribute("user_id")` = JWT user_id** |
| 대무 컨텍스트 자동 적용 여부 | **✅ 자동 만족 — 추가 작업 불필요** |
## 핵심 발견
### 1. 도메인 테이블의 `CREATED_BY/UPDATED_BY` 는 자동으로 actual processor(B)
INVYONE 의 모든 컨트롤러가 `@RequestAttribute("user_id") String userId` 패턴으로 JWT user_id 를 받아 params.put("user_id", userId) 후 service 호출.
`JwtAuthenticationFilter.java:38-41`:
```java
request.setAttribute("user_id", userId); // JWT 의 user_id = 실제 로그인한 사용자 = actual processor B
```
`SubstituteContextFilter` (T5) 는 JWT user_id 를 그대로 두고 `effective_user_ids` 만 별도 attribute 로 추가 주입. 즉 `request.getAttribute("user_id")` 는 평시와 대무 시 동일하게 B (로그인한 사용자).
→ 도메인 테이블 INSERT/UPDATE 시 컨트롤러가 user_id 를 params 에 넣으면 그 값은 자연스럽게 B. spec 결정과 일치.
### 2. effective_user_ids 를 user_id 로 잘못 쓰는 패턴 없음
`effective_user_ids` 는 본 작업(T8) 이전에는 존재하지 않던 attribute. 기존 코드 어디도 이걸 user_id 로 끌어쓰지 않음.
T8 이후 effective_user_ids 를 사용하는 곳:
- `approval.xml` selectRequests/countRequests my_approvals 분기 (L213-220, 247-254)
- `approval.xml` selectMyPendingLines (L458-)
이 3곳은 **WHERE 조건의 권한 union 용도** 로만 사용하고, 어디서도 `CREATED_BY = #{effective_user_ids[0]}` 같은 패턴 없음.
### 3. 결재 audit log 만 user_id ≠ processor_id 분리 기록
`ApprovalService.processApproval` (T9) → `AuditLogService.insertAuditLog` 호출:
- `user_id` = A (위임자, APPROVER_ID)
- `processor_id` = B (실제 처리자)
- `SYSTEM_AUDIT_LOG` 에만 분리. 도메인 테이블 (APPROVAL_LINES 등) 의 `CREATED_BY/UPDATED_BY` 는 그대로 B.
## 33개 파일 목록 (참고)
```
admin-cross-tenant.xml (2) approval.xml (4) barcodeLabel.xml (8)
batch.xml (2) businessRule.xml (8) cascadingHierarchy.xml (2)
cascadingRelation.xml (5) categoryValueCascading.xml (3) collection.xml (7)
common.xml (2) dashboard.xml (2) dataflow.xml (6)
dataflowDiagram.xml (3) digitalTwin.xml (9) dynamicForm.xml (4)
externalCall.xml (1) externalCallConfig.xml (8) externalDbConnection.xml (9)
externalRestApiConnection.xml (4) flow.xml (2) flowExternalDbConnection.xml (7)
layout.xml (3) multilang.xml (25) production.xml (4)
report.xml (22) schedule.xml (2) screenEmbedding.xml (2)
screenManagement.xml (21) substitute.xml (5) systemNotice.xml (3)
tableCategoryValue.xml (9) template.xml (9) webTypeStandard.xml (3)
```
## Phase 1 결론
**도메인 테이블의 `CREATED_BY/UPDATED_BY` 에 대해 추가 작업 없음.**
- spec 의 "actual processor(B) 통일" 결정이 기존 컨트롤러 패턴으로 자연 만족.
- 대무 추적은 `SYSTEM_AUDIT_LOG.PROCESSOR_ID` 컬럼으로 분리 기록 (T1 + T14).
- 회귀 위험 영역 없음.
## Phase 2 (메뉴/일반 권한 union) 으로 미룬 영역
spec 의 "B 로그인 시 A 의 권한이 자동 합쳐서 적용" 중 **메뉴/AUTHORITY_MASTER 룩업** 영역은 Phase 1 Non-Goal:
- `admin.xml`, `auth.xml`, `role.xml`, `screenGroup.xml` 의 사용자 권한/메뉴 쿼리에 `IN (effective_user_ids)` 적용 필요
- Phase 2 작업 — 별도 plan + critic 검증 후 진행
scan 진행: ✅ 완료